高级结果映射
下面我们来看看官方文档上提供的 Demo ,一个复杂的查询语句
<select id="selectBlogDetails" parameterType="int" resultMap="detailedBlogResultMap"> select B.id as blog_id, B.title as blog_title, B.author_id as blog_author_id, A.id as author_id, A.username as author_username, A.password as author_password, A.email as author_email, A.bio as author_bio, A.favourite_section as author_favourite_section, P.id as post_id, P.blog_id as post_blog_id, P.author_id as post_author_id, P.created_on as post_created_on, P.section as post_section, P.subject as post_subject, P.draft as draft, P.body as post_body, C.id as comment_id, C.post_id as comment_post_id, C.name as comment_name, C.comment as comment_text, T.id as tag_id, T.name as tag_name from Blog B left outer join Author A on B.author_id = A.id left outer join Post P on B.id = P.blog_id left outer join Comment C on P.id = C.post_id left outer join Post_Tag PT on PT.post_id = P.id left outer join Tag T on PT.tag_id = T.id where B.id = #{id} </select>
Ok,看看对于上面复杂的语句,对应的ResultMap为:
<resultMap id="detailedBlogResultMap" type="Blog"> <constructor> <idArg column="blog_id" javaType="int"/> </constructor> <result property="title" column="blog_title"/> <association property="author" javaType=" Author"> <id property="id" column="author_id"/> <result property="username" column="author_username"/> <result property="password" column="author_password"/> <result property="email" column="author_email"/> <result property="bio" column="author_bio"/> <result property="favouriteSection" column="author_favourite_section"/> </association> <collection property="posts" ofType="Post"> <id property="id" column="post_id"/> <result property="subject" column="post_subject"/> <association property="author" javaType="Author"/> <collection property="comments" ofType=" Comment"> <id property="id" column="comment_id"/> </collection> <collection property="tags" ofType=" Tag" > <id property="id" column="tag_id"/> </collection> <discriminator javaType="int" column="draft"> <case value="1" resultType="DraftPost"/> </discriminator> </collection> </resultMap>
事情来了,上面 resultMap 有很多节点,下面是 resultMap 元素的概念视图
· constructor - 类在实例化时 , 用来注入结果到构造方法中
· idArg - ID 参数 ; 标记结果作为 ID 可以帮助提高整体效能
· arg - 注入到构造方法的一个普通结果
· id – 一个 ID 结果 ; 标记结果作为 ID 可以帮助提高整体效能
· result – 注入到字段或 JavaBean 属性的普通结果
· association – 一个复杂的类型关联 ; 许多结果将包成这种类型
· 嵌入结果映射 – 结果映射自身的关联 , 或者参考一个
· collection – 复杂类型的集
· 嵌入结果映射 – 结果映射自身的集 , 或者参考一个
· discriminator – 使用结果值来决定使用哪个结果映射
· case – 基于某些值的结果映射
· 嵌入结果映射 – 这种情形结果也映射它本身 , 因此可以包含很多相 同的元素 , 或者它可以参照一个外部的结果映射。
下面我们来详细说明每个元素
id & result :
<id property="id" column="post_id"/> <result property="subject" column="post_subject"/>
这些是结果映射最基本内容。 id 和 result 都映射一个单独列的值到简单数据类型 ( 字符 串 , 整型 , 双精度浮点数 , 日期等 ) 的单独属性或字段。
这两者之间的唯一不同是 id 表示的结果将是当比较对象实例时用到的标识属性。这帮助来改进整体表现 , 特别是缓存和嵌入结果映射 ( 也就是联合映射 ) 。
Id and Result Attributes | |
属性 | 描述 |
property | 映射到列结果的字段或属性。如果匹配的是存在的 , 和给定名称相同 的 JavaBeans 的属性 , 那么就 会使用。否则 MyBatis 将会寻找给定名称 property 的字段。这两种情形你可以使用通常点式的复 杂属性导航。比如 , 你 可以这样映射一些东西 : “username” , 或者映射到一些复杂的东西 : “address.street.number” 。 |
column | 从数据库中得到的列名 , 或者是列名的重命名标签。这也是通常和会 传递给 resultSet.getString (columnName) 方法参数中相同的字符串。 |
javaType | 一个 Java 类的完全限定名 , 或一个类型别名 ( 参加上面内建类型别名 的列表 ) 。如果你映射到一个 JavaBean,MyBatis 通常可以断定类型。 然而 , 如果你映射到的是 HashMap, 那么你应该明确地指定 javaType 来保证所需的行为。 |
jdbcType | 在这个表格之后的所支持的 JDBC 类型列表中的类型。 JDBC 类型是仅 仅需要对插入 , 更新和删 除操作可能为空的列进行处理。这是 JDBC jdbcType 的需要 , 而不是 MyBatis 的。如果你直接使 用 JDBC 编程 , 你需要指定 这个类型 - 但仅仅对可能为空的值。 |
typeHandler | 我们在前面讨论过默认的类型处理器。使用这个属性 , 你可以覆盖默 认的类型处理器。这个属性值 是类的完全限定名或者是一个类型处理 器的实现 , 或者是类型别名。 |
构造方法:
<constructor> <idArg column="id" javaType="int"/> <arg column="username" javaType="String"/> </constructor>
这里需要注意id和username的顺序问题,不能颠倒。
public class User {
//...
public User(int id, String username) {
//...
}
//...
}
关联
<association property="author" column="blog_author_id" javaType=" Author"> <id property="id" column="author_id"/> <result property="username" column="author_username"/> </association>
关联元素处理“有一个”类型的关系。比如 , 在我们的示例中 , 一个博客有一个用户。 关联映射就工作于这种结果之上。你指定了目标属性 , 来获取值的列 , 属性的 java 类型 ( 很 多情况下 MyBatis 可以自己算出来 ) , 如果需要的话还有 jdbc 类型 , 如果你想覆盖或获取的 结果值还需要类型控制器。
关联中不同的是你需要告诉 MyBatis 如何加载关联。 MyBatis 在这方面会有两种不同的方式:
嵌套查询:通过执行另外一个 SQL 映射语句来返回预期的复杂类型。
嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集。首先 , 然让我们来查看这个元素的属性。所有的你都会看到 , 它和普通的只由 select 和 resultMap 属性的结果映射不同。
关联的嵌套查询
属性 | 描述 |
column | 来自数据库的类名 , 或重命名的列标签。这和通常传递给 resultSet.getString(columnName) 方法的字 符串是相同的。 column 注 意 : 要 处 理 复 合 主 键 , 你 可 以 指 定 多 个 列 名 通 过 column= ” {prop1=col1,prop2=col2} ” 这种语法来传递给嵌套查询语 句。这会引起 prop1 和 prop2 以参数对象形式来设置给目标嵌套查询语句。 |
select | 另外一个映射语句的 ID, 可以加载这个属性映射需要的复杂类型。获取的 在列属性中指定的列的值将被 传递给目标 select 语句作为参数。表格后面 有一个详细的示例。 select 注 意 : 要 处 理 复 合 主 键 , 你 可 以 指 定 多 个 列 名 通 过 column= ” {prop1=col1,prop2=col2} ” 这种语法来传递 给嵌套查询语 句。这会引起 prop1 和 prop2 以参数对象形式来设置给目标嵌套查询语句。 |
<resultMap id="blogResult" type="Blog"> <association property="author" column="blog_author_id" javaType="Author" select="selectAuthor"/> </resultMap> <select id="selectBlog" parameterType="int" resultMap="blogResult"> SELECT * FROM BLOG WHERE ID = #{id} </select> <select id="selectAuthor" parameterType="int" resultType="Author"> SELECT * FROM AUTHOR WHERE ID = #{id} </select>
我们有两个查询语句 : 一个来加载博客 , 另外一个来加载作者 , 而且博客的结果映射描 述了“ selectAuthor ”语句应该被用来加载它的 author 属性。
其他所有的属性将会被自动加载 , 假设它们的列和属性名相匹配。
这种方式很简单 , 但是对于大型数据集合和列表将不会表现很好。问题就是我们熟知的 “ N+1 查询问题”。概括地讲 ,N+1 查询问题可以是这样引起的 :
你执行了一个单独的 SQL 语句来获取结果列表 ( 就是“ +1 ” ) 。
对返回的每条记录 , 你执行了一个查询语句来为每个加载细节 ( 就是“ N ” ) 。
这个问题会导致成百上千的 SQL 语句被执行。这通常不是期望的。
MyBatis 能延迟加载这样的查询就是一个好处 , 因此你可以分散这些语句同时运行的消耗。然而 , 如果你加载一个列表 , 之后迅速迭代来访问嵌套的数据 , 你会调用所有的延迟加载 , 这样的行为可能是很糟糕的。
所以还有另外一种方法。
关联的嵌套结果
属性 | 描述 |
resultMap | 这是结果映射的 ID, 可以映射关联的嵌套结果到一个合适的对象图中。这 是一种替代方法来调 用另外一个查询语句。这允许你联合多个表来合成到 resultMap 一个单独的结果集。这样的结 果集可能包含重复 , 数据的重复组需要被分 解 , 合理映射到一个嵌套的对象图。为了使它变得容 易 ,MyBatis 让你 “ 链 接 ” 结果映射 , 来处理嵌套结果。一个例子会很容易来仿照 , 这个表格后 面也 有一个示例。 |
columnPrefix | 当加入多个表 , 你将不得不使用列别名 , 以避免重复列名在 ResultSet 。 指定 columnPrefix 允许 您映射到一个外部等栏目 resultMap |
<select id="selectBlog" parameterType="int" resultMap="blogResult"> select B.id as blog_id, B.title as blog_title, B.author_id as blog_author_id, A.id as author_id, A.username as author_username, A.password as author_password, A.email as author_email, A.bio as author_bio from Blog B left outer join Author A on B.author_id = A.id where B.id = #{id} </select>
注意这个联合查询, 以及采取保护来确保所有结果被唯一而且清晰的名字来重命名。 这使得映射非常简单。现在我们可以映射这个结果:
<resultMap id="blogResult" type="Blog"> <id property="id" column="blog_id" /> <result property="title" column="blog_title"/> <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/> </resultMap> <resultMap id="authorResult" type="Author"> <id property="id" column="author_id"/> <result property="username" column="author_username"/> <result property="password" column="author_password"/> <result property="email" column="author_email"/> <result property="bio" column="author_bio"/> </resultMap>
简单来说就是需要关联哪些字段,才去查哪些字段。上面的还可以改为:
<resultMap id="blogResult" type="Blog"> <id property="id" column="blog_id" /> <result property="title" column="blog_title"/> <association property="author" javaType="Author"> <id property="id" column="author_id"/> <result property="username" column="author_username"/> <result property="password" column="author_password"/> <result property="email" column="author_email"/> <result property="bio" column="author_bio"/> </association> </resultMap>
上面的关系都是一对一的情况,下面我们来看一下一对多个的情况
集合的嵌套查询
首先 , 让我们看看使用嵌套查询来为博客加载文章
<resultMap id="blogResult" type="Blog"> <collection property="posts" javaType="ArrayList" column="blog_id" ofType="Post" select="selectPostsForBlog"/> </resultMap> <select id="selectBlog" parameterType="int" resultMap="blogResult"> SELECT * FROM BLOG WHERE ID = #{id} </select> <select id="selectPostsForBlog" parameterType="int" resultType="Blog"> SELECT * FROM POST WHERE BLOG_ID = #{id} </select>
这里你应该注意很多东西 , 但大部分代码和上面的关联元素是非常相似的。首先 , 你应 该注意我们使用的是集合元素。然后要注意那个新的“ property ”属性。这个属性是指 JavaBean (博客类中文章属性集合)中属性类型。 ofType 指的是具体集合中每个元素的类型。所以你可以读出下面这个映射 :
<collection property="posts" javaType="ArrayList" column="blog_id" ofType="Post" select="selectPostsForBlog"/>
读作 : “在 Post 类型的 ArrayList 中的 posts 的集合。”
javaType 属性是不需要的 , 因为 MyBatis 在很多情况下会为你算出来。所以你可以缩短写法 :
<collection property="posts" column="blog_id" ofType="Post" select="selectPostsForBlog"/>
<select id="selectBlog" parameterType="int" resultMap="blogResult"> select B.id as blog_id, B.title as blog_title, B.author_id as blog_author_id, P.id as post_id, P.subject as post_subject, P.body as post_body, from Blog B left outer join Post P on B.id = P.blog_id where B.id = #{id} </select>
<resultMap id="blogResult" type="Blog"> <id property="id" column="blog_id" /> <result property="title" column="blog_title"/> <collection property="posts" ofType="Post"> <id property="id" column="post_id"/> <result property="subject" column="post_subject"/> <result property="body" column="post_body"/> </collection> </resultMap>
同样, 如果你引用更长的形式允许你的结果映射的更多重用, 你可以使用下面这个替代 的映射:
<resultMap id="blogResult" type="Blog"> <id property="id" column="blog_id" /> <result property="title" column="blog_title"/> <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/> </resultMap> <resultMap id="blogPostResult" type="Post"> <id property="id" column="id"/> <result property="subject" column="subject"/> <result property="body" column="body"/> </resultMap>
如果大家学了 Hibernate ,再来看看这个,应该来说还是相对好理解点的。
鉴别器
<discriminator javaType="int" column="draft"> <case value="1" resultType="DraftPost"/> </discriminator>
有时一个单独的数据库查询也许返回很多不同 ( 但是希望有些关联 ) 数据类型的结果集。 鉴别器元素就是被设计来处理这个情况的 , 还有包括类的继承层次结构。鉴别器非常容易理 解 , 因为它的表现很像 Java 语言中的 switch 语句。
定义鉴别器指定了 column 和 javaType 属性。 列是 MyBatis 查找比较值的地方。 JavaType 是需要被用来保证等价测试的合适类型 ( 尽管字符串在很多情形下都会有用 ) 。比如 :
<resultMap id="vehicleResult" type="Vehicle"> <id property="id" column="id" /> <result property="vin" column="vin"/> <result property="year" column="year"/> <result property="make" column="make"/> <result property="model" column="model"/> <result property="color" column="color"/> <discriminator javaType="int" column="vehicle_type"> <case value="1" resultMap="carResult"/> <case value="2" resultMap="truckResult"/> <case value="3" resultMap="vanResult"/> <case value="4" resultMap="suvResult"/> </discriminator> </resultMap>
缓存
MyBatis 包含一个非常强大的查询缓存特性 , 它可以非常方便地配置和定制。 MyBatis 3 中的缓存实现的很多改进都已经实现了 , 使得它更加强大而且易于配置。
默认情况下是没有开启缓存的 , 除了局部的 session 缓存 , 可以增强变现而且处理循环 依赖也是必须的。要开启二级缓存 , 你需要在你的 SQL 映射文件中添加一行 :
<cache/>
字面上看就是这样。这个简单语句的效果如下 :
映射语句文件中的所有 select 语句将会被缓存。
映射语句文件中的所有 insert,update 和 delete 语句会刷新缓存。
缓存会使用 Least Recently Used(LRU, 最近最少使用的 ) 算法来收回。
根据时间表 ( 比如 no Flush Interval, 没有刷新间隔 ), 缓存不会以任何时间顺序 来刷新。
缓存会存储列表集合或对象 ( 无论查询方法返回什么 ) 的 1024 个引用。
缓存会被视为是 read/write( 可读 / 可写 ) 的缓存 , 意味着对象检索不是共享的 , 而 且可以安全地被调用者修改 , 而不干扰其他调用者或线程所做的潜在修改。
所有的这些属性都可以通过缓存元素的属性来修改。比如 :
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存 , 并每隔 60 秒刷新 , 存数结果对象或列表的 512 个引用 , 而且返回的对象被认为是只读的 , 因此在不同线程中的调用者之间修改它们会 导致冲突。
可用的收回策略有(默认的是 LRU ) :
LRU – 最近最少使用的 : 移除最长时间不被使用的对象。
FIFO – 先进先出 : 按对象进入缓存的顺序来移除它们。
SOFT – 软引用 : 移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用 : 更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval( 刷新间隔 ) 可以被设置为任意的正整数 , 而且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置 , 也就是没有刷新间隔 , 缓存仅仅调用语句时刷新。
size( 引用数目 ) 可以被设置为任意正整数 , 要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是 1024 。
readOnly( 只读 ) 属性可以被设置为 true 或 false 。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝 ( 通过序列化 ) 。这会慢一些 , 但是安全 , 因此默认是 false 。
使用自定义缓存
除了这些自定义缓存的方式 , 你也可以通过实现你自己的缓存或为其他第三方缓存方案 创建适配器来完全覆盖缓存行为。
<cache type="com.domain.something.MyCustomCache"/>
这个示例展示了如何使用一个自定义的缓存实现。 type 属性指定的类必须实现 org.mybatis.cache.Cache 接口。这个接口是 MyBatis 框架中很多复杂的接口之一 , 但是简单 给定它做什么就行。
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
ReadWriteLock getReadWriteLock();
}
要配置你的缓存 , 简单和公有的 JavaBeans 属性来配置你的缓存实现 , 而且是通过 cache 元素来传递属性 , 比如 , 下面代码会在你的缓存实现中调用一个称为 “ setCacheFile(String file) ” 的方法 :
<cache type="com.domain.something.MyCustomCache"> <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/> </cache>
你可以使用所有简单类型作为 JavaBeans 的属性 ,MyBatis 会进行转换。
记得缓存配置和缓存实例是绑定在 SQL 映射文件的命名空间是很重要的。因此 , 所有 在相同命名空间的语句正如绑定的缓存一样。 语句可以修改和缓存交互的方式 , 或在语句的 语句的基础上使用两种简单的属性来完全排除它们。默认情况下 , 语句可以这样来配置 :
<select ... flushCache="false" useCache="true"/> <insert ... flushCache="true"/> <update ... flushCache="true"/> <delete ... flushCache="true"/>
因为那些是默认的 , 你明显不能明确地以这种方式来配置一条语句。相反 , 如果你想改 变默认的行为 , 只能设置 flushCache 和 useCache 属性。比如 , 在一些情况下你也许想排除 从缓存中查询特定语句结果 , 或者你也许想要一个查询语句来刷新缓存。相似地 , 你也许有 一些更新语句依靠执行而不需要刷新缓存。