MyBatis Mapper XML 文件 02

高级结果映射

下面我们来看看官方文档上提供的 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 元素的概念视图

resultMap

·         constructor  - 类在实例化时 , 用来注入结果到构造方法中

·         idArg  - ID 参数 ; 标记结果作为 ID 可以帮助提高整体效能

·         arg  - 注入到构造方法的一个普通结果

·         id  – 一个 ID 结果 ; 标记结果作为 ID 可以帮助提高整体效能

扫描二维码关注公众号,回复: 645856 查看本文章

·         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 属性。比如 , 在一些情况下你也许想排除 从缓存中查询特定语句结果 , 或者你也许想要一个查询语句来刷新缓存。相似地 , 你也许有 一些更新语句依靠执行而不需要刷新缓存。

猜你喜欢

转载自xdwangiflytek.iteye.com/blog/1749251