MyBatis-浅尝
简介
MyBatis什么?
官网给出的答案是:MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
MyBatis其实就是一个中间件,对JDBC进行封装,让开发人员跟加专注于SQL,而不是数据库的连接等额外事情。
MyBatis总体架构
- mybatis-config.xml:为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。
- SqlSessionFactoryBuilder(构造器): 它会根据配置或者代码来生成 SqlSessionFactory,采用的是分步构建的 Builder 模式。
- SqlSessionFactory(工厂接口):依靠它来生成 SqlSession,使用的是工厂模式。
- SqlSession(会话):一个既可以发送 SQL 执行返回结果,也可以获取 Mapper 的接口。在现有的技术中,一般我们会让其在业务逻辑代码中“消失”,而使用的是 MyBatis 提供的 SQL Mapper 接口编程技术,它能提高代码的可读性和可维护性。
- SQL Mapper(映射器):MyBatis 新设计存在的组件,它由一个 Java 接口和 XML 文件(或注解)构成,需要给出对应的 SQL 和映射规则。它负责发送 SQL 去执行,并返回结果。
SqlSessionFactory
使用 MyBatis 首先是使用配置或者代码去生产 SqlSessionFactory,而 MyBatis 提供了构造器 SqlSessionFactoryBuilder。在 MyBatis 中,既可以通过读取配置的 XML 文件的形式生成 SqlSessionFactory,也可以通过 Java 代码的形式去生成 SqlSessionFactory。SqlSessionFactory 是一个接口,在 MyBatis 中它存在两个实现类:SqlSessionManager 和 DefaultSqlSessionFactory。一般而言,具体是由 DefaultSqlSessionFactory 去实现的,而 SqlSessionManager 使用在多线程的环境中,它的具体实现依靠 DefaultSqlSessionFactory,其关系图为:
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的,而 SqlSessionFactory 唯一的作用就是生产 MyBatis 的核心接口对象 SqlSession,所以它的责任是唯一的。我们往往会采用单例模式处理它,下面讨论使用配置文件和 Java 代码两种形式去生成 SqlSessionFactory 的方法。
创建 SqlSessionFactory两种方式
- 从 XML 中构建 SqlSessionFactory
<?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>
<!--别名-->
<typeAliases>
<typeAliases alias="user" type="com.mybatis.po.User"/>
</typeAliases>
<!-- 数据库环境 -->
<environments default="development">
<environment id="development">
<!-- 使用JDBC事务管理 -->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- MySQL 数据库驱动 -->
<property name="driver" value="${driver}"/>
<!-- 数据库链接url -->
<property name="url" value="${url}"/>
<!-- 账户 -->
<property name="username" value="${username}"/>
<!-- 密码 -->
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!-- 将mapper文件加入到配置文件中 -->
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
- 使用代码创建SqlSessionFactory
// 数据库连接池信息
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword ("1128");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis");
dataSource.setDefeultAutoCommit(false);
// 采用 MyBatis 的 JDBC 事务方式
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment ("development", transactionFactory, dataSource);
// 创建 Configuration 对象
Configuration configuration = new Configuration(environment);
// 注册一个 MyBatis 上下文别名
configuration.getTypeAliasRegistry().registerAlias("role", Role.class);
// 加入一个映射器
configuration.addMapper(RoleMapper.class);
//使用 SqlSessionFactoryBuilder 构建 SqlSessionFactory
SqlSessionFactory SqlSessionFactory =
new SqlSessionFactoryBuilder().build(configuration);
return SqlSessionFactory;
一般情况下我们通过xml方式创建SqlSessionFactory,进行一些数据库连接的配置管理都比较方便,除非有特殊的需要,比如在配置文件中,需要配置加密过的数据库用户名和密码,需要我们在生成 SqlSessionFactory 前解密为明文的时候,才会考虑使用代码创建的方式。
SqlSession
在 MyBatis 中,SqlSession 是其核心接口。在 MyBatis 中有两个实现类,DefaultSqlSession 和 SqlSessionManager。DefaultSqlSession 是单线程使用的,而 SqlSessionManager 在多线程环境下使用。SqlSession 的作用类似于一个 JDBC 中的 Connection 对象,代表着一个连接资源的启用。具体而言,它的作用有 3 个:
- 获取 Mapper 接口。
- 发送 SQL 给数据库。
- 控制数据库事务。
我们通过SqlSessionFactory 获取到SqlSession:
然后通过SqlSession 进行SQL操作,以下就是SqlSession接口提供的方法:
SQL Mapper
MyBatis映射部分是MyBatis最重要的部分,MyBatis如何将我们写的“Sql“变为数据库能执行的sql,又是如何将数据库拿到的数据变为我们所需要的实体,这些都是通过映射来实现的。映射器由一个接口和对应的 XML 文件(或注解)组成。它可以配置以下内容:
- 描述映射规则。
- 提供 SQL 语句,并可以配置 SQL 参数类型、返回类型、缓存刷新等信息。
- 配置缓存。
- 提供动态 SQL。
我们知道映射器是通过一个接口和对应的xml文件就能实现各种sql操作,但是接口是不能直接运行的,MyBatis 运用了动态代理技术使得接口能运行起来,MyBatis 会为这个接口生成一个代理对象,代理对象会去处理相关的逻辑。
-
通过xml方式实现映射器。
先定义一个接口,通过用户ID查询用户
public interface UserMapper { public UserDO getUser(Long id); }
在SqlSession 的配置文件中指定加载的User.xml
<?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="com.mybatis.mapper.UserMapper"> <select id="getUser" parameterType="long" resultType="user"> SELECT id,user_id as userId,name FROM user WHERE id =#{id} </select> </mapper>
其实这里采用的是一种被称为自动映射的功能,MyBatis 在默认情况下提供自动映射,只要 SQL 返回的列名能和 POJO 对应起来即可。
这里 SQL 返回的列名 id 和userId, name 是可以和之前定义的 POJO 的属性对应起来的,而表里的列 user_id 通过 SQL 别名的改写,使其成为 userId,也是和 POJO 对应起来的,所以此时 MyBatis 就可以把 SQL 查询的结果通过自动映射的功能映射成为一个 POJO。
-
通过注解方式实现映射器。
除 XML 方式定义映射器外,还可以采用注解方式定义映射器,它只需要一个接口就可以通过 MyBatis 的注解来注入 SQL,如下所示:
public interface UserMapper2 { @Select("select id,user_id as UserId,name from user_info where id=#{id}") public UserDO getUser(Long id); }
这完全等同于 XML 方式创建映射器。也许你会觉得使用注解的方式比 XML 方式要简单得多。如果它和 XML 方式同时定义时,XML 方式将覆盖掉注解方式,所以 MyBatis 官方推荐使用的是 XML 方式。
MyBatis 重要组件作用域与生命周期
所谓生命周期就是每一个对象应该存活的时间,比如一些对象一次用完后就要关闭,使它们被 Java 虚拟机(JVM)销毁,以避免继续占用资源,所以我们会根据每一个组件的作用去确定其生命周期。
-
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
-
SqlSessionFactory
SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 **SqlSessionFactory 的最佳作用域是应用作用域 **。
-
SqlSession
如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费完,系统就会瘫痪,所以用 try...catch...finally... 语句来保证其正确关闭。所以 SqlSession 的最佳的作用域是请求或方法作用域。
-
Mapper
Mapper 是一个接口,它由 SqlSession 所创建,所以它的最大生命周期至多和 SqlSession 保持一致,尽管它很好用,但是由于 SqlSession 的关闭,它的数据库连接资源也会消失,所以它的生命周期应该小于等于 SqlSession 的生命周期。Mapper 代表的是一个请求中的业务处理,所以它应该在一个请求中,一旦处理完了相关的业务,就应该废弃它。
Mybatis 常用标签
-
crud 标签(select insert update delete)
- <select> 为查询标签,使用最多的标签,其常用属性有 id ,parameterType,resultType,resultMap,flushCache,useCache,timeout,fetchSize,statementType,resultSetType。
- <insert> 标签用于映射插入语句,MyBatis 执行完一条插入语句后将返回一个整数表示其影响的行数。它的属性与 <select> 元素的属性大部分相同,其他属性有keyProperty (用来指定主键,可以指定多个值,用逗号分隔),keyColumn(指定第几列为主键),useGeneratedKeys(由数据库内部主键决定)
- <update> 与<delete> 标签比较简单,它们的属性和<insert> 元素、<select> 元素的属性差不多,执行后也返回一个整数,表示影响了数据库的记录行数。
-
sql 标签
- <sql> 元素的作用在于可以定义 SQL 语句的一部分(代码片段),以方便后面的 SQL 语句引用它,例如反复使用的列名。使用<include> 元素的 refid 属性引用了自定义的代码片段。
-
resultMap 标签
<resultMap> 元素表示结果映射集,是 MyBatis 中最重要也是最强大的元素,主要用来定义映射规则、级联的更新以及定义类型转化器等。
<resultMap id="" type=""> <constructor><!-- 类再实例化时用来注入结果到构造方法 --> <idArg/><!-- ID参数,结果为ID --> <arg/><!-- 注入到构造方法的一个普通结果 --> </constructor> <id/><!-- 用于表示哪个列是主键 --> <result/><!-- 注入到字段或JavaBean属性的普通结果 --> <association property=""/><!-- 用于一对一关联 --> <collection property=""/><!-- 用于一对多、多对多关联 --> <discriminator javaType=""><!-- 使用结果值来决定使用哪个结果映射 --> <case value=""/><!-- 基于某些值的结果映射 --> </discriminator> </resultMap>
-
if 标签
很多时候根据需求手动拼接 SQL 语句,这是一个极其麻烦的工作,而 MyBatis 提供了对 SQL 语句动态组装的功能,动态 SQL 通常要做的事情是有条件地包含 where 子句的一部分,所以在 MyBatis 中<if> 元素是最常用的元素,它类似于 Java 中的 if 语句。
-
choose when otherwise 标签
这是一组标签,相当于java语句中中的 switch case default 语句。
-
trim where 标签
- <trim> 元素的主要功能是可以在自己包含的内容前加上某些前缀,也可以在其后加上某些后缀,与之对应的属性是 prefix 和 suffix。可以把包含内容的首部某些内容覆盖,即忽略,也可以把尾部的某些内容覆盖,对应的属性是 prefixOverrides 和 suffixOverrides。正因为 <trim> 元素有这样的功能,所以也可以非常简单地利用 <trim> 来代替 <where> 元素的功能。
- <where> 元素的作用是会在写入 <where> 元素的地方输出一个 where 语句,另外一个好处是不需要考虑 <where> 元素里面的条件输出是什么样子的,MyBatis 将智能处理。如果所有的条件都不满足,那么 MyBatis 就会查出所有的记录,如果输出后是以 and 开头的,MyBatis 会把第一个 and 忽略。当然如果是以 or 开头的,MyBatis 也会把它忽略;此外,在 <where> 元素中不需要考虑空格的问题,MyBatis 将智能加上。
-
foreach 标签
<foreach> 元素主要用在构建 in 条件中,它可以在 SQL 语句中迭代一个集合。其属性有属性主要有 item、index、collection、open、separator、close。
- item 表示集合中每一个元素进行迭代时的别名。
- index 指定一个名字,用于表示在迭代过程中每次迭代到的位置。
- open 表示该语句以什么开始。
- separator 表示在每次进行迭代之间以什么符号作为分隔符。
- close 表示以什么结束。