调用链

SqlSessionFactoryBuilder.build()
-XMLConfigBuilder.parse() (config.xml)
-parseConfiguration()
-mapperElement()
-XMLMapperBuilder.parse() (mapper.xml)
-configurationElement()
-cacheRefElement()
-buildStatementFromContext()
-XMLStatementBuilder.parseStatementNode() (select|insert|update|delete节点)

在mapper文件中,以mapper作为根节点,其下面可以配置的元素节点有: select, insert, update, delete, cache, cache-ref, resultMap, sql 。

1. insert, update, delete 的配置及使用

相信,看到insert, update, delete, 我们就知道其作用了,顾名思义嘛,myabtis 作为持久层框架,必须要对CRUD啊。好啦,咱们就先来看看 insert, update, delete 怎么配置, 能配置哪些元素吧:

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
  4. "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
  5. <!-- mapper 为根元素节点, 一个namespace对应一个dao -->
  6. <!--
  7. Mapper元素只有一个属性namespace,它有两个作用:`一是用于区分不同的mapper`(在不同的mapper文件里,子元素的id可以相同,mybatis通过namespace和子元素的id联合区分),`二是与接口关联`(应用程序通过接口访问mybatis时,mybatis通过接口的完整名称查找对应的mapper配置,因此namespace的命名务必小心一定要某接口同名)。
  8. -->
  9. <mapper namespace="com.dy.dao.UserDao">
  10. <!--
  11. cache- 配置本定命名空间的缓存。
  12. type- cache实现类,默认为PERPETUAL,可以使用自定义的cache实现类(别名或完整类名皆可)
  13. eviction- 回收算法,默认为LRU,可选的算法有:
  14. LRU– 最近最少使用的:移除最长时间不被使用的对象。
  15. FIFO– 先进先出:按对象进入缓存的顺序来移除它们。
  16. SOFT– 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  17. WEAK– 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
  18. flushInterval- 刷新间隔,默认为1个小时,单位毫秒
  19. size- 缓存大小,默认大小1024,单位为引用数
  20. readOnly- 只读
  21. -->
  22. <cache type="PERPETUAL" eviction="LRU" flushInterval="60000"
  23. size="512" readOnly="true" />
  24. <!--
  25. cache-ref–从其他命名空间引用缓存配置。
  26. 如果你不想定义自己的cache,可以使用cache-ref引用别的cache。因为每个cache都以namespace为id,所以cache-ref只需要配置一个namespace属性就可以了。需要注意的是,如果cache-ref和cache都配置了,以cache为准。
  27. -->
  28. <cache-ref namespace="com.someone.application.data.SomeMapper"/>
  29. <insert
  30. <!-- 1. id (必须配置)
  31. id是命名空间中的唯一标识符,可被用来代表这条语句。
  32. 一个命名空间(namespace) 对应一个dao接口,
  33. 这个id也应该对应dao里面的某个方法(相当于方法的实现),因此id 应该与方法名一致 -->
  34. id="insertUser"
  35. <!-- 2. parameterType (可选配置, 默认为mybatis自动选择处理)
  36. 将要传入语句的参数的完全限定类名或别名, 如果不配置,mybatis会通过ParameterHandler 根据参数类型默认选择合适的typeHandler进行处理
  37. parameterType 主要指定参数类型,可以是int, short, long, string等类型,也可以是复杂类型(如对象) -->
  38. parameterType="com.demo.User"
  39. <!-- 3. flushCache (可选配置,默认配置为true)
  40. 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:true(对应插入、更新和删除语句) -->
  41. flushCache="true"
  42. <!-- 4. statementType (可选配置,默认配置为PREPARED)
  43. STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 -->
  44. statementType="PREPARED"
  45. <!-- 5. keyProperty (可选配置, 默认为unset)
  46. (仅对 insert 和 update 有用)唯一标记一个属性,MyBatis 会通过 getGeneratedKeys 的返回值或者通过 insert 语句的 selectKey 子元素设置它的键值,默认:unset。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 -->
  47. keyProperty=""
  48. <!-- 6. keyColumn (可选配置)
  49. (仅对 insert 和 update 有用)通过生成的键值设置表中的列名,这个设置仅在某些数据库(像 PostgreSQL)是必须的,当主键列不是表中的第一列的时候需要设置。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 -->
  50. keyColumn=""
  51. <!-- 7. useGeneratedKeys (可选配置, 默认为false)
  52. (仅对 insert 和 update 有用)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系数据库管理系统的自动递增字段),默认值:false。 -->
  53. useGeneratedKeys="false"
  54. <!-- 8. timeout (可选配置, 默认为unset, 依赖驱动)
  55. 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)。 -->
  56. timeout="20">
  57. <update
  58. id="updateUser"
  59. parameterType="com.demo.User"
  60. flushCache="true"
  61. statementType="PREPARED"
  62. timeout="20">
  63. <delete
  64. id="deleteUser"
  65. parameterType="com.demo.User"
  66. flushCache="true"
  67. statementType="PREPARED"
  68. timeout="20">
  69. </mapper>

以上就是一个模板配置, 哪些是必要配置,哪些是根据自己实际需求,看一眼就知道了。看一个真实的UserDao-Mapper.xml配置:

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"
  4. "http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd">
  5. <mapper namespace="com.dy.dao.UserDao">
  6. <!-- 对应userDao中的insertUser方法, -->
  7. <insert id="insertUser" parameterType="com.dy.entity.User">
  8. insert into user(id, name, password, age, deleteFlag)
  9. values(#{id}, #{name}, #{password}, #{age}, #{deleteFlag})
  10. </insert>
  11. <!-- 对应userDao中的updateUser方法 -->
  12. <update id="updateUser" parameterType="com.dy.entity.User">
  13. update user set name = #{name}, password = #{password}, age = #{age}, deleteFlag = #{deleteFlag}
  14. where id = #{id};
  15. </update>
  16. <!-- 对应userDao中的deleteUser 方法 -->
  17. <delete id="deleteUser" parameterType="com.dy.entity.User">
  18. delete from user where id = #{id};
  19. </delete>
  20. </mapper>

这样,一个简单的映射关系就建立了。仔细观察上面parameterType, "com.dy.entity.User",包名要是再长点呢,每次都这样写,写得蛋疼了。别忘了之前讲的 typeAliases(别名), 那么这个地方,用上别名,岂不是技能跟蛋疼的长长的包名说拜拜了。好啦,咱们配上别名,在哪儿配? 当然是在mybatis 的全局配置文件(我这儿名字是mybatis-conf.xml), 不要认为是在mapper的配置文件里面配置哈。

  1. <typeAliases>
  2. <!--
  3. 通过package, 可以直接指定package的名字, mybatis会自动扫描你指定包下面的javabean,
  4. 并且默认设置一个别名,默认的名字为: javabean 的首字母小写的非限定类名来作为它的别名。
  5. 也可在javabean 加上注解@Alias 来自定义别名, 例如: @Alias(user)
  6. <package name="com.dy.entity"/>
  7. -->
  8. <typeAlias alias="user" type="com.dy.entity.User"/>
  9. </typeAliases>

这样,一个别名就取好了,咱们可以把上面的 com.dy.entity.User 都直接改为user 了。 这多方便呀!

我这儿数据库用的是mysql, 我把user表的主键id 设置了自动增长, 以上代码运行正常, 那么问题来了(当然,我不是要问学挖掘机哪家强),我要是换成oracle数据库怎么办? oracle 可是不支持id自增长啊? 怎么办?请看下面:

  1. <!-- 对应userDao中的insertUser方法, -->
  2. <insert id="insertUser" parameterType="com.dy.entity.User">
  3. <!-- oracle等不支持id自增长的,可根据其id生成策略,先获取id -->
  4. <selectKey resultType="int" order="BEFORE" keyProperty="id">
  5. select seq_user_id.nextval as id from dual
  6. </selectKey>
  7. insert into user(id, name, password, age, deleteFlag)
  8. values(#{id}, #{name}, #{password}, #{age}, #{deleteFlag})
  9. </insert>

同理,如果我们在使用mysql的时候,想在数据插入后返回插入的id, 我们也可以使用 selectKey 这个元素

  1. <!-- 对应userDao中的insertUser方法, -->
  2. <insert id="insertUser" parameterType="com.dy.entity.User">
  3. <!-- oracle等不支持id自增长的,可根据其id生成策略,先获取id
  4. <selectKey resultType="int" order="BEFORE" keyProperty="id">
  5. select seq_user_id.nextval as id from dual
  6. </selectKey>
  7. -->
  8. <!-- mysql插入数据后,获取id,该方法LAST_INSERT_ID()与数据库连接绑定,同属统一会话级别。-->
  9. <selectKey keyProperty="id" resultType="int" order="AFTER" >
  10. SELECT LAST_INSERT_ID() as id
  11. </selectKey>
  12. insert into user(id, name, password, age, deleteFlag)
  13. values(#{id}, #{name}, #{password}, #{age}, #{deleteFlag})
  14. </insert>

这儿,我们就简单提一下 这个元素节点吧: selectKey给了你一个简单的行为在你的数据库中来处理自动生成的主键,而不需要使你的Java代码变得复杂。在上面的示例中,selectKey元素将会首先运行,userid会被设置,然后插入语句会被调用。另外,selectKey节点生成的KeyGenerator优先级高于statement节点的useGeneratedKeys属性生成的KeyGenerator对象,也就是说配置了SelectKey子节点就不需要再配置useGeneratedKeys属性了

  1. <selectKey
  2. <!-- selectKey 语句结果应该被设置的目标属性。如果希望得到多个生成的列,也可以是逗号分隔的属性名称列表。 -->
  3. keyProperty="id"
  4. <!-- 结果的类型。MyBatis 通常可以推算出来,但是为了更加确定写上也不会有什么问题。MyBatis 允许任何简单类型用作主键的类型,包括字符串。如果希望作用于多个生成的列,则可以使用一个包含期望属性的 Object 或一个 Map。 -->
  5. resultType="int"
  6. <!-- 这可以被设置为 BEFORE 或 AFTER。如果设置为 BEFORE,那么它会首先选择主键,设置 keyProperty 然后执行插入语句。如果设置为 AFTER,那么先执行插入语句,然后是 selectKey 元素 - 这和像 Oracle 的数据库相似,在插入语句内部可能有嵌入索引调用。 -->
  7. order="BEFORE"
  8. <!-- 与前面相同,MyBatis 支持 STATEMENT,PREPARED 和 CALLABLE 语句的映射类型,分别代表 PreparedStatement 和 CallableStatement 类型。 -->
  9. statementType="PREPARED">

2. select、resultMap的配置及使用

select无疑是我们最常用,也是最复杂的,mybatis通过resultMap能帮助我们很好地进行高级映射。下面就开始看看select 以及 resultMap的用法:

先看select的配置吧:

  1. <select
  2. <!-- 1. id (必须配置)
  3. id是命名空间中的唯一标识符,可被用来代表这条语句。
  4. 一个命名空间(namespace) 对应一个dao接口,
  5. 这个id也应该对应dao里面的某个方法(相当于方法的实现),因此id 应该与方法名一致
  6. -->
  7. id="selectPerson"
  8. <!-- 2. parameterType (可选配置, 默认为mybatis自动选择处理)
  9. 将要传入语句的参数的完全限定类名或别名, 如果不配置,mybatis会通过ParameterHandler 根据参数类型默认选择合适的typeHandler进行处理
  10. parameterType 主要指定参数类型,可以是int, short, long, string等类型,也可以是复杂类型(如对象) -->
  11. parameterType="int"
  12. <!-- 3. resultType (resultType 与 resultMap 二选一配置)
  13. resultType用以指定返回类型,指定的类型可以是基本类型,可以是java容器,也可以是javabean -->
  14. resultType="hashmap"
  15. <!-- 4. resultMap (resultType 与 resultMap 二选一配置)
  16. resultMap用于引用我们通过 resultMap标签定义的映射类型,这也是mybatis组件高级复杂映射的关键 -->
  17. resultMap="personResultMap"
  18. <!-- 5. flushCache (可选配置)
  19. 将其设置为 true,任何时候只要语句被调用,都会导致本地缓存和二级缓存都会被清空,默认值:false -->
  20. flushCache="false"
  21. <!-- 6. useCache (可选配置)
  22. 将其设置为 true,将会导致本条语句的结果被二级缓存,默认值:对 select 元素为 true -->
  23. useCache="true"
  24. <!-- 7. timeout (可选配置)
  25. 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为 unset(依赖驱动)-->
  26. timeout="10000"
  27. <!-- 8. fetchSize (可选配置)
  28. 这是尝试影响驱动程序每次批量返回的结果行数和这个设置值相等。默认值为 unset(依赖驱动)-->
  29. fetchSize="256"
  30. <!-- 9. statementType (可选配置)
  31. STATEMENT,PREPARED 或 CALLABLE 的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED-->
  32. statementType="PREPARED"
  33. <!-- 10. resultSetType (可选配置)
  34. FORWARD_ONLY,SCROLL_SENSITIVE 或 SCROLL_INSENSITIVE 中的一个,默认值为 unset (依赖驱动)-->
  35. resultSetType="FORWARD_ONLY">

配置看起来总是这么多,不过实际常用的配置也就那么几个, 根据自己的需要吧,上面都已注明是否必须配置。看一个CourseDao-Mapper.xml配置:

  1. <mapper namespace="com.dy.dao.CourseDao">
  2. <!--
  3. 1.此处直接将resultType 设置为course, 一看就知道我设置了别名吧,如果没有设置别名,那么resultType = com.dy.entity.Course。
  4. 2.可能细心的你会发现:Course.java中的属性名与数据库字段名不一致,下面,我就在sql语句中用了as, 使之匹配,当然方法不止一种,在学习了resultMap之后,你能看到一种更直观优雅的方式去将javabean中的属性与数据库字段名保持一致
  5. 3.findCourseById 与CourseDao中findCourseById方法对应, 那么传入的参数名称以及类型也应该保持对应关系。
  6. 4.可以看到,在sql语句中,通过#{}表达式可以获取参数。
  7. 5.下面这条sql语句,实际上的形式是怎么样的?还记得之前说过,mybatis默认为preparedStatement吧,那么,用我们jdbc代码来看,它其实就是:
  8. select course_id as id, course_name as name, course_delete_flg as deleteFlag from t_course where course_id=?
  9. -->
  10. <select id="findCourseById" resultType="course" >
  11. select course_id as id, course_name as name, course_delete_flg as deleteFlag from t_course where course_id=#{courseId}
  12. </select>
  13. </mapper>

上面的示例,我们针对course, 简单演示了 select的用法, 不过有个问题值得思考: 一个student可以对应多个course, 那么,在mybatis中如何处理这种一对多, 甚至于多对多,一对一的关系呢?

这儿,就不得不提到 resultMap 这个东西, mybatis的resultMap功能可谓十分强大,能够处理复杂的关系映射, 那么resultMap 该怎么配置呢? 别急,这就来了:

  1. <!--
  2. resultMap –结果映射,用来描述如何从数据库结果集映射到你想要的对象。
  3. 1.type 对应类型,可以是javabean, 也可以是其它
  4. 2.id 必须唯一, 用于标示这个resultMap的唯一性,在使用resultMap的时候,就是通过id指定
  5. -->
  6. <resultMap type="" id="">
  7. <!-- id, 唯一性,注意啦,这个id用于标示这个javabean对象的唯一性, 不一定会是数据库的主键(不要把它理解为数据库对应表的主键)
  8. property属性对应javabean的属性名,column对应数据库表的列名
  9. (这样,当javabean的属性与数据库对应表的列名不一致的时候,就能通过指定这个保持正常映射了)
  10. -->
  11. <id property="" column=""/>
  12. <!-- result与id相比, 对应普通属性 -->
  13. <result property="" column=""/>
  14. <!--
  15. constructor对应javabean中的构造方法
  16. -->
  17. <constructor>
  18. <!-- idArg 对应构造方法中的id参数;-->
  19. <idArg column=""/>
  20. <!-- arg 对应构造方法中的普通参数;-->
  21. <arg column=""/>
  22. </constructor>
  23. <!--
  24. 聚集元素用来处理“一对多”的关系。需要指定映射的Java实体类的属性,属性的javaType(一般为ArrayList);列表中对象的类型ofType(Java实体类);对应的数据库表的列名称;
  25. collection,对应javabean中容器类型, 是实现一对多的关键
  26. property 为javabean中容器对应字段名
  27. column 为体现在数据库中列名
  28. ofType 就是指定javabean中容器指定的类型
  29. 不同情况需要告诉MyBatis 如何加载一个聚集。MyBatis 可以用两种方式加载:
  30. 1. select: 执行一个其它映射的SQL 语句返回一个Java实体类型。较灵活;
  31. 2. resultMap: 使用一个嵌套的结果映射来处理通过join查询结果集,映射成Java实体类型。
  32. -->
  33. <collection property="" column="" ofType=""></collection>
  34. <!--
  35. 联合元素用来处理“一对一”的关系。需要指定映射的Java实体类的属性,属性的javaType(通常MyBatis 自己会识别)。对应的数据库表的列名称。如果想覆写的话返回结果的值,需要指定typeHandler。
  36. association 为关联关系,是实现N对一的关键。
  37. property 为javabean中容器对应字段名
  38. column 为体现在数据库中列名
  39. javaType 指定关联的类型
  40. 不同情况需要告诉MyBatis 如何加载一个联合。MyBatis可以用两种方式加载:
  41. 1. select: 执行一个其它映射的SQL 语句返回一个Java实体类型。较灵活;
  42. 2. resultMap: 使用一个嵌套的结果映射来处理,通过join查询结果集,映射成Java实体类型。
  43. -->
  44. <association property="" column="" javaType=""></association>
  45. <!--
  46. 有时一个单独的数据库查询也许返回很多不同(但是希望有些关联)数据类型的结果集。鉴别器元素就是被设计来处理这个情况的,还有包括类的继承层次结构。鉴别器非常容易理解,因为它的表现很像Java语言中的switch语句。
  47. 定义鉴别器指定了column和javaType属性。列是MyBatis查找比较值的地方。JavaType是需要被用来保证等价测试的合适类型(尽管字符串在很多情形下都会有用)。
  48. 下面这个例子为,当classId为20000001时,才映射classId属性。
  49. -->
  50. <discriminator column="CLASS_ID" javaType="String" jdbcType="VARCHAR">
  51. <case value="20000001" resultType="liming.student.manager.data.model.StudentEntity" >
  52. <result property="classId" column="CLASS_ID" javaType="String" jdbcType="VARCHAR"/>
  53. </case>
  54. </discriminator>
  55. </resultMap>

好啦,知道resutMap怎么配置后,咱们立即接着上面的demo来练习一下吧,一个student对应多个course, 典型的一对多,咱们就来看看mybatis怎么配置这种映射吧:StudentDao-Mapper.xml

  1. <mapper namespace="com.dy.dao.StudentDao">
  2. <!-- 这儿定义一个resultMap -->
  3. <resultMap type="student" id="studentMap">
  4. <!--
  5. 数据库中主键是id, 但是我这儿却是指定idCard为主键,为什么?
  6. 刚刚讲了,id用来表示唯一性, 我们可以认为只要idCard一样,那么他就是同一个学生。
  7. 如果此处用数据库中id, 那么mybatis将会认为数据库中每条记录都是一个student, 这显然不符合逻辑
  8. -->
  9. <id property="idCard" column="stu_id_card"/>
  10. <result property="id" column="stu_id"/>
  11. <result property="name" column="stu_name"/>
  12. <result property="deleteFlag" column="stu_delete_flg"/>
  13. <constructor>
  14. <idArg javaType="String" column="STUDENT_ID"/>
  15. <arg javaType="String" column="STUDENT_NAME"/>
  16. <arg javaType="String" column="STUDENT_SEX"/>
  17. <arg javaType="Date" column="STUDENT_BIRTHDAY"/>
  18. </constructor>
  19. <!--
  20. 这儿就是实现一对多的关键。
  21. 在Student中,courseList为List<Course>, 因此,ofType也应该与之对应(当然,我用了别名,不然要蛋疼的写全名了)。
  22. collection的子标签是在指定Course的映射关系(由于Course的javabean的属性名与数据库的列名不一致)
  23. -->
  24. <collection property="courseList" column="stu_course_id" ofType="Course">
  25. <id property="id" column="course_id"/>
  26. <result property="name" column="course_name"/>
  27. <result property="deleteFlag" column="course_delete_flg"/>
  28. </collection>
  29. </resultMap>
  30. <!-- 这儿将返回类型设置成了上面指定的studentMap -->
  31. <select id="findStudentById" resultMap="studentMap">
  32. 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}
  33. </select>
  34. <!--
  35. sql –可以重用的SQL块,可以被其他数据库操作语句引用。
  36. -->
  37. <sql id="userColumns"> userid,username,password</sql>
  38. <select id="queryUsers" parameterType="UserDto" resultType="UserDto" useCache="false">
  39. select <include refid="userColumns"/> from t_user t where t.username = #{username}
  40. </select>
  41. </mapper>

当然,我们需要定义StudentEntity实体类的构造方法:

  1. public StudentEntity(String studentID, String studentName, String studentSex, Date studentBirthday){
  2. this.studentID = studentID;
  3. this.studentName = studentName;
  4. this.studentSex = studentSex;
  5. this.studentBirthday = studentBirthday;
  6. }

相信通过以上示例, 大家也能够使用mybatis的select 和 resultMap的用法了。上面只演示了一对多的映射,其实多对一、多对多也与它类似,所以我就没演示了,有兴趣的可以自己动手再做做。

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的强大用处)。

  1. private void cacheElement(XNode context) throws Exception {
  2. if (context != null) {
  3. String type = context.getStringAttribute("type", "PERPETUAL");
  4. Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
  5. String eviction = context.getStringAttribute("eviction", "LRU");
  6. Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
  7. Long flushInterval = context.getLongAttribute("flushInterval");
  8. Integer size = context.getIntAttribute("size");
  9. boolean readWrite = !context.getBooleanAttribute("readOnly", false);
  10. Properties props = context.getChildrenAsProperties();
  11. builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);
  12. }
  13. }

5. 子元素之cache-ref解析

cacheRefElement方法负责解析cache-ref元素,它通过调用CacheRefResolver的相应方法完成cache的引用。创建好的cache-ref引用关系存入configuration的cacheRefMap缓存中。

  1. private void cacheRefElement(XNode context) {
  2. if (context != null) {
  3. configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
  4. CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
  5. try {
  6. cacheRefResolver.resolveCacheRef();
  7. } catch (IncompleteElementException e) {
  8. configuration.addIncompleteCacheRef(cacheRefResolver);
  9. }
  10. }
  11. }

6. 子元素之resultMap解析

resultMapElement方法负责解析resultMap元素,它通过调用ResultMapResolver的相应方法完成resultMap的解析。创建好的resultMap存入configuration的resultMaps缓存中(该缓存以namespace+resultMap的id为key,这里再次体现了mybatis的namespace的强大用处)。

  1. private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
  2. return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
  3. }
  4. private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
  5. ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
  6. String id = resultMapNode.getStringAttribute("id",
  7. resultMapNode.getValueBasedIdentifier());
  8. String type = resultMapNode.getStringAttribute("type",
  9. resultMapNode.getStringAttribute("ofType",
  10. resultMapNode.getStringAttribute("resultType",
  11. resultMapNode.getStringAttribute("javaType"))));
  12. String extend = resultMapNode.getStringAttribute("extends");
  13. Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
  14. Class<?> typeClass = resolveClass(type);
  15. Discriminator discriminator = null;
  16. List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
  17. resultMappings.addAll(additionalResultMappings);
  18. List<XNode> resultChildren = resultMapNode.getChildren();
  19. for (XNode resultChild : resultChildren) {
  20. if ("constructor".equals(resultChild.getName())) {
  21. processConstructorElement(resultChild, typeClass, resultMappings);
  22. } else if ("discriminator".equals(resultChild.getName())) {
  23. discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
  24. } else {
  25. ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
  26. if ("id".equals(resultChild.getName())) {
  27. flags.add(ResultFlag.ID);
  28. }
  29. resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
  30. }
  31. }
  32. ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
  33. try {
  34. return resultMapResolver.resolve();
  35. } catch (IncompleteElementException e) {
  36. configuration.addIncompleteResultMap(resultMapResolver);
  37. throw e;
  38. }
  39. }

7. 子元素之sql解析

sqlElement方法负责解析sql元素。id属性用于区分不同的sql元素,在同一个mapper配置文件中可以配置多个sql元素。

  1. private void sqlElement(List<XNode> list) throws Exception {
  2. if (configuration.getDatabaseId() != null) {
  3. sqlElement(list, configuration.getDatabaseId());
  4. }
  5. sqlElement(list, null);
  6. }
  7. private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
  8. for (XNode context : list) {
  9. String databaseId = context.getStringAttribute("databaseId");
  10. String id = context.getStringAttribute("id");
  11. id = builderAssistant.applyCurrentNamespace(id, false);
  12. if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) sqlFragments.put(id, context);
  13. }
  14. }
  15. private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
  16. if (requiredDatabaseId != null) {
  17. if (!requiredDatabaseId.equals(databaseId)) {
  18. return false;
  19. }
  20. } else {
  21. if (databaseId != null) {
  22. return false;
  23. }
  24. // skip this fragment if there is a previous one with a not null databaseId
  25. if (this.sqlFragments.containsKey(id)) {
  26. XNode context = this.sqlFragments.get(id);
  27. if (context.getStringAttribute("databaseId") != null) {
  28. return false;
  29. }
  30. }
  31. }
  32. return true;
  33. }

8. 子元素之statement解析

buildStatementFromContext方法负责解析statement元素。id属性用于区分不同的statement元素,在同一个配置文件中可以配置多个statement元素。通过调用XMLStatementBuilder的parseStatementNode方法完成解析。在这个方法内有几个重要的步骤,理解他们对正确的配置statement元素很有帮助。

MappedStatement对象封装了statement元素的所有属性以及子节点值,MappedStatement对象有一个id属性用于唯一标记它,这个id由namespace加statement元素的id属性值构成。创建好的MappedStatement对象存入Configuration对象的mappedStatements缓存中,key为MappedStatement对象的id值。

XMLMapperBuilder.java

  1. private void buildStatementFromContext(List<XNode> list) {
  2. if (configuration.getDatabaseId() != null) {
  3. buildStatementFromContext(list, configuration.getDatabaseId());
  4. }
  5. buildStatementFromContext(list, null);
  6. }
  7. private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  8. for (XNode context : list) {
  9. final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
  10. try {
  11. statementParser.parseStatementNode();
  12. } catch (IncompleteElementException e) {
  13. configuration.addIncompleteStatement(statementParser);
  14. }
  15. }
  16. }

XMLStatementBuilder.java

  1. public void parseStatementNode() {
  2. //1.1 【动态解析子元素】
  3. String id = context.getStringAttribute("id");
  4. String databaseId = context.getStringAttribute("databaseId");
  5. if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;
  6. Integer fetchSize = context.getIntAttribute("fetchSize");
  7. Integer timeout = context.getIntAttribute("timeout");
  8. String parameterMap = context.getStringAttribute("parameterMap");
  9. String parameterType = context.getStringAttribute("parameterType");
  10. Class<?> parameterTypeClass = resolveClass(parameterType);
  11. String resultMap = context.getStringAttribute("resultMap");
  12. String resultType = context.getStringAttribute("resultType");
  13. String lang = context.getStringAttribute("lang");
  14. LanguageDriver langDriver = getLanguageDriver(lang);
  15. Class<?> resultTypeClass = resolveClass(resultType);
  16. String resultSetType = context.getStringAttribute("resultSetType");
  17. StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  18. ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  19. String nodeName = context.getNode().getNodeName();
  20. SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  21. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  22. boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  23. boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  24. boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  25. // Include Fragments before parsing
  26. XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  27. includeParser.applyIncludes(context.getNode());
  28. // Parse selectKey after includes and remove them.
  29. processSelectKeyNodes(id, parameterTypeClass, langDriver);
  30. //1.2 【生成SqlSource】
  31. // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  32. SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  33. String resultSets = context.getStringAttribute("resultSets");
  34. String keyProperty = context.getStringAttribute("keyProperty");
  35. String keyColumn = context.getStringAttribute("keyColumn");
  36. //1.3 【生成KeyGenerator】
  37. KeyGenerator keyGenerator;
  38. String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  39. keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  40. if (configuration.hasKeyGenerator(keyStatementId)) {
  41. keyGenerator = configuration.getKeyGenerator(keyStatementId);
  42. } else {
  43. keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
  44. configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
  45. ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
  46. }
  47. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
  48. fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
  49. resultSetTypeEnum, flushCache, useCache, resultOrdered,
  50. keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  51. }

上方的parseStatementNode()方法中进行一下操作:

1.1 动态解析子元素

statement节点可以配置各种子元素,比如前面提到的include子元素和selectKey子元素等(在动态sql里还有更多的子元素,具体参考mybatis的官方文档)。动态解析子元素通过parseDynamicTags方法完成。该方法根据子元素的类型递归的解析成一个个的SqlNode,这些SqlNode对象提供了apply方法,供后续调用时生成sql语句所需。需要注意的是SelectKey没有对应的SqlNode对象,因为它的功能是用来生成KeyGenerator对象的(具体来说是SelectKeyGenerator对象)。另外,SelectKey节点生成的KeyGenerator优先级高于statement节点的useGeneratedKeys属性生成的KeyGenerator对象,也就是说配置了SelectKey子节点就不需要再配置useGeneratedKeys属性了。

1.2 生成SqlSource

SqlSource用于后续调用时根据SqlNode和参数对象生成sql语句。它接收一个叫做rootsqlNode的对象作为构造参数。

1.3 生成KeyGenerator

如果配置了selectKey子元素,KeyGenerator直接使用selectKey子元素里生成的KeyGenerator对象(具体来说是SelectKeyGenerator对象)。若没配置,则如果useGeneratedKeys属性的值为”true”且配置了 keyProperty属性,则生成默认的Jdbc3KeyGenerator对象,该对象调用JDBC驱动的getGeneratedKeys方法返回insert语句执行后生成的自增长主键。

10. 注册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就用到了自动扫描)

  1. private void bindMapperForNamespace() {
  2. String namespace = builderAssistant.getCurrentNamespace();
  3. if (namespace != null) {
  4. Class<?> boundType = null;
  5. try {
  6. boundType = Resources.classForName(namespace);
  7. } catch (ClassNotFoundException e) {
  8. //ignore, bound type is not required
  9. }
  10. if (boundType != null) {
  11. if (!configuration.hasMapper(boundType)) {
  12. // Spring may not know the real resource name so we set a flag
  13. // to prevent loading again this resource from the mapper interface
  14. // look at MapperAnnotationBuilder#loadXmlResource
  15. configuration.addLoadedResource("namespace:" + namespace);
  16. configuration.addMapper(boundType);
  17. }
  18. }
  19. }
  20. }