#0 系列目录#java
在mapper文件中,以mapper做为根节点,其下面能够配置的元素节点有: select, insert, update, delete, cache, cache-ref, resultMap, sql 。node
#1 insert, update, delete 的配置及使用# 相信,看到insert, update, delete, 咱们就知道其做用了,顾名思义嘛,myabtis 做为持久层框架,必需要对CRUD啊。好啦,我们就先来看看 insert, update, delete 怎么配置, 能配置哪些元素吧:mysql
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"> <!-- mapper 为根元素节点, 一个namespace对应一个dao --> <!-- Mapper元素只有一个属性namespace,它有两个做用:`一是用于区分不一样的mapper`(在不一样的mapper文件里,子元素的id能够相同,mybatis经过namespace和子元素的id联合区分),`二是与接口关联`(应用程序经过接口访问mybatis时,mybatis经过接口的完整名称查找对应的mapper配置,所以namespace的命名务必当心必定要某接口同名)。 --> <mapper namespace="com.dy.dao.UserDao"> <!-- cache- 配置本定命名空间的缓存。 type- cache实现类,默认为PERPETUAL,可使用自定义的cache实现类(别名或完整类名皆可) eviction- 回收算法,默认为LRU,可选的算法有: LRU– 最近最少使用的:移除最长时间不被使用的对象。 FIFO– 先进先出:按对象进入缓存的顺序来移除它们。 SOFT– 软引用:移除基于垃圾回收器状态和软引用规则的对象。 WEAK– 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。 flushInterval- 刷新间隔,默认为1个小时,单位毫秒 size- 缓存大小,默认大小1024,单位为引用数 readOnly- 只读 --> <cache type="PERPETUAL" eviction="LRU" flushInterval="60000" size="512" readOnly="true" /> <!-- cache-ref–从其余命名空间引用缓存配置。 若是你不想定义本身的cache,可使用cache-ref引用别的cache。由于每一个cache都以namespace为id,因此cache-ref只须要配置一个namespace属性就能够了。须要注意的是,若是cache-ref和cache都配置了,以cache为准。 --> <cache-ref namespace="com.someone.application.data.SomeMapper"/> <insert <!-- 1. id (必须配置) id是命名空间中的惟一标识符,可被用来表明这条语句。 一个命名空间(namespace) 对应一个dao接口, 这个id也应该对应dao里面的某个方法(至关于方法的实现),所以id 应该与方法名一致 --> id="insertUser" <!-- 2. parameterType (可选配置, 默认为mybatis自动选择处理) 将要传入语句的参数的彻底限定类名或别名, 若是不配置,mybatis会经过ParameterHandler 根据参数类型默认选择合适的typeHandler进行处理 parameterType 主要指定参数类型,能够是int, short, long, string等类型,也能够是复杂类型(如对象) --> parameterType="com.demo.User" <!-- 3. flushCache (可选配置,默认配置为true) 将其设置为 true,任什么时候候只要语句被调用,都会致使本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句) --> flushCache="true" <!-- 4. statementType (可选配置,默认配置为PREPARED) STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 --> statementType="PREPARED" <!-- 5. keyProperty (可选配置, 默认为unset) (仅对 insert 和 update 有用)惟一标记一个属性,MyBatis 会经过 getGeneratedKeys 的返回值或者经过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。若是但愿获得多个生成的列,也能够是逗号分隔的属性名称列表。 --> keyProperty="" <!-- 6. keyColumn (可选配置) (仅对 insert 和 update 有用)经过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候须要设置。若是但愿获得多个生成的列,也能够是逗号分隔的属性名称列表。 --> keyColumn="" <!-- 7. useGeneratedKeys (可选配置, 默认为false) (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(好比:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。 --> useGeneratedKeys="false" <!-- 8. timeout (可选配置, 默认为unset, 依赖驱动) 这个设置是在抛出异常以前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。 --> timeout="20"> <update id="updateUser" parameterType="com.demo.User" flushCache="true" statementType="PREPARED" timeout="20"> <delete id="deleteUser" parameterType="com.demo.User" flushCache="true" statementType="PREPARED" timeout="20"> </mapper>
以上就是一个模板配置, 哪些是必要配置,哪些是根据本身实际需求,看一眼就知道了。看一个真实的UserDao-Mapper.xml配置:算法
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN" "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"> <mapper namespace="com.dy.dao.UserDao"> <!-- 对应userDao中的insertUser方法, --> <insert id="insertUser" parameterType="com.dy.entity.User"> insert into user(id, name, password, age, deleteFlag) values(#{id}, #{name}, #{password}, #{age}, #{deleteFlag}) </insert> <!-- 对应userDao中的updateUser方法 --> <update id="updateUser" parameterType="com.dy.entity.User"> update user set name = #{name}, password = #{password}, age = #{age}, deleteFlag = #{deleteFlag} where id = #{id}; </update> <!-- 对应userDao中的deleteUser 方法 --> <delete id="deleteUser" parameterType="com.dy.entity.User"> delete from user where id = #{id}; </delete> </mapper>
这样,一个简单的映射关系就创建了。仔细观察上面parameterType, "com.dy.entity.User",包名要是再长点呢,每次都这样写,写得蛋疼了。别忘了以前讲的 typeAliases(别名), 那么这个地方,用上别名,岂不是技能跟蛋疼的长长的包名说拜拜了
。好啦,我们配上别名,在哪儿配? 固然是在mybatis 的全局配置文件(我这儿名字是mybatis-conf.xml), 不要认为是在mapper的配置文件里面配置哈。spring
<typeAliases> <!-- 经过package, 能够直接指定package的名字, mybatis会自动扫描你指定包下面的javabean, 而且默认设置一个别名,默认的名字为: javabean 的首字母小写的非限定类名来做为它的别名。 也可在javabean 加上注解@Alias 来自定义别名, 例如: @Alias(user) <package name="com.dy.entity"/> --> <typeAlias alias="user" type="com.dy.entity.User"/> </typeAliases>
这样,一个别名就取好了,我们能够把上面的 com.dy.entity.User 都直接改成user 了。 这多方便呀!sql
我这儿数据库用的是mysql, 我把user表的主键id 设置了自动增加, 以上代码运行正常, 那么问题来了(固然,我不是要问学挖掘机哪家强),我要是换成oracle数据库怎么办? oracle 但是不支持id自增加啊? 怎么办?
请看下面:数据库
<!-- 对应userDao中的insertUser方法, --> <insert id="insertUser" parameterType="com.dy.entity.User"> <!-- oracle等不支持id自增加的,可根据其id生成策略,先获取id --> <selectKey resultType="int" order="BEFORE" keyProperty="id"> select seq_user_id.nextval as id from dual </selectKey> insert into user(id, name, password, age, deleteFlag) values(#{id}, #{name}, #{password}, #{age}, #{deleteFlag}) </insert>
同理,若是咱们在使用mysql的时候,想在数据插入后返回插入的id, 咱们也可使用 selectKey 这个元素
:apache
<!-- 对应userDao中的insertUser方法, --> <insert id="insertUser" parameterType="com.dy.entity.User"> <!-- oracle等不支持id自增加的,可根据其id生成策略,先获取id <selectKey resultType="int" order="BEFORE" keyProperty="id"> select seq_user_id.nextval as id from dual </selectKey> --> <!-- mysql插入数据后,获取id,该方法LAST_INSERT_ID()与数据库链接绑定,同属统一会话级别。--> <selectKey keyProperty="id" resultType="int" order="AFTER" > SELECT LAST_INSERT_ID() as id </selectKey> insert into user(id, name, password, age, deleteFlag) values(#{id}, #{name}, #{password}, #{age}, #{deleteFlag}) </insert>
**这儿,咱们就简单提一下 <selectKey> 这个元素节点吧:**selectKey给了你一个简单的行为在你的数据库中来处理自动生成的主键,而不须要使你的Java代码变得复杂。在上面的示例中,selectKey元素将会首先运行,userid会被设置,而后插入语句会被调用。另外,selectKey节点生成的KeyGenerator优先级高于statement节点的useGeneratedKeys属性生成的KeyGenerator对象,也就是说配置了SelectKey子节点就不须要再配置useGeneratedKeys属性了
。缓存
<selectKey <!-- selectKey 语句结果应该被设置的目标属性。若是但愿获得多个生成的列,也能够是逗号分隔的属性名称列表。 --> keyProperty="id" <!-- 结果的类型。MyBatis 一般能够推算出来,可是为了更加肯定写上也不会有什么问题。MyBatis 容许任何简单类型用做主键的类型,包括字符串。若是但愿做用于多个生成的列,则可使用一个包含指望属性的 Object 或一个 Map。 --> resultType="int" <!-- 这能够被设置为 BEFORE 或 AFTER。若是设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 而后执行插入语句。若是设置为 AFTER,那么先执行插入语句,而后是 selectKey 元素 - 这和像 Oracle 的数据库类似,在插入语句内部可能有嵌入索引调用。 --> order="BEFORE" <!-- 与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别表明 PreparedStatement 和 CallableStatement 类型。 --> statementType="PREPARED">
#2 select、resultMap的配置及使用## select无疑是咱们最经常使用,也是最复杂的,mybatis经过resultMap能帮助咱们很好地进行高级映射。下面就开始看看select 以及 resultMap的用法:安全
先看select的配置吧:
<select <!-- 1. id (必须配置) id是命名空间中的惟一标识符,可被用来表明这条语句。 一个命名空间(namespace) 对应一个dao接口, 这个id也应该对应dao里面的某个方法(至关于方法的实现),所以id 应该与方法名一致 --> id="selectPerson" <!-- 2. parameterType (可选配置, 默认为mybatis自动选择处理) 将要传入语句的参数的彻底限定类名或别名, 若是不配置,mybatis会经过ParameterHandler 根据参数类型默认选择合适的typeHandler进行处理 parameterType 主要指定参数类型,能够是int, short, long, string等类型,也能够是复杂类型(如对象) --> parameterType="int" <!-- 3. resultType (resultType 与 resultMap 二选一配置) resultType用以指定返回类型,指定的类型能够是基本类型,能够是java容器,也能够是javabean --> resultType="hashmap" <!-- 4. resultMap (resultType 与 resultMap 二选一配置) resultMap用于引用咱们经过 resultMap标签订义的映射类型,这也是mybatis组件高级复杂映射的关键 --> resultMap="personResultMap" <!-- 5. flushCache (可选配置) 将其设置为 true,任什么时候候只要语句被调用,都会致使本地缓存和二级缓存都会被清空,默认值:false --> flushCache="false" <!-- 6. useCache (可选配置) 将其设置为 true,将会致使本条语句的结果被二级缓存,默认值:对 select 元素为 true --> useCache="true" <!-- 7. timeout (可选配置) 这个设置是在抛出异常以前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)--> timeout="10000" <!-- 8. fetchSize (可选配置) 这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)--> fetchSize="256" <!-- 9. statementType (可选配置) STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED--> statementType="PREPARED" <!-- 10. resultSetType (可选配置) FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)--> resultSetType="FORWARD_ONLY">
配置看起来老是这么多,不过实际经常使用的配置也就那么几个, 根据本身的须要吧,上面都已注明是否必须配置。看一个CourseDao-Mapper.xml配置:
<mapper namespace="com.dy.dao.CourseDao"> <!-- 1.此处直接将resultType 设置为course, 一看就知道我设置了别名吧,若是没有设置别名,那么resultType = com.dy.entity.Course。 2.可能细心的你会发现:Course.java中的属性名与数据库字段名不一致,下面,我就在sql语句中用了as, 使之匹配,固然方法不止一种,在学习了resultMap以后,你能看到一种更直观优雅的方式去将javabean中的属性与数据库字段名保持一致 3.findCourseById 与CourseDao中findCourseById方法对应, 那么传入的参数名称以及类型也应该保持对应关系。 4.能够看到,在sql语句中,经过#{}表达式能够获取参数。 5.下面这条sql语句,实际上的形式是怎么样的?还记得以前说过,mybatis默认为preparedStatement吧,那么,用咱们jdbc代码来看,它其实就是: select course_id as id, course_name as name, course_delete_flg as deleteFlag from t_course where course_id=? --> <select id="findCourseById" resultType="course" > select course_id as id, course_name as name, course_delete_flg as deleteFlag from t_course where course_id=#{courseId} </select> </mapper>
上面的示例,咱们针对course, 简单演示了 select的用法, 不过有个问题值得思考: 一个student能够对应多个course, 那么,在mybatis中如何处理这种一对多, 甚至于多对多,一对一的关系呢?
这儿,就不得不提到 resultMap 这个东西, mybatis的resultMap功能可谓十分强大,可以处理复杂的关系映射
, 那么resultMap 该怎么配置呢? 别急,这就来了:
<!-- resultMap –结果映射,用来描述如何从数据库结果集映射到你想要的对象。 1.type 对应类型,能够是javabean, 也能够是其它 2.id 必须惟一, 用于标示这个resultMap的惟一性,在使用resultMap的时候,就是经过id指定 --> <resultMap type="" id=""> <!-- id, 惟一性,注意啦,这个id用于标示这个javabean对象的惟一性, 不必定会是数据库的主键(不要把它理解为数据库对应表的主键) property属性对应javabean的属性名,column对应数据库表的列名 (这样,当javabean的属性与数据库对应表的列名不一致的时候,就能经过指定这个保持正常映射了) --> <id property="" column=""/> <!-- result与id相比, 对应普通属性 --> <result property="" column=""/> <!-- constructor对应javabean中的构造方法 --> <constructor> <!-- idArg 对应构造方法中的id参数;--> <idArg column=""/> <!-- arg 对应构造方法中的普通参数;--> <arg column=""/> </constructor> <!-- 汇集元素用来处理“一对多”的关系。须要指定映射的Java实体类的属性,属性的javaType(通常为ArrayList);列表中对象的类型ofType(Java实体类);对应的数据库表的列名称; collection,对应javabean中容器类型, 是实现一对多的关键 property 为javabean中容器对应字段名 column 为体如今数据库中列名 ofType 就是指定javabean中容器指定的类型 不一样状况须要告诉MyBatis 如何加载一个汇集。MyBatis 能够用两种方式加载: 1. select: 执行一个其它映射的SQL 语句返回一个Java实体类型。较灵活; 2. resultMap: 使用一个嵌套的结果映射来处理经过join查询结果集,映射成Java实体类型。 --> <collection property="" column="" ofType=""></collection> <!-- 联合元素用来处理“一对一”的关系。须要指定映射的Java实体类的属性,属性的javaType(一般MyBatis 本身会识别)。对应的数据库表的列名称。若是想覆写的话返回结果的值,须要指定typeHandler。 association 为关联关系,是实现N对一的关键。 property 为javabean中容器对应字段名 column 为体如今数据库中列名 javaType 指定关联的类型 不一样状况须要告诉MyBatis 如何加载一个联合。MyBatis能够用两种方式加载: 1. select: 执行一个其它映射的SQL 语句返回一个Java实体类型。较灵活; 2. resultMap: 使用一个嵌套的结果映射来处理,经过join查询结果集,映射成Java实体类型。 --> <association property="" column="" javaType=""></association> <!-- 有时一个单独的数据库查询也许返回不少不一样(可是但愿有些关联)数据类型的结果集。鉴别器元素就是被设计来处理这个状况的,还有包括类的继承层次结构。鉴别器很是容易理解,由于它的表现很像Java语言中的switch语句。 定义鉴别器指定了column和javaType属性。列是MyBatis查找比较值的地方。JavaType是须要被用来保证等价测试的合适类型(尽管字符串在不少情形下都会有用)。 下面这个例子为,当classId为20000001时,才映射classId属性。 --> <discriminator column="CLASS_ID" javaType="String" jdbcType="VARCHAR"> <case value="20000001" resultType="liming.student.manager.data.model.StudentEntity" > <result property="classId" column="CLASS_ID" javaType="String" jdbcType="VARCHAR"/> </case> </discriminator> </resultMap>
好啦,知道resutMap怎么配置后,我们当即接着上面的demo来练习一下吧,一个student对应多个course, 典型的一对多,我们就来看看mybatis怎么配置这种映射吧:StudentDao-Mapper.xml
<mapper namespace="com.dy.dao.StudentDao"> <!-- 这儿定义一个resultMap --> <resultMap type="student" id="studentMap"> <!-- 数据库中主键是id, 可是我这儿倒是指定idCard为主键,为何? 刚刚讲了,id用来表示惟一性, 咱们能够认为只要idCard同样,那么他就是同一个学生。 若是此处用数据库中id, 那么mybatis将会认为数据库中每条记录都是一个student, 这显然不符合逻辑 --> <id property="idCard" column="stu_id_card"/> <result property="id" column="stu_id"/> <result property="name" column="stu_name"/> <result property="deleteFlag" column="stu_delete_flg"/> <constructor> <idArg javaType="String" column="STUDENT_ID"/> <arg javaType="String" column="STUDENT_NAME"/> <arg javaType="String" column="STUDENT_SEX"/> <arg javaType="Date" column="STUDENT_BIRTHDAY"/> </constructor> <!-- 这儿就是实现一对多的关键。 在Student中,courseList为List<Course>, 所以,ofType也应该与之对应(固然,我用了别名,否则要蛋疼的写全名了)。 collection的子标签是在指定Course的映射关系(因为Course的javabean的属性名与数据库的列名不一致) --> <collection property="courseList" column="stu_course_id" ofType="Course"> <id property="id" column="course_id"/> <result property="name" column="course_name"/> <result property="deleteFlag" column="course_delete_flg"/> </collection> </resultMap> <!-- 这儿将返回类型设置成了上面指定的studentMap --> <select id="findStudentById" resultMap="studentMap"> SELECT s.*, c.* FROM t_student s LEFT JOIN t_course c ON s.stu_course_id=c.course_id WHERE s.stu_id_card=#{idCard} </select> <!-- sql –能够重用的SQL块,能够被其余数据库操做语句引用。 --> <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> </mapper>
固然,咱们须要定义StudentEntity实体类的构造方法:
public StudentEntity(String studentID, String studentName, String studentSex, Date studentBirthday){ this.studentID = studentID; this.studentName = studentName; this.studentSex = studentSex; this.studentBirthday = studentBirthday; }
相信经过以上示例, 你们也可以使用mybatis的select 和 resultMap的用法了。上面只演示了一对多的映射,其实多对1、多对多也与它相似,因此我就没演示了,有兴趣的能够本身动手再作作。
#3 字符串代入法# 默认的状况下,使用#{}语法会促使MyBatis 生成PreparedStatement 属性而且使用PreparedStatement 的参数(=?)来安全的设置值
。尽可能这些是快捷安全,也是常用的。但有时候你可能想直接未更改的字符串代入到SQL 语句中。好比说,对于ORDER BY,你可能会这样使用:ORDER BY ${columnName}但MyBatis 不会修改和规避掉这个字符串
。
注意:这样地接收和应用一个用户输入到未更改的语句中,是很是不安全的。这会让用户能植入破坏代码,因此,要么要求字段不要容许客户输入,要么你直接来检测他的合法性 。
#4 子元素之cache解析# Mapper配置文件是由XMLMapperBuilder解析的,其中cacheElement方法负责解析cache元素,它经过调用CacheBuilder的相应方法完成cache的建立
。每一个cache内部都有一个惟一的ID,这个id的值就是namespace。建立好的cache对象存入configuration的cache缓存中(该缓存以cache的ID属性即namespace为key,这里再次体现了mybatis的namespace的强大用处)。
private void cacheElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type", "PERPETUAL"); Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); String eviction = context.getStringAttribute("eviction", "LRU"); Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); Long flushInterval = context.getLongAttribute("flushInterval"); Integer size = context.getIntAttribute("size"); boolean readWrite = !context.getBooleanAttribute("readOnly", false); Properties props = context.getChildrenAsProperties(); builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props); } }
#5 子元素之cache-ref解析# cacheRefElement方法负责解析cache-ref元素,它经过调用CacheRefResolver的相应方法完成cache的引用。建立好的cache-ref引用关系存入configuration的cacheRefMap缓存中。
private void cacheRefElement(XNode context) { if (context != null) { configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace")); CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace")); try { cacheRefResolver.resolveCacheRef(); } catch (IncompleteElementException e) { configuration.addIncompleteCacheRef(cacheRefResolver); } } }
#6 子元素之resultMap解析# resultMapElement方法负责解析resultMap元素,它经过调用ResultMapResolver的相应方法完成resultMap的解析。建立好的resultMap存入configuration的resultMaps缓存中(该缓存以namespace+resultMap的id为key,这里再次体现了mybatis的namespace的强大用处)。
private ResultMap resultMapElement(XNode resultMapNode) throws Exception { return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList()); } private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); String extend = resultMapNode.getStringAttribute("extends"); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); Class<?> typeClass = resolveClass(type); Discriminator discriminator = null; List<ResultMapping> resultMappings = new ArrayList<ResultMapping>(); resultMappings.addAll(additionalResultMappings); List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { if ("constructor".equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
#7 子元素之sql解析# sqlElement方法负责解析sql元素。id属性用于区分不一样的sql元素,在同一个mapper配置文件中能够配置多个sql元素。
private void sqlElement(List<XNode> list) throws Exception { if (configuration.getDatabaseId() != null) { sqlElement(list, configuration.getDatabaseId()); } sqlElement(list, null); } private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception { for (XNode context : list) { String databaseId = context.getStringAttribute("databaseId"); String id = context.getStringAttribute("id"); id = builderAssistant.applyCurrentNamespace(id, false); if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context); } } private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) { if (requiredDatabaseId != null) { if (!requiredDatabaseId.equals(databaseId)) { return false; } } else { if (databaseId != null) { return false; } // skip this fragment if there is a previous one with a not null databaseId if (this.sqlFragments.containsKey(id)) { XNode context = this.sqlFragments.get(id); if (context.getStringAttribute("databaseId") != null) { return false; } } } return true; }
#8 子元素之statement解析# buildStatementFromContext方法负责解析statement元素。id属性用于区分不一样的statement元素,在同一个配置文件中能够配置多个statement元素。经过调用XMLStatementBuilder的parseStatementNode方法完成解析
。在这个方法内有几个重要的步骤,理解他们对正确的配置statement元素颇有帮助。
statement节点能够配置各类子元素,好比前面提到的include子元素和selectKey子元素等(在动态sql里还有更多的子元素,具体参考mybatis的官方文档)。动态解析子元素经过parseDynamicTags方法完成。该方法根据子元素的类型递归的解析成一个个的SqlNode,这些SqlNode对象提供了apply方法,供后续调用时生成sql语句所需。须要注意的是SelectKey没有对应的SqlNode对象,由于它的功能是用来生成KeyGenerator对象的(具体来讲是SelectKeyGenerator对象)。另外,SelectKey节点生成的KeyGenerator优先级高于statement节点的useGeneratedKeys属性生成的KeyGenerator对象,也就是说配置了SelectKey子节点就不须要再配置useGeneratedKeys属性了。
SqlSource用于后续调用时根据SqlNode和参数对象生成sql语句。它接收一个叫作rootsqlNode的对象做为构造参数。
若是配置了selectKey子元素,KeyGenerator直接使用selectKey子元素里生成的KeyGenerator对象(具体来讲是SelectKeyGenerator对象)。若没配置,则若是useGeneratedKeys属性的值为"true"且配置了 keyProperty属性,则生成默认的Jdbc3KeyGenerator对象,该对象调用JDBC驱动的getGeneratedKeys方法返回insert语句执行后生成的自增加主键。
MappedStatement对象封装了statement元素的全部属性以及子节点值,MappedStatement对象有一个id属性用于惟一标记它,这个id由namespace加statement元素的id属性值构成。建立好的MappedStatement对象存入Configuration对象的mappedStatements缓存中,key为MappedStatement对象的id值。
XMLMapperBuilder.java:
private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
XMLStatementBuilder.java:
public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return; Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); } builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
#9 注册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就用到了自动扫描)
。
private void bindMapperForNamespace() { String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //ignore, bound type is not required } if (boundType != null) { if (!configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); configuration.addMapper(boundType); } } } }