上一章节说道,解析mybatis的配置文件的最后一步是解析mapper元素。每个mapper元素对应一个mapper配置文件,这一章节就来讲讲这个mapper配置文件是如何解析的。mapper配置文件是配置sql映射的地方,它看起来就像下面这个样子:
<?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.iMybatis.abc.dao.UserDao">
<cache type="PERPETUAL" eviction="LRU" flushInterval="60000"
size="512" readOnly="true" />
<resultMap id="userResultMap" type="UserDto">
<id property="userid" column="userid" />
<result property="username" column="username" />
<result property="password" column="password" />
</resultMap>
<sql id="userColumns"> userid,username,password </sql>
<select id="queryUsers" parameterType="UserDto" resultType="UserDto"
useCache="false">
select <include refid="userColumns" />
from t_user t where t.username = #{username}
</select>
<insert id="insertUser" parameterType="UserDto"
useGeneratedKeys="true" keyProperty="userid">
insert into t_user (userid,username,password)
values (#{userid},#{username},#{password})
</insert>
<update id="updateUser" parameterType="UserDto">
update t_user set
username= #{username},
password = #{password},
where userid = #{userid}
</update>
<delete id="deleteUser" parameterType="UserDto">
delete from t_user where userid = #{userid}
</delete>
</mapper>
Mapper元素只有一个属性namespace,它有两个作用:一是用于区分不同的mapper(在不同的mapper文件里,子元素的id可以相同,mybatis通过namespace和子元素的id联合区分),二是与接口关联(应用程序通过接口访问mybatis时,mybatis通过接口的完整名称查找对应的mapper配置,因此namespace的命名务必小心一定要某接口同名)。此外,mapper配置文件还有几个顶级子元素(它们须按照顺序定义):
l cache:配置本地命名空间的缓存。
l cache-ref:从其他命名空间引用缓存配置。
l resultMap:结果映射,用来描述如何从数据库结果集映射到你想要的对象。
l parameterMap:已经被废弃了!建议不要使用,本章也不会对它进行讲解。
l sql:可以重用的SQL块,可以被其他数据库操作语句引用。
l insert | update | delete | select:数据库操作语句。
Mapper的解析在XMLMapperBuilder里完成,主要通过configurationElement方法完成解析,在configurationElement内部调用各个元素的子解析方法完成解析。
下面分别介绍这些子元素。
一、cache
1、配置示例
<cache type="PERPETUAL" eviction="LRU"flushInterval="60000" size="512"readOnly="true" />
type-cache:实现类,默认为PERPETUAL,可以使用自定义的cache实现类(别名或完整类名皆可)
eviction:回收算法,默认为LRU,可选的算法有:
LRU:最近最少使用的:移除最长时间不被使用的对象。
FIFO:先进先出:按对象进入缓存的顺序来移除它们。
SOFT:软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK:弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval:刷新间隔,默认为1个小时,单位毫秒
size:缓存大小,默认大小1024,单位为引用数
readOnly:只读
2、解析
cacheElement方法负责解析cache元素,它通过调用CacheBuilder的相应方法完成cache的创建。每个cache内部都有一个唯一的ID,这个id的值就是namespace。创建好的cache对象存入configuration的cache缓存中(该缓存以cache的ID属性即namespace为key,这里再次体现了mybatis的namespace的强大用处)。
二、cache-ref
1、配置示例
<cache-ref namespace="com.someone.application.data.SomeMapper"/>
如果你不想定义自己的cache,可以使用cache-ref引用别的cache。因为每个cache都以namespace为id,所以cache-ref只需要配置一个namespace属性就可以了。需要注意的是,如果cache-ref和cache都配置了,以cache为准。
2、解析
cacheRefElement方法负责解析cache-ref元素,它通过调用CacheRefResolver的相应方法完成cache的引用。创建好的cache-ref引用关系存入configuration的cacheRefMap缓存中。
三、resultMap
1、配置示例
<resultMap id="userResultMap" type="User">
<id property=" userid " column="userid" />
<result property="username" column="username" />
<result property="password" column="password" />
</resultMap>
resultMap提供了从数据库表列名到java对象属性的映射管理,示例只是提供了最简单的情况,resultMap能做的不仅仅是示例中这么点(作者不喜欢使用resultMap完成结果的复杂映射,相反更喜欢结果和对象的直接映射)。在mapper配置文件中可以配置多个resultMap,不同的resultMap用id加以区分。type属性标记java类型(别名)。子元素中的property指带java中的属性,column指带数据库表的列名。
其实 id 跟 result 标签可以通用,只是 id 有特殊标识可以在影射的时候更快的匹配到,如果我没猜错的话这个 id 标识其实不是主键也能用。
2、解析
resultMapElement方法负责解析resultMap元素,它通过调用ResultMapResolver的相应方法完成resultMap的解析。创建好的resultMap存入configuration的resultMaps缓存中(该缓存以namespace+resultMap的id为key,这里再次体现了mybatis的namespace的强大用处)。
四、sql
1、配置示例
<sql id="userColumns"> userid,username,password</sql>
这个元素可以被用来定义可重用的SQL代码段,可以包含在其他语句中。例如:
<select id="queryUsers" parameterType="UserDto" resultType="UserDto" useCache="false">
select <include refid="userColumns"/> from t_user t where t.username = #{username}
</select>
2、解析
sqlElement方法负责解析sql元素。id属性用于区分不同的sql元素,在同一个mapper配置文件中可以配置多个sql元素。
五、statement
1、配置示例
作为数据库访问的statement元素包含4种类型:insert、update、delete、query。其中前3者可以归为一类,query单独为一类,下面分开讲解。
(1)select
<select id="queryUsers" parameterType="UserDto" resultType="UserDto" useCache="false">
select <include refid="userColumns"/> from t_user t where t.username = #{username}
</select>
select元素有很多属性允许配置,来决定每条语句的作用细节,如下:
(2)insert、udpate、delete
<insert id="insertUser" parameterType="UserDto" useGeneratedKeys="true" keyProperty="userid">
insert into t_user (userid,username,password)
values (#{userid},#{username},#{password})
</insert>
<update id="updateUser" parameterType="UserDto">
update t_user set
username = #{username},
password = #{password},
where userid = #{userid}
</update>
<delete id="deleteUser" parameterType="UserDto">
delete from t_user where userid = #{userid}
</delete>
属性说明如下:
(3)selectKey
<selectKey keyProperty="userid" resultType="int" order="BEFORE">
select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1
</selectKey>
selectKey给了你一个简单的行为在你的数据库中来处理自动生成的主键,而不需要使你的Java代码变得复杂。在上面的示例中,selectKey元素将会首先运行,userid会被设置,然后插入语句会被调用。另外,selectKey节点生成的KeyGenerator优先级高于statement节点的useGeneratedKeys属性生成的KeyGenerator对象,也就是说配置了SelectKey子节点就不需要再配置useGeneratedKeys属性了。属性说明如下:
2、解析
buildStatementFromContext方法负责解析statement元素。id属性用于区分不同的statement元素,在同一个配置文件中可以配置多个statement元素。通过调用XMLStatementBuilder的parseStatementNode方法完成解析。在这个方法内有几个重要的步骤,理解他们对正确的配置statement元素很有帮助。
(1)动态解析子元素
statement节点可以配置各种子元素,比如前面提到的include子元素和selectKey子元素等(在动态sql里还有更多的子元素,具体参考mybatis的官方文档)。动态解析子元素通过parseDynamicTags方法完成。该方法根据子元素的类型递归的解析成一个个的SqlNode,这些SqlNode对象提供了apply方法,供后续调用时生成sql语句所需。需要注意的是SelectKey没有对应的SqlNode对象,因为它的功能是用来生成KeyGenerator对象的(具体来说是SelectKeyGenerator对象)。另外,SelectKey节点生成的KeyGenerator优先级高于statement节点的useGeneratedKeys属性生成的KeyGenerator对象,也就是说配置了SelectKey子节点就不需要再配置useGeneratedKeys属性了。
(2)生成SqlSource
SqlSource用于后续调用时根据SqlNode和参数对象生成sql语句。它接收一个叫做rootsqlNode的对象作为构造参数。
(3)生成KeyGenerator
如果配置了selectKey子元素,KeyGenerator直接使用selectKey子元素里生成的KeyGenerator对象(具体来说是SelectKeyGenerator对象)。若没配置,则如果useGeneratedKeys属性的值为"true"且配置了 keyProperty属性,则生成默认的Jdbc3KeyGenerator对象,该对象调用JDBC驱动的getGeneratedKeys方法返回insert语句执行后生成的自增长主键。
(4)创建MappedStatement
MappedStatement对象封装了statement元素的所有属性以及子节点值,MappedStatement对象有一个id属性用于唯一标记它,这个id由namespace加statement元素的id属性值构成。创建好的MappedStatement对象存入Configuration对象的mappedStatements缓存中,key为MappedStatement对象的id值。
附:注册mapper类型
我们知道每个mapper配置文件的namespace属性对应于某个接口,应用程序通过接口访问mybatis时,mybatis会为这个接口生成一个代理对象,这个对象就叫mapper对象,在生成代理对象前mybatis会校验接口是否已注册,未注册的接口会产生一个异常。为了避免这种异常,就需要注册mapper类型。这个步骤是在XMLMapperBuilder的bindMapperForNamespace方法中完成的。它通过调用Configuration对象的addMapper方法完成,而Configuration对象的addMapper方法是通过MapperRegistry的addMapper方法完成的,它只是简单的将namespace属性对应的接口类型存入本地缓存中。
Configuration对象提供了一个重载的addMappers(StringpackageName)方法,该方法以包路径名为参数,它的功能是自动扫描包路径下的接口并注册到MapperRegistry的缓存中,同时扫描包路径下的mapper配置文件并解析之。解析配置文件是在MapperAnnotationBuilder类的parse方法里完成的,该方法先解析配置文件,然后再解析接口里的注解配置,且注解里的配置会覆盖配置文件里的配置,也就是说注解的优先级高于配置文件,这点需要注意。采用自动扫描会大大简化配置,只不过需要应用程序自己调用,mybatis默认是不会调用这个方法的(后续将会讲解的spring集成mybatis就用到了自动扫描,敬请期待)。