引用:http://xdwangiflytek.iteye.com/blog/1749251
高级结果映射
下面我们来看看官方文档上提供的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 PPT.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 和 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的顺序问题,不能颠倒。
关联
- <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 在很多情况下会为你算出来。所以你可以缩短写法:
- <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 映射文件中添加一行:
字面上看就是这样。这个简单语句的效果如下:
映射语句文件中的所有 select 语句将会被缓存。
映射语句文件中的所有insert,update 和 delete 语句会刷新缓存。
缓存会使用 Least RecentlyUsed(LRU,最近最少使用的)算法来收回。
根据时间表(比如 no FlushInterval,没有刷新间隔), 缓存不会以任何时间顺序来刷新。
缓存会存储列表集合或对象(无论查询方法返回什么)的 1024 个引用。
缓存会被视为是 read/write(可读/可写)的缓存,意味着对象检索不是共享的,而 且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
所有的这些属性都可以通过缓存元素的属性来修改。比如:
这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。
可用的收回策略有(默认的是 LRU):
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒 形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
readOnly(只读)属性可以被设置为 true或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
使用自定义缓存
除了这些自定义缓存的方式, 你也可以通过实现你自己的缓存或为其他第三方缓存方案创建适配器来完全覆盖缓存行为。
这个示例展示了如何使用一个自定义的缓存实现。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 属性。比如,在一些情况下你也许想排除从缓存中查询特定语句结果,或者你也许想要一个查询语句来刷新缓存。相似地,你也许有一些更新语句依靠执行而不需要刷新缓存。