MyBatis进阶

前言

接着之前介绍Mybatis入门的内容,本篇博客将进一步学习mybatis框架的使用。

MyBatis配置

在之前的入门篇中介绍的spring-mybatis的例子里,在mybatis配置文件中我们只使用到了mybatis的映射器配置与日志配置,没有用到mybatis的其他配置项,接下来来看看mybatis的配置文件提供了哪些可配置的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<properties/><!--属性-->
<settings/><!--设置-->
<typeAliases/><!--别名-->
<typeHandlers/><!--类型处理器-->
<objectFactory/><!--对象工厂-->
<plugins><!--插件-->
<environments><!--环境配置-->
<environment><!--环境变量-->
<transactionManager/><!--事务管理-->
<dataSource/><!--数据源-->
<environment/>
<environments>
<databaseIdProvider/><!--数据库厂商标识-->
<mappers/><!--映射器-->
</configuration>

上述mybatis的配置文件中,列举了所有可配置的选项,并且这些配置项不能颠倒顺序,否则mybatis启动会报错。

接下来简答介绍一些常用的配置项:

  • properties属性:通过properties可以配置一些参数,允许将参数放在xml文件或者properties文件中,方便统一管理和修改系统参数。
  • settings设置项:settings设置里面有很多可配置的地方,这些配置项直接影响mybatis的运行,但是大部分时间使用默认值即可,如果需要修改settings内配置项,可以查看mybatis官网相关的说明,官网的解释很详细且权威,传送门在这:点击
  • typeHandler类型转换器:在typeHandler中,分为jdbcType和javaType,其中jdbcType用于描述数据库的数据类型,而javaType用于描述java的数据类型,那么typeHandler的作用就是完成jdbcType与javaType之间的互相转换的。大多数情况下,mybatis会自动探测并使用系统自定义的typeHandler进行处理,但是mybatis也允许用户自定义typeHandler,来完成用户特定的转换需求,例如枚举类型的转换。如何自定义typeHandler并使用自定义的typeHandler在官网上也有举例说明,这里就不赘述。
  • environments环境配置:环境配置主要是配置数据库信息的,可以配置事务管理器与数据源信息,但是在实际中一般会将mybatis置于spring容器内使用,所以一般采用spring的数据源配置与数据库事务管理功能。
  • mappers映射器:映射器是mybatis最核心的组件,mappers这个配置项主要用于引入映射器,一般通过映射器的映射xml文件来引入或者通过映射器的接口的全限定包名来引入。

关于mybatis的配置,大多数的情况下使用默认的配置即可,如果需要自己定义一些配置,建议去官网上查看配置的详细情况,这里是大致列举了一些常用配置的作用而已。今天还惊喜的发现mybatis的参考文档竟然有中文版,当然如果觉得中文翻译的意思不准确可以直接看英文原版。

映射器

映射器是mybatis最核心的组件,它由一个接口加上xml文件组成。在映射器中可以配置参数,各类的SQL语句,缓存,级联等内容,并且可以通过映射规则映射到指定的POJO上。映射器的接口与映射xml文件配置使用,可以有效的消除jdbc底层代码。

映射器的映射xml文件有以下几个元素:

  • cache – 给定命名空间的缓存配置
  • cache-ref – 其他命名空间缓存配置的引用
  • resultMap – 描述如何从数据库结果集中来加载对象,它将提供映射规则
  • sql – 可被其他语句引用的可重用语句块,可以定义一部分SQL,然后在其他地方引用
  • insert – 映射插入语句,执行后返回一个整数,代表插入的条数
  • update – 映射更新语句,执行后返回一个整数,代表更新的条数
  • delete – 映射删除语句,执行后返回一个整数,代表删除的条数
  • select – 映射查询语句,返回查询结果

select元素

映射器中的select元素代表SQL的select语句,用于查询。select元素中有以下常用的属性:

  • id:在Mapper的命名空间中唯一的标识符。Mapper的命名空间与select元素的id将唯一定位对应的映射器接口的某个方法。
  • parameterType:将会传入这条语句的参数类的完全限定名或别名,可以选择Java Bean,Map等参数类型传递给SQL。这个属性是可选的,mybatis可以通过TypeHandler推断出具体传入语句的参数。
  • resultType:从这条语句中返回的期望类型的类的完全限定名或别名。注意如果是集合情形,那应该是集合可以包含的类型,而不能是集合本身。
  • resultMap:映射集的引用,用于结果集的映射,完成结果的映射功能。注意resultType和resultMap不能同时使用。
  • flushCache:将其设置为true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false。
  • useCache:将其设置为true,将会导致本条语句的结果被二级缓存,默认值:对select元素为true。

来看看在之前的入门篇的例子中的select元素:

1
2
3
<select id="getPerson" parameterType="int" resultType="mybatis.spring.test.pojo.Person">
SELECT name,age,tel,id from t_person WHERE id = #{id}
</select>

当然还要配合映射器接口才能完成映射:

1
public Person getPerson(@Param("id") int id);

这里用到mybatis提供的自动映射功能,只要SQL的列名与POJO的属性名保持一致,同时在mybatis的配置文件中settings元素的autoMappingBehavior是开启的(默认是开启)即可完成自动映射。如果列名与属性名不一致的话,还可以通过SQL的别名机制来处理。比如说,假如原来列名为person_name,属性名为personName,那么在SQL中就可以这么写:select person_name as personName … 这样同样可以完成自动映射。

在上述接口方法中,用到了@Param注解用于传递参数,这是mybatis为开发者提供的注解,可以通过它去定义映射器的参数名称,同时利用注解不仅仅可以传递一个参数也可以传递多个参数。除了使用注解,我们还可以用Java Bean来传递参数,将参数作为属性放入Bean中,传递参数给SQL的时候就只用传递一个Bean即可,SQL中可以直接引用Bean中的属性值。还是接着之前的例子来说明:

先定义一个传递参数的Bean–PersonParams:

1
2
3
4
5
6
public class PersonParams{
private String name;
private String age;
/** setter and getter **/
}

映射器接口定义:

1
public List<Person> findPersonByBean(PersonParams personParam);

select元素:

1
2
3
<select id="findPersonByBean" parameterType="PersonParams类的全限定名" resultType="mybatis.spring.test.pojo.Person">
SELECT name,age,tel,id from t_person WHERE name = #{name} and age = #{age}
</select>

这里特别说明关于select元素返回一个对象或者对象集合的问题:

  1. 返回数据类型由DAO中的接口和映射xml文件共同决定。另外,不论是返回单一对象还是对象列表,映射xml中的配置都是一样的,都是resultType=“ . .”类型或resultMap=”resultMap-ref”*。
  2. 每一次mybatis从数据库中select数据之后,都会检查数据条数和DAO中定义的返回值是否匹配。
  3. 若返回一条数据,DAO中定义的返回值是一个对象或对象的List列表,则可以正常匹配,将查询的数据按照DAO中定义的返回值存放。
  4. 若返回多条数据,DAO中定义的返回值是一个对象,则无法将多条数据映射为一个对象,此时mybatis报错。

resultMap元素

通过resultType加上mybatis的自动映射可以应对大多数的结果集的映射需求,但是这种方法无法定义更多的属性,比如typeHandler,级联等。为了支持更加复杂的映射,mybatis提供了resultMap属性来完成SQL到Java Bean的映射关系定义。接下来,用resulyMap改写之前使用自动映射的select元素。

1
2
3
4
5
6
7
8
9
<resultMap id = "Map" type = "mybatis.spring.test.pojo.Person">
<id property="id" column="id">
<result property="name" column="name">
<result property="age" column="age">
<result property="tel" column="tel">
</resultMap>
<select id="getPerson" parameterType="int" resultMap="Map">
SELECT name,age,tel,id from t_person WHERE id = #{id}
</select>

子元素id代表主键,result代表属性,id和result元素的property属性代表POJO的属性名称,column代表SQL的列名,这样就将POJO的属性和SQL的列名进行一一对应了。

上述例子只是一个简单的例子,那么一个完整的resultMap元素的构成有哪些?

  • constructor:类在实例化时,用来注入结果到构造方法中
  • id:表示哪个列是主键
  • result:注入到JavaBean属性的普通结果,配置POJO到SQL列名的映射关系
  • association:一个复杂的类型关联;许多结果将包成这种类型
  • collection:复杂类型的集
  • discriminator:使用结果值来决定使用哪个结果映射

constructor元素用于配置构造方法,一个POJO可能不存在没有参数的构造方法,就要使用construtor元素来配置了。

id元素,result元素主要来定义POJO与SQL列名的映射规则,它们有如下属性:

  • property:映射规则中的POJO的属性名,可以用导航式字段,例如需要访问Person类的Address属性的id,就可以写成Address.id
  • column:对应的SQL的列名
  • javaType:Java类型
  • jdbcType:数据库类型
  • typeHandler:类型处理器,允许使用自定义的类型处理器

其中association,collection和discriminator这些元素是与结果映射的级联相关的。Mybatis支持级联映射,简单来说,假如我们select一个person类,person类有一个属性为address,address也是一个POJO,也就是说需要再进行一次select才能查询到address类的信息。这里需要说明的是,级联不是必须的,级联可以便捷的获取关联数据,但是如果级联太多的话会影响执行效率,也就是著名的N+1问题(如果有N个关联关系完成了级联,那么只要再加入一个关联关系,就变成了N+1个,所有的级联SQL都会被执行,显然不是所有数据是我们感兴趣的,造成了资源的浪费与性能的浪费)。为了应对级联中的N+1问题,mybatis支持延迟加载,在select的时候并不是一次性取出所有的SQL结果来映射,对于那些不常用的级联数据等到需要的时候再取出。在mybatis的全局配置文件中可以设定延迟加载属性,同时在级联元素association和collection中的fetchType属性也可以定义延迟加载。

insert,update,delete

相比于select来说,insert,update,delete就很简单了,它们执行的结果返回的是整数,用以标识该SQL语句影响了数据库的记录行数,不需要进行结果集的映射。除此之外,insert元素是支持主键回填的,在inser语句中有一个属性useGeneratedKeys,用来控制是否使用数据库生成的主键来回填到POJO,默认值为false。当打开了主键回填之后,还要配置keyProperty或者keyColumn,告诉mybatis把生成的主键放入哪个属性中。至于在实际中用不用得到数据库生成的主键,我也不能完全肯定,这里只要知道mybatis是支持主键回填的即可,使用起来也很容易。

sql元素

这个元素可以被用来定义可重用的SQL代码段,可以包含在其他语句中。 比如:

1
2
3
4
5
6
7
8
9
10
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>

动态SQL

mybatis提供了对SQL语句动态的组装能力,大量的判断都可以在mybatis的映射xml里面配置,大大减少了代码量,提供了灵活性。

mybatis的动态SQL包括以下几种元素,如图所示:

if

if元素使用的场景适用于在SQL语句的where子句中动态地选择某几个判断条件作为SQL语句的一部分进行拼接。这里偷个懒,直接引用官方参考文档里的例子:

1
2
3
4
5
6
7
8
9
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>

如果传入的参数title不是null,那么就将 AND title like #{title} 这子句拼接在where之后,这样就可以有条件地包含where子句的一部分。

choose(when,otherwise)

if元素是单条件的,choose元素就是多条件的选择类似java里的switch语句,原理一样,还是接着上面的例子来举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>

上述参考文档中的例子otherwise元素写的是 AND featured = 1 是为了解决前面的所有case都不满足的情况,我建议写成 AND 1 = 1 更好理解。
该动态SQL语句的功能:如果参数提供了“title”就按“title”查找,提供了“author”就按“author”查找,若两者都没有提供,就返回所有符合默认条件的查询结果集。

trim(where,set)

前面的例子中的where子句总会有一个默认的查询条件也就是例子中的 state = ‘ACTIVE’ ,如果state也设置成动态的呢?可以通过where,set元素来达到这个需求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>

同样如果是update子句,那么也就相应的set元素,如下:

1
2
3
4
5
6
7
8
9
10
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>

如果where,set都无法满足动态拼接SQL的需求,我们可以使用trim来自定义如何拼接SQL子句,where和set只是mybatis定义好的拼接SQL的方法而已,但这也已经可以满足大多数需求了。

foreach

动态 SQL 的另外一个常用的必要操作是需要对一个集合进行遍历,通常是在构建 IN 条件语句的时候。还是参考文档里买的例子:

1
2
3
4
5
6
7
8
9
10
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>

foreach元素允许指定一个集合,声明可以用在元素体内的集合项和索引变量,也允许你指定开闭匹配的字符串以及在迭代中间放置分隔符。我们可以将任何可迭代对象(如列表、集合等)和任何的字典或者数组对象传递给foreach作为集合参数。当使用可迭代对象或者数组时,index是当前迭代的次数,item的值是本次迭代获取的元素。当使用字典(或者Map.Entry对象的集合)时,index是键,item是值。

总结

提供一个最好的学习mybatis的教材:官方参考文档。mybatis的使用很简单,很容易上手,按照参考文档多用就会很熟练了。至于mybatis的原理,看了mybatis的使用方法之后都能猜出来,肯定是动态代理了,映射器只提供了接口,mybatis内部将对接口进行动态代理,完成既定的逻辑。

0%