深入浅出MyBatis-MapperBuilder
上一章节说道,解析mybatis的配置文件的最后一步是解析mapper元素。每个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" />
l type-cache实现类,默认为PERPETUAL,可以使用自定义的cache实现类(别名或完整类名皆可)
l eviction-回收算法,默认为LRU,可选的算法有:
LRU– 最近最少使用的:移除最长时间不被使用的对象。
FIFO– 先进先出:按对象进入缓存的顺序来移除它们。
SOFT– 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK– 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
l flushInterval-刷新间隔,默认为1个小时,单位毫秒
l size-缓存大小,默认大小1024,单位为引用数
l 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指带数据库表的列名。
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元素有很多属性允许配置,来决定每条语句的作用细节,如下:
属性 |
描述 |
id |
在命名空间中唯一的标识符,可以被用来引用这条语句。同样,id+namespace既作为configuration中缓存的key又与namespace对应的接口中的方法关联。 |
parameterType |
将会传入这条语句的参数类的完全限定名或别名。 |
|
已废弃。 |
resultType |
从这条语句中返回的期望类型的类的完全限定名或别名。注意集合情形,那应该是集合可以包含的类型,而不能是集合本身(是否使用集合由id对应的方法的返回类型决定)。使用resultType或resultMap,但不能同时使用。 |
resultMap |
引用外部的resultMap。使用resultMap或resultType,但不能同时使用。 |
flushCache |
将其设置为true,不论语句什么时候被调用,都会导致缓存被清空。默认值:false。 |
useCache |
将其设置为true,将会导致本条语句的结果被缓存。默认值:true。 |
timeout |
这个设置驱动程序等待数据库返回请求结果,并抛出异常时间的最大等待值。默认不设置(驱动自行处理)。 |
fetchSize |
这是暗示驱动程序每次批量返回的结果行数。默认不设置(驱动自行处理)。 |
statementType |
STATEMENT,PREPARED或CALLABLE的一种。这会让MyBatis使用选择使用Statement,PreparedStatement或CallableStatement。默认值:PREPARED。 |
resultSetType |
FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE中的一种。默认是不设置(驱动自行处理)。 |
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>
属性说明如下:
属性 |
描述 |
id |
在命名空间中唯一的标识符,可以被用来引用这条语句。同样,id+namespace既作为configuration中缓存的key又与namespace对应的接口中的方法关联。 |
parameterType |
将会传入这条语句的参数类的完全限定名或别名。 |
|
已废弃。 |
flushCache |
将其设置为true,不论语句什么时候被调用,都会导致缓存被清空。默认值:false。 |
timeout |
这个设置驱动程序等待数据库返回请求结果,并抛出异常时间的最大等待值。默认不设置(驱动自行处理)。 |
statementType |
STATEMENT,PREPARED或CALLABLE的一种。这会让MyBatis使用选择使用Statement,PreparedStatement或CallableStatement。默认值:PREPARED。 |
useGeneratedKeys |
(仅对insert有用)这会告诉MyBatis使用JDBC的getGeneratedKeys方法来取出由数据(比如:像MySQL和SQL Server这样的数据库管理系统的自动递增字段)内部生成的主键。默认值:false。 |
keyProperty |
(仅对insert有用)标记一个属性,MyBatis会通过getGeneratedKeys或者通过insert语句的selectKey子元素设置它的值。默认:不设置。 |
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属性了。属性说明如下:
属性 |
描述 |
keyProperty |
selectKey语句结果设置的目标属性。 |
resultType |
结果的类型。MyBatis允许任何简单类型用作主键的类型,包括字符串。 |
order |
这可以被设置为BEFORE或AFTER。如果设置为BEFORE,那么它会首先选择主键,设置keyProperty然后执行插入语句。如果设置为AFTER,那么先执行插入语句,然后是selectKey元素-这和如Oracle数据库相似,可以在插入语句中嵌入序列调用。 |
statementType |
和前面的相同,MyBatis支持STATEMENT,PREPARED和CALLABLE语句的映射类型,分别代表PreparedStatement和CallableStatement类型。 |
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就用到了自动扫描,敬请期待)。