mybatis学习笔记(四)

高级结果映射
MyBatis创建的一个想法:数据库不用永远是你想要的或需要它们是什么样的。而我们最喜欢的数据库最好是第三范式或BCNF范式,但它们有时不是。
我们将要如何映射以下语句呢?
<!-- 非常复杂的语句 -->
<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>

注:B.author_id = A.id 即 blog_author_id = author_id 即:Blog的author_id是外键,Author的id是主键 --> blog_author_id主键,author_id外键
B.id = P.blog_id 即 blog_id=post_blog_id 即:Blog的id是主键,Post的blog_id是外键 --> blog_id主键,post_blog_id外键
P.id = C.post_id 即 post_id = comment_post_id 即:Post的id是主键,Comment的post_id是外键 --> post_id主键,comment_post_id外键

......

<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
	<constructor>
		<idArg column="blog_id" javaType="int"/>
	</constructor>
	<result property="title" column="blog_title"/>
	<association property="author" column="blog_author_id" 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" column="post_author_id" javaType="Author"/>
		<collection property="comments" column="post_id" ofType=" Comment">
			<id property="id" column="comment_id"/>
		</collection>
		<collection property="tags" column="post_id" 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可以帮助提高整体效能
result – 注入到字段或JavaBean属性的普通结果
association – 一个复杂的类型关联;许多结果将包成这种类型
嵌入结果映射 – 结果映射自身的关联,或者参考一个
collection – 复杂类型的集
嵌入结果映射 – 结果映射自身的集,或者参考一个
discriminator – 使用结果值来决定使用哪个结果映射
case – 基于某些值的结果映射
嵌入结果映射 – 这种情形结果也映射它本身,因此可以包含很多相同的元素,或者它可以参照一个外部的结果映射。

下面一部分将详细说明每个元素。

id,result

<resultMap>
	<id property="id" column="post_id"/>
	<result property="subject" column="post_subject"/>
</resultMap>
这些是结果映射最基本内容。id和result都映射一个单独列的值到简单数据类型(字符串,整型,双精度浮点数,日期等)的单独属性或字段。

这两者之间的唯一不同是id表示的结果将是当比较对象实例时用到的标识属性。这帮助来改进整体表现,特别是缓存和嵌入结果映射(也就是联合映射)。

属性

描述

property

映射到列结果的字段或属性。如果匹配的是存在的,和给定名称相同的JavaBeans的属性,那么就会使用。否则MyBatis将会寻找给定名称的字段。这两种情形你可以使用通常点式的复杂属性导航。比如,你可以这样映射一些东西:“username”,或者映射到一些复杂的东西:“address.street.number”。

column

从数据库中得到的列名,或者是列名的重命名标签。这也是通常和会传递给resultSet.getString(columnName)方法参数中相同的字符串。

javaType

一个Java类的完全限定名,或一个类型别名(参加上面内建类型别名的列表)。如果你映射到一个JavaBean,MyBatis通常可以断定类型。然而,如果你映射到的是HashMap,那么你应该明确地指定javaType来保证所需的行为。

jdbcType

在这个表格之后的所支持的JDBC类型列表中的类型。JDBC类型是仅仅需要对插入,更新和删除操作可能为空的列进行处理。这是JDBC的需要,而不是MyBatis的。如果你直接使用JDBC编程,你需要指定这个类型-但仅仅对可能为空的值。

typeHandler

我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。这个属性值是类的完全限定名或者是一个类型处理器的实现,或者是类型别名。


构造方法

<constructor>
	<idArg column="id" javaType="int"/>
	<arg column=”username” javaType=”String”/>
</constructor>

对于大多数数据传输对象(Data Transfer Object,DTO)类型,属性可以起作用,而且像绝大多数的领域模型,指令也许是你想使用一成不变的类的地方。通常包含引用或查询数据的表很少或基本不变的话对一成不变的类来说是合适的。构造方法注入允许你在初始化时为类设置属性的值,而不用暴露出公有方法。MyBatis也支持私有属性和私有JavaBeans属性来达到这个目的,但是一些人更青睐构造方法注入。Constructor(构造方法)元素支持这个。

public class User {
	//…
	public User(int id, String username) {
		//…
	}
	//…
}
为了向这个构造方法中注入结果,MyBatis需要通过它的参数的类型来标识构造方法。Java没有自查(或反射)参数名的方法。所以当创建一个构造方法元素时,保证参数是按顺序排列的,而且数据类型也是确定的。

<constructor>
	<idArg column="id" javaType="int"/>
	<arg column=”username” javaType=”String”/>
</constructor>
剩余的属性和规则和固定的id和result元素是相同的。

关联

<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在这方面会有两种不同的方式:
(1) 嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型。
(2) 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集。
首先,然让我们来查看这个元素的属性。

属性

描述

property

映射到列结果的字段或属性。如果匹配的是存在的,和给定名称相同的JavaBeans的属性,那么就会使用。否则MyBatis将会寻找给定名称的字段。这两种情形你可以使用通常点式的复杂属性导航。比如,你可以这样映射一些东西:“username”,或者映射到一些复杂的东西:“address.street.number”。

column

来自数据库的列名,或重命名的列标签。这和通常传递给resultSet.getString(columnName)方法的字符串是相同的。

注意:要处理复合主键,你可以指定多个列名通过column=”{prop1=col1,prop2=col2}”这种语法来传递给嵌套查询语句。这会引起prop1和prop2以参数对象形式来设置给目标嵌套查询语句。

javaType

一个Java类的完全限定名,或一个类型别名(参加上面内建类型别名的列表)。如果你映射到一个JavaBean,MyBatis通常可以断定类型。然而,如果你映射到的是HashMap,那么你应该明确地指定javaType来保证所需的行为。

jdbcType

在这个表格之前的所支持的JDBC类型列表中的类型。JDBC类型是仅仅需要对插入,更新和删除操作可能为空的列进行处理。这是JDBC的需要,而不是MyBatis的。如果你直接使用JDBC编程,你需要指定这个类型-但仅仅对可能为空的值。

typeHandler

我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。这个属性值是类的完全限定名或者是一个类型处理器的实现,或者是类型别名。


关联的嵌套查询

select

另外一个映射语句的ID,可以加载这个属性映射需要的复杂类型。获取的在列属性中指定的列的值将被传递给目标select语句作为参数。表格后面有一个详细的示例。

注意:要处理复合主键,你可以指定多个列名通过column={prop1=col1,prop2=col2}这种语法来传递给嵌套查询语句。这会引起prop1prop2以参数对象形式来设置给目标嵌套查询语句。

示例:

<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查询问题可以是这样引起的:
(1)你执行了一个单独的SQL语句来获取结果列表(就是“+1”)。
(2)对返回的每条记录,你执行了一个查询语句来为每个加载细节(就是“N”)。

关联的嵌套结果

resultMap

这是结果映射的ID,可以映射关联的嵌套结果到一个合适的对象图中。这是一种替代方法来调用另外一个查询语句。这允许你联合多个表来合成到一个单独的结果集。这样的结果集可能包含重复,数据的重复组需要被分解,合理映射到一个嵌套的对象图。为了使它变得容易,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,
		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=”blog_id” column="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>
非常重要:在嵌套结果映射中id元素扮演了非常重要的角色。应该通常指定一个或多个属性,它们可以用来唯一标识结果。实际上就是如果你不使用它(id元素),但是会产生一个严重的性能问题,不过MyBatis仍然可以正常工作。选择的属性越少越好,它们可以唯一地标识结果。主键就是一个显而易见的选择(即便是联合主键)。
现在,上面的示例用了外部的结果映射元素来映射关联。这使得Author结果映射可以重用。然而,如果你不需要重用它的话,或者你仅仅引用你所有的结果映射合到一个单独描述的结果映射中。你可以嵌套结果映射。这里给出使用这种方式的相同示例:

<resultMap id="blogResult" type="Blog">
	<id property=”blog_id” column="id" />
	<result property="title" column="blog_title"/>
	<association property="author" column="blog_author_id"
		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>


集合

<collection property="posts" ofType="domain.blog.Post">
	<id property="id" column="post_id"/>
	<result property="subject" column="post_subject"/>
	<result property="body" column="post_body"/>
</collection>
集合元素的作用几乎和关联是相同的。实际上,它们也很相似。
我们来继续上面的示例,一个博客只有一个作者。但是博客有很多文章。在博客类中,这可以由下面这样的写法来表示:
private List<Post> posts;
要映射嵌套结果集合到List中,我们使用集合元素。就像关联元素一样,我们可以从连接中使用嵌套查询,或者嵌套结果。

集合的嵌套查询
首先,让我们看看使用嵌套查询来为博客加载文章。
<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="Author">
	SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
首先,你应该注意我们使用的是集合元素。然后要注意那个新的“ofType”属性。这个属性用来区分JavaBean(或字段)属性类型和集合包含的类型来说是很重要的。所以你可以读出下面这个映射:
<collection property="posts" javaType=”ArrayList” column="blog_id"
	ofType="Post" select=”selectPostsForBlog”/>
读作:“在Post类型的ArrayList中的posts的集合。”

集合的嵌套结果
<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>
要记得id元素的重要性。
如果你引用更长的形式允许你的结果映射的更多重用,你可以使用下面这个替代的映
<resultMap id="blogResult" type="Blog">
	<id property=”id” column="blog_id" />
	<result property="title" column="blog_title"/>
	<collection property="posts" ofType="Post" resultMap=”blogPostResult”/>
</resultMap>
<resultMap id="blogPostResult" type="Post">
	<id property="id" column="post_id"/>
	<result property="subject" column="post_subject"/>
	<result property="body" column="post_body"/>
</resultMap>

鉴别器
<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>
<resultMap id="carResult" type="Car">
	<result property=”doorCount” column="door_count" />
</resultMap>
上面示例中,MyBatis会从结果集中得到每条记录,然后比较它的vehicle类型的值。如果它匹配任何一个鉴别器的实例,那么就使用这个实例指定的结果映射。
还有另外一种语法来做简洁的映射风格。比如:
<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" resultType="carResult">
			<result property=”doorCount” column="door_count" />
		</case>
		<case value="2" resultType="truckResult">
			<result property=”boxSize” column="box_size" />
			<result property=”extendedCab” column="extended_cab" />
		</case>
		<case value="3" resultType="vanResult">
			<result property=”powerSlidingDoor” column="power_sliding_door" />
		</case>
		<case value="4" resultType="suvResult">
			<result property=”allWheelDrive” column="all_wheel_drive" />
		</case>
	</discriminator>
</resultMap>




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值