写点什么

MyBatis 程序基础实现

作者:andy
  • 2022-10-27
    北京
  • 本文字数:7199 字

    阅读完需:约 24 分钟

一、开发项目的搭建


1、创建基于 MyBatis 框架的 Maven 项目;

2、配置 pom.xml 文件,追加 MyBatis 开发包;


<dependency>			<groupId>org.mybatis</groupId>			<artifactId>mybatis</artifactId>			<version>3.4.5</version>		</dependency>
复制代码


3、设置 MyBatis 核心内容配置文件,核心内容包括:

  • 数据源配置(基于 Spring 框架,则免除配置);

  • 相关资源文件映射配置(一张表一个资源文件);

  • 缓存配置;

  • 类别名配置;


核心内容文件置于 resources 目录下。

配置范例:


<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configuration  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration>  <environments default="development">    <environment id="development">      <transactionManager type="JDBC"/>      <dataSource type="POOLED">        <!--         	using org.gjt.mm.mysql.Driver class before driver version 6.0.X         	It depends on MySql.        	When use MySql 5.7.+, com.mysql.cj.jdbc.Driver should be used as driver class.        -->        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>        <!--        	Url format depends on MySql.        	When use MySql 5.7.+, configuration of serverTimezone and useSSL should be added.        	Based on inner system interaction, SSL is closed.         -->        <property name="url" value="jdbc:mysql://localhost:3306/owndb?useSSL=false&amp;serverTimezone=UTC"/>        <property name="username" value="root"/>        <property name="password" value="admin"/>      </dataSource>    </environment>  </environments>  <mappers>  	<mapper resource="org/fuys/owndb/vo/mapping/User.xml"/>  </mappers></configuration>
复制代码


说明:

,表示的是数据库的连接类型。

  • POOLED,表示使用数据库连接池的方式进行数据库连接,性能最好;

  • JNDI,使用服务器上配置好的数据库连接池进行连接;

  • UNPOOLED,表示不适用数据库连接池进行连接,而是在需要使用时,创建一个新的连接,性能最差。

,表示事务管理。

  • JDBC,使用 JDBC 进行数据库事务的管理控制,唯一可选项;

  • MANAGED,不做事务处理,交由容器解决。


4、添加 MySql 数据库连接支持


<dependency>			<groupId>mysql</groupId>			<artifactId>mysql-connector-java</artifactId>			<version>6.0.6</version>		</dependency>
复制代码


5、添加数据库脚本,创建数据库表


drop database if exists owndb;create database owndb char set utf8;use owndb;drop table if exists user;create table user(	uid		int		auto_increment,	name	varchar(255) not null,    constraint pk_uid primary key(uid)) engine InnoDB;alter table user add sex int;
复制代码


其中,engine InnoDB 表示支持事务处理。


二、实现数据操作


1、根据 ORM 映射关系,创建对象的 VO 类。


package org.fuys.owndb.vo;import java.io.Serializable;@SuppressWarnings("serial")public class User implements Serializable {	private String name;	// getter和setter略}
复制代码


2、资源目录下创建于 vo 类对应的映射文件。

namespace 是必须要求的,这样可以避免混淆。同时,可以使用完全限定的名称超越简化的声明,因为可以与接口绑定


<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="org.fuys.owndb.vo.mapping.User">	<insert id="insert" parameterType="org.fuys.owndb.vo.User">		insert user(name) values(#{name})	</insert></mapper>
复制代码


3、核心配置文件(mybatis-config.xml)添加 vo 类映射文件的注册配置,让 MyBatis 能够找到映射文件的位置。


<mappers>  	<mapper resource="org/fuys/owndb/vo/mapping/User.xml"/>  </mappers>
复制代码


4、进行数据库的操作,任意一个 MyBatis 程序都是围绕 SqlSessionFactory 展开的,主要依赖以下几个类:

  • MyBatis 资源文件通过 Reader、InputStream 读取;

  • MyBatis 的连接需要通过连接工厂取得,连接工厂即:org.apache.ibatis.session.SqlSessionFactory

  • MyBatis 具体的连接使用 org.apache.ibatis.session.SqlSession



图 MyBatis 执行流程


Java 代码示例


package org.fuys.owndb.test;import java.io.InputStream;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.fuys.owndb.vo.User;import org.junit.Test;import junit.framework.TestCase;public class UserVoTest {	@Test	public void testInsert() throws Exception{			InputStream is = Resources.getResourceAsStream("mybatis-config.xml");		SqlSessionFactory sqlFactory = new SqlSessionFactoryBuilder().build(is);		SqlSession session = sqlFactory.openSession();		User user = new User();		user.setName("jackson");			int len = session.insert("org.fuys.owndb.vo.mapping.User.insert",user);		TestCase.assertEquals(len, 1);		session.commit();		session.close();		is.close();	}}
复制代码


5、范围(scope)和生命周期(lifecycle)

依赖注入框架能够创建线程安全的、事务的 sqlsession 和映射器(mapper)。同时,能够将他们直接注入到 bean 中。但这样往往会让开发者忽略了对象是具有生命周期的。

SqlSessionFactoryBuilder,只要创建了 SqlSessionFactory 类对象,便不在需要,其范围是方法范围(method scope)。

SqlSessionFactory,应用程序执行期间,该工厂类一直存在,也不用多次重新创建。因此,该工厂类的范围是应用范围(application scope)。

SqlSession,每一个线程拥有独立的 SqlSession 实例,由此,SqlSession 是非线程安全的。最佳的范围是方法范围,也最好不要一直长期保持对 SqlSession 的引用。建议在代码的最后,加上 SqlSession 的关闭操作。


三、自定义 SessionFactory 工厂


考虑到代码的重用性,MyBatis 的数据库操作的固化形式,为了简化 SqlSessionFactory 和 SqlSession 代码编写,可以编写自定义的工厂类。



图 自定义 MyBatis 的 SessionFactory 工厂类


自定义的 SessionFactory 工厂类需要实现以下几个功能点:

  • 获取 SqlSessionFactory 和 SqlSession 对象;

  • 关闭 SqlSession 对象;

  • 重新建立 SqlSessionFactory 对象,SqlSessionFactory 并不相当于连接池的管理者;

工厂类:


package org.fuys.owndb.model;import java.io.InputStream;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 1,get SqlSessionFactory and SqlSession object<br> * 2,close SqlSession connection<br> * 3,rebuild SqlSessionFactory * @author ys * */public class MyBatisSqlSessionFactory {	/**	 * Define logger	 */	private static Logger logger = LoggerFactory.getLogger(MyBatisSqlSessionFactory.class);	/**	 * Define MyBatis-config.xml path	 * JVM read from classPath	 */	private static String MYBATIS_CONFIG = "mybatis-config.xml";	/**	 * Define inputStream to read mybatis-config.xml file	 */	private static InputStream is = null;	/**	 * Define sqlSessionFactory object	 */	private static SqlSessionFactory sqlSessionFactory = null;	/**	 * Ensure sqlSessionFacoty object is not null	 */	static{		rebuildSqlSessionFactory();	}	/**	 * Define local thread to save SqlSession object	 */	private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<>();	/**	 * Rebuild sqlSessionFactory object	 */	public static void rebuildSqlSessionFactory(){		try {			if(sqlSessionFactory==null){				is = Resources.getResourceAsStream(MYBATIS_CONFIG);				sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);			}		} catch (Exception e) {			logger.error("rebuild SqlSessionFactory Exception", e);		}finally{			try {				is.close();			} catch (Exception e2) {				logger.error("close inputStream", e2);			}		}	}	/**	 * Get sqlSessionFactory object	 * @return	 */	public static SqlSessionFactory getSqlSessionFactory(){		return sqlSessionFactory;	}	/**	 * Get sqlSession object	 * @return	 */	public static SqlSession getSqlSession(){		SqlSession sqlSession = threadLocal.get();		if(sqlSession==null){			if(sqlSessionFactory==null){				rebuildSqlSessionFactory();			}			sqlSession = sqlSessionFactory.openSession();			threadLocal.set(sqlSession);		}		return sqlSession;	}	/**	 * Close SqlSession object	 */	public static void closeSqlSession(){		SqlSession sqlSession = threadLocal.get();		if(sqlSession!=null){			sqlSession.close();			threadLocal.remove();		}	}	}
复制代码


测试工厂类:


package org.fuys.owndb.test;import org.apache.ibatis.session.SqlSession;import org.fuys.owndb.model.MyBatisSqlSessionFactory;import org.fuys.owndb.vo.User;import org.junit.Test;import junit.framework.TestCase;public class TestMyBatisSqlSessionFactory {	@Test	public void test() {		User user = new User();		user.setName("Yao Ming");		user.setSex(1);			SqlSession session = MyBatisSqlSessionFactory.getSqlSession();		int len = session.insert("org.fuys.owndb.vo.mapping.User.insert",user);		session.commit();		MyBatisSqlSessionFactory.closeSqlSession();		TestCase.assertEquals(len, 1);	}}
复制代码


四、数据库 CRUD 操作


对于持久层框架而言,最基本的操作便是 CRUD 处理,这是最最基本功。CRUD 也是实体层最需要去实现的功能。

整个 CRUD 的配置与代码添加,其运作流程已在文章中的第二部分(实现数据操作)说明,在此不再赘述。 以下针对各个环节提出一些注意点,在未来的开发中,得以避免相关问题的发生。

4.1、数据增加

创建数据表,如下:


# goal tabledrop table if exists goal;create table goal(	goalId		int		auto_increment,    name		varchar(30)		not null,    description	varchar(255),    start_time	timestamp,    end_time	timestamp,    constraint	pk_goalId	primary key (goalId))engine InnoDB;
复制代码


创建对应的 VO 类,如下:


package org.fuys.owndb.vo;import java.io.Serializable;import java.util.Date;@SuppressWarnings("serial") // Different from specific valuepublic class Goal implements Serializable {	private String goalId;	private String name;	private String description;	private Date startTime;	private Date endTime;	// getter and setter method ....}
复制代码


注意一:

当数据库表字段的属性名称包含有下划线“_”,但是对于 Java 规范的命名规则,建议对象变量属性的名称不要带有下划线“_”(当定义常量时可使用下划线“_”)。如 goal 表中字段 start_time 与 Goal 类的属性 startTime。


注意二:

可在核心内容配置文件(mybatis-config.xml)中添加 VO 类的别名,方便各个配置文件引用该 VO 类。


<typeAliases>		<typeAlias type="org.fuys.owndb.vo.Goal" alias="Goal"/>	</typeAliases>
复制代码


执行的测试程序:


@Test	public void testInsertGoal() throws Exception{		Goal goal = new Goal();		goal.setName("Executor" + new Random().nextInt(9999));		goal.setDescription("This is the most important goals in the world.");		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");		goal.setStartTime(sdf.parse("2018-01-01 00:00:00"));		goal.setEndTime(sdf.parse("2099-12-30 23:59:59"));				int len = MyBatisSqlSessionFactory.getSqlSession().insert("org.fuys.owndb.vo.mapping.Goal.insert", goal);		MyBatisSqlSessionFactory.getSqlSession().commit();		MyBatisSqlSessionFactory.closeSqlSession();		logger.info("Goal vo is " + goal);		TestCase.assertEquals(len, 1);	}
复制代码


测试结果:



INFO TestMyBatisSqlSessionFactory - Goal vo is Goal [goalId=null, name=Executor5271, description=This is the most important goals in the world., startTime=Mon Jan 01 00:00:00 CST 2018, endTime=Wed Dec 30 23:59:59 CST 2099]
复制代码


注意三:

为了达到简便多次,不修改传递参数代码的目的,可以在测试类中的传递对象参数,加上一个随机值,这样就不用多次去改变传递参数代码,减少测试繁琐事物。如:goal.setName("Executor" + new Random().nextInt(9999));

注意四:

测试程序结果中,VO 对象未返回生成记录的自增长主键值,可通过以下配置方式修改。当记录生成之后,即可获取返回自增长主键值。


<insert id="insert" parameterType="Goal" keyProperty="goalId" useGeneratedKeys="true">		insert goal(name,description,start_time,end_time) 		values(#{name},#{description},#{startTime},#{endTime})	</insert>
复制代码


返回结果:



INFO TestMyBatisSqlSessionFactory - Goal vo is Goal [goalId=3, name=Executor8915, description=This is the most important goals in the world., startTime=Mon Jan 01 00:00:00 CST 2018, endTime=Wed Dec 30 23:59:59 CST 2099]
复制代码


4.2、数据修改和删除


注意:

MyBatis 框架中,所有的 CRUD 操作都是由 SqlSession 控制的。而接口中的 insert、update、delete 方法,实际上没有什么区别,可以互相使用,因为 JDBC 中 PreparedStatement 接口的 insert、update、delete 方法调用的都是 executeUpdate 方法。


4.3、数据查询

对于数据的查询,分为两种:

  • 查询单个数据对象;

  • 查询多个数据对象。

无论是查询单个还是查询多个,都需要利用反射机制实现 VO 转换。因此,需要保证 VO 的属性与数据库表中的属性保持一致。如若不一致,有两种方式解决:

  • 查询列设置别名,而这个别名与 vo 对象属性一致


<select id="selectByGoalId" parameterType="int" resultType="Goal">		SELECT goalId,name,description,start_time AS "startTime",end_time AS "endTime"		FROM goal		WHERE goalId = #{goalId}	</select>
复制代码


  • 设置 vo 对象与数据库表的整体映射


<resultMap type="Goal" id="GoalMap">		<id column="goalId" property="goalId"/>		<result column="name" property="name"/>		<result column="description" property="description"/>		<result column="start_time" property="startTime"/>		<result column="end_time" property="endTime"/>	</resultMap>	<select id="selectAll" resultMap="GoalMap">		SELECT goalId,name,description,start_time,end_time		FROM goal	</select>
复制代码


4.4、分页数据查询


注意一:

在整个项目开发中,对于分页数据的查询,一般需要完成两方面内容:

  • 查询条件下的数据信息;

  • 查询条件下的数据个数。

而我们往往会遇到分页的模糊查询,那么,对于传递的参数,需有:当前页、每页显示的行数、列名称、关键字。


Java 示例:


@Test	public void testSelectSplit(){		Map<String, Object> map = new HashMap<>();		map.put("column", "name");		map.put("keyword", "%%");		int count = MyBatisSqlSessionFactory.getSqlSession().selectOne("org.fuys.owndb.vo.mapping.Goal.selectCount",map);		TestCase.assertTrue(count>-1);		logger.info(String.valueOf(count));		map.put("start", 3);		map.put("linesize", 2);		List<Goal> goalList = MyBatisSqlSessionFactory.getSqlSession().selectList("org.fuys.owndb.vo.mapping.Goal.selectSplit",map);		TestCase.assertNotNull(goalList);		logger.info(String.valueOf(goalList.size()));		logger.info(goalList.toString());	}
复制代码


注意二:

${}:不会变为“?”进行解析,而是直接使用相关值。

#{}:会变为“?”进行解析,从而获取正确格式的值。

问题:

mapper 配置文件中,对于 delete 方法,怎样控制,可以使得开发不会因为程序漏洞而误删所有数据?

1、数据定期备份,可以暂时解决;

2、利用数据库的数据恢复功能,前提是数据库支持这一个功能;

3、可以限定 delete 的删除条件以及参数类型(如参数设置为 int,默认值为 0,不会删除所有数据),避免产生没有 where 条件而删除所有的情况。


四、使用 Java 程序实现 SqlSessionFactory


使用 Java 代码同样可以实现 SqlSessionFactory 功能,但是,需要注意,mapper 添加 Java 类,需要使用 Annotation。


代码示例如下:


DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();TransactionFactory transactionFactory = new JdbcTransactionFactory();Environment environment = new Environment("development", transactionFactory, dataSource);Configuration configuration = new Configuration(environment);configuration.addMapper(BlogMapper.class);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
复制代码


用户头像

andy

关注

还未添加个人签名 2019-11-21 加入

还未添加个人简介

评论

发布
暂无评论
MyBatis程序基础实现_andy_InfoQ写作社区