一、背景描述
需求:查询任务逾期记录时,把任务相关信息查询出来;
表设计:任务相关信息是一张表(uoa_assignment),任务逾期记录是一张表(uoa_assignment_overdue_log);外键是任务主键(id);
JavaBean:class Assignment, class AssignmentOverdueLog; class AssignmentOverdueLog中包含class Assignment对象。
目的:在查询任务逾期记录时,两张表关联查询出来之后,想要把Assignment的结果集映射到AssignmentOverdueLog中。
两个JavaBean对象如下:AssignmentOverdueLog对象中包含一个Assignment对象。
在查询任务逾期记录时,两张表关联查询出来之后,想要把Assignment的结果集映射到AssignmentOverdueLog中。
二、解决方案
mapper.xml中正确的写法
方法1
写两个<resultMap></resultMap>,通过<association>标签关联起来,然后在查询的sql语句结果集映射中填写外层的<resultMap>的属性id。
在上面的例子中,您会看到AssignmentOverdueLog联合一个“assignmentResultMap”结果映射来加载Assignment实例。
重点提示:id元素在嵌套结果映射中扮演了非常重要的角色,您应该总是指定一个或多个属性来唯一标识这个结果集。事实上,如果您没有那样做,MyBatis也会工作,但是会导致严重性能开销。选择尽量少的属性来唯一标识结果,而使用主键是最明显的选择(即使是复合主键)。
上面的例子使用一个扩展的resultMap 元素来联合映射。这可使Assignment结果映射可重复使用。
然后,如果您不需要重用它,您可以直接嵌套这个联合结果映射。这就是另外一种写法了。
方法2
方法3
上面的例子,首先执行<select id=“queryListByLastMonth”>,执行结果存放到<resultMap id=“assignmentOverdueLogMap”>结果映射中。“assignmentOverdueLogMap”是一个AssignmentOverdueLog类型,从<select id=“queryListByLastMonth”>查出的数据都会自动赋值给”assignmentOverdueLogMap”的与列名匹配的属性,这时assignment_id,responsibleUser,responsibleName等就被赋值了。同时“assignmentOverdueLogMap”还有一个关联属性"assignment",执行嵌套查询select=”getAssignment”后,Assignment对象的属性id,type,transactType,title,description等也被赋于数据库中匹配的值。
我们使用两个select语句:一个用来加载AssignmentOverdueLog,另一个用来加载Assignment。AssignmentOverdueLog的resultMap 描述了使用“getAssignment”语句来加载Assignment的属性。
如果列名和属性名称相匹配的话,所有匹配的属性都会自动加载。
虽然这个方法简单,但是对于大数据集或列表查询,就非常不友好了。此问题被称为“N+1 选择问题”(N+1 Selects Problem)。概括地说,N+1选择问题是这样产生的:
1、您执行单条SQL语句去获取一个列表的记录( “+1”)。
2、对列表中的每一条记录,再执行一个联合select 语句来加载每条记录更加详细的信息(“N”)。
这个问题会导致成千上万的SQL语句的执行,因此并非总是可取的。
上面的例子,MyBatis可以使用延迟加载这些查询,因此这些查询立马可节省开销。然而,如果您加载一个列表后立即迭代访问嵌套的数据,这将会调用所有的延迟加载,因此性能会变得非常糟糕。
鉴于此,这有另外一种方式。联合嵌套结果集(Nested Results for Association),也就是上面的方法1和方法2两种方式。
会用到ResultMap联合嵌套结果集(Nested Results for Association)
ResultMap | 一个可以映射联合嵌套结果集到一个适合的对象视图上的ResultMap 。这是一个替代的方式去调用另一个select 语句。它允许您去联合多个表到一个结果集里。这样的结果集可能包括冗余的、重复的需要分解和正确映射到一个嵌套对象视图的数据组。简言之,MyBatis 让您把结果映射‘链接’到一起,用来处理嵌套结果。也就是上面的方法1和方法2两种方式。 |
在上面的例子中您已经看到如果处理“一对一”(“has one”)类型的联合查询。但是对于“一对多”(“has many”)的情况如果处理呢?这个问题在下一节讨论:(待补充........)。
拓展:
<association property="assignment" javaType="com.iot.uoa.assignment.entity.Assignment"> <result property="id" column="id"/> <result property="type" column="task_type"/> <result property="title" column="title"/> <result property="responsibleUser" column="responsible_user"/> <result property="responsibleName" column="responsible_name"/> <result property="transactType" column="transact_type"/> <result property="description" column="description"/> <result property="taskLevel" column="task_level"/> </association>
Association元素处理“has-one”(一对一)这种类型关系。比如在我们的例子中,一个AssignmentOverdueLog有一个Assignment。联合映射与其它的结果集映射工作方式差不多,指定property、column、javaType(通常MyBatis会自动识别)、jdbcType(如果需要)、typeHandler。
不同的地方是您需要告诉MyBatis 如何加载一个联合查询。MyBatis使用两种方式来加载:
Select:通过执行另一个返回预期复杂类型的映射SQL语句(即引用外部定义好的SQL语句块)。
Results:通过嵌套结果映射(nested result mappings)来处理联接结果集(joined results)的重复子集。
首先,让我们检查一下元素属性。正如您看到的,它不同于普通只有select和resultMap属性的结果映射。
属性 | 描述 |
property | 映射数据库列的字段或属性。如果JavaBean 的属性与给定的名称匹配,就会使用匹配的名字。否则,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类型。这个属性只在insert,update 或delete 的时候针对允许空的列有用。JDBC 需要这项,但MyBatis 不需要。如果您直接编写JDBC代码,在允许为空值的情况下需要指定这个类型。 |
typeHandler | 我们已经在文档中讨论过默认类型处理器。使用这个属性可以重写默认类型处理器。它的值可以是一个TypeHandler实现的完整类名,也可以是一个类型别名。 |
显示详细信息
联合嵌套选择(Nested Select for Association)
select | 通过这个属性,通过ID引用另一个加载复杂类型的映射语句。从指定列属性中返回的值,将作为参数设置给目标select 语句。表格下方将有一个例子。注意:在处理组合键时,您可以使用column=”{prop1=col1,prop2=col2}”这样的语法,设置多个列名传入到嵌套语句。这就会把prop1和prop2设置到目标嵌套语句的参数对象中。 |
完结!