resultType属性非常有用,但在返回结果类型比较复杂的情况下却无能为力,为此,MyBatis在select元素中还为我们提供了另外一个resultMap属性,用于引用一个使用<resultMap/>元素定义好的自定义结果集映射规则。
<resultMap/>算是MyBatis中最重要、最强大的元素了,它可以让你比使用JDBC调用结果集省掉90%的代码,也可以让你做许多JDBC不支持的事。 事实上,编写类似的对复杂语句联合映射的代码也许会需要上千行。resultMap的设计就是简单语句不需要明确的结果映射,而很多复杂语句确实需要描述它们的关系。
resultMap子元素:
<!---用来将查询结果作为参数注入到实例的构造方法中->
<constructor>
<!--标记结果作为 ID-->
<idArg />
<!--标记结果作为普通参数-->
<arg />
</constructor>
<!--一个ID结果,标记结果作为 ID-->
<id/>
<!--一个普通结果,JavaBean的普通属性或字段-->
<result/>
<!--关联其他的对象-->
<association></association>
<!--关联其他的对象集合-->
<collection></collection>
<!--鉴别器,根据结果值进行判断,决定如何映射-->
<discriminator>
<!--结果值的一种情况,将对应一种映射规则-->
<case></case>
</discriminator>
在一个<resultMap/>元素中,这些子元素出现的先后顺序是有严格规定的,它们从前到后依次是:constructor-->id --> result--> association-->collection -->discriminator。
id & result
<id/>和<result/>是resultMap中最基本的映射内容,它们都可以将查询结果中一个单独列的值映射为返回结果对象中的简单数据类型(字符串,整型,双精度浮点数,日期等)的单独属性或字段。这两者之间的唯一不同是id 表示的结果将是当比较对象实例时用到的标识属性。这帮助来改进整体表现,特别是缓存和嵌入结果映射(也就是联合映射) 。
<!-- 返回javaBean对象 -->
<resultMap type="User" id="userResultMap">
<id column="USER_ID" property="userId" />
<result column="USER_NAME" property="userName" />
<result column="USER_PASS" property="passWord" />
<result column="USER_GENDER" property="gender" />
</resultMap>
<!-- 返回Map对象 -->
<resultMap type="map" id="userResultMap">
<id column="USER_ID" property="userId" javaType="long"/>
<result column="USER_NAME" property="userName" javaType="string"/>
<result column="USER_PASS" property="passWord" javaType="string"/>
<result column="USER_GENDER" property="gender" javaType="string"/>
</resultMap>
<id/>和<result/>都支持以下几个属性:
属性 | 描述 |
column | 从数据库中查询到的结果集的列名或列别名 |
property | 将 column属性指定的列结果映射到对象的哪个属性 |
javaType | 列结果在对象中所对应的Java数据类型,返回类型为Map时需指定 |
jdbcType | 列结果在数据库中所支持的JDBC 类型 |
typeHandler | 所使用的类型处理器 |
为了未来的参考,MyBatis 通过包含的 jdbcType 枚举型,支持下面的 JDBC 类型。
BIT | FLOAT | CHAR | TIMESTAMP | OTHER | UNDEFINED |
TINYINT | REAL | VARCHAR | BINARY | BLOB | NVARCHAR |
SMALLINT | DOUBLE | LONGVARCHAR | VARBINARY | CLOB | NCHAR |
INTEGER | NUMERIC | DATE | LONGVARBINARY | BOOLEAN | NCLOB |
BIGINT | DECIMAL | TIME | NULL | CURSOR | ARRAY |
constructor
使用<constructor/>元素可以在对象实例化时,将查询到结果集作为参数注入到实例的构造方法中,以实现对实例化对象的特殊处理。
例如,为了防止由于数据库user表中的年龄字段长期未更新,而导致查询出来的年龄信息不准确,我们希望查询user信息时根据用户的生日计算出用户的年龄,而不再直接从数据库中获取用户的年龄信息。
首先在用户实体类User.java中增加如下构造方法:
private static final SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
public User(Long userId, Date birthday) {
super();
this.userId = userId;
if (null != birthday) {
this.birthday = birthday;
int startYear = Integer.parseInt(yearFormat.format(birthday));
int nowYear = Integer.parseInt(yearFormat.format(new Date()));
this.age = nowYear - startYear;
}
}
相应的Mapper配置如下:
<resultMap type="User" id="userResultMap">
<constructor>
<idArg column="USER_ID" javaType="java.lang.Long" />
<arg column="USER_BIRTHDAY" javaType="java.util.Date" />
</constructor>
<id column="USER_ID" property="userId" />
<result column="USER_NAME" property="userName" />
<result column="PASS_WORD" property="userGender" />
</resultMap>
<select id="selectUserById" resultMap="userResultMap">
select * from t_user where USER_ID = #{userId}
</select>
<constructor/>元素的<idArg/>和<arg/>除了支持示例中的column和javaType属性,还支持以下几个属性:jdbcType、typeHandler、select、resultMap、name,其中的name属性从3.4.3版本起开始引入,用来指定该在构造方法中形参的名字。
注意:使用<constructor/>元素在构造方法中初始化的参数若使用resultMap的其他子元素进行映射,则构造方法为对象实例赋予的属性值将被覆盖。
association
<association/>元素用于处理查询结果中关联其他对象的情况。
比如:一个用户属于一个部门,我们在查询一个用户的信息时,想要把他所在的部门也一并查出。
public class User{
private Long userId; //用户id
private String userName; //用户名称
.... //省略一些属性
private Dept dept; //所属部门
//此处省略get和set方法
}
MyBatis提供了两种不同的方式用来处理这种对象关联查询:嵌套查询和嵌套结果。
嵌套查询
所谓嵌套查询,指的是通过执行另外一个 SQL 映射语句来返回所关联的对象类型。
<association/>元素可以通过columnPrefix属性可以去除查询结果内列名的指定前缀。
<resultMap type="User" id="userResultMap">
<id column="USER_ID" property="userId" />
<result column="USER_NAME" property="userName" />
<association column="DEPT_ID" property="dept" select="selectDeptById" javaType="Dept" />
</resultMap>
<select id="selectUserById" resultMap="userResultMap">
select * from t_user where USER_ID = #{userId}
</select>
<resultMap type="Dept" id="deptResultMap">
<id column="DEPT_ID" property="deptId" />
<result column="DEPT_NAME" property="deptName" />
</resultMap>
<select id="selectDeptById" resultMap="deptResultMap">
select * from t_dept where DEPT_ID = #{deptId}
</select>
嵌套结果
所谓嵌套结果,指的是通过联表查询将所需的所有字段内容先查询出来,再通过级联属性映射来创建复杂类型的结果对象。
<resultMap type="User" id="userResultMap">
<id column="USER_ID" property="userId" />
<result column="USER_NAME" property="userName" />
<result column="DEPT_ID" property="dept.deptId" />
<result column="DEPT_NAME" property="dept.deptName" />
</resultMap>
<sql id="userAndDeptFields">
u.USER_ID AS USER_ID,
u.USER_NAME AS USER_NAME,
d.DEPT_ID AS DEPT_ID,
d.DEPT_NAME AS DEPT_NAME
</sql>
<select id="selectUserById" resultMap="userResultMap">
SELECT
<include refid="userAndDeptFields"></include>
FROM t_user u LEFT OUTER JOIN t_dept d ON u.DEPT_ID = d.DEPT_ID
WHERE
u.USER_ID = #{userId}
</select>
此时的resultMap还可以写为如下两种形式。
<resultMap type="User" id="userResultMap">
<id column="USER_ID" property="userId" />
<result column="USER_NAME" property="userName" />
<association property="dept" javaType="Dept">
<id column="DEPT_ID" property="deptId" />
<result column="DEPT_NAME" property="deptName" />
</association>
</resultMap>
<resultMap type="User" id="userResultMap">
<id column="USER_ID" property="userId" />
<result column="USER_NAME" property="userName" />
<association property="dept" resultMap="deptResultMap"> </association>
</resultMap>
<resultMap type="Dept" id="deptResultMap">
<id column="DEPT_ID" property="deptId" />
<result column="DEPT_NAME" property="deptName" />
</resultMap>
collection
<collection/>元素用于处理查询结果中关联其他对象集合的情况,比如:一个部门包含多个员工,我们在查询一个部门的信息时,想要把这个部门里的员工集合也一并查出。
部门实体:
public class Dept{
private Long deptId; //部门id
private String deptName; //部门名称
...
private List<User> users; //部门下用户集合
//此处省略get和set方法
}
嵌套查询
<resultMap type="Dept" id="deptResultMap">
<id column="DEPT_ID" property="deptId" />
<result column="DEPT_NAME" property="deptName" />
<collection column="DEPT_ID" property="users" javaType="list" select="userListByClass"> </collection>
</resultMap>
<resultMap type="User" id="userResultMap">
<id column="USER_ID" property="userId" />
<result column="USER_NAME" property="userName" />
</resultMap>
<select id="selectDeptById" resultMap="deptResultMap">
select * from t_dept where DEPT_ID = #{deptId}
</select>
<select id="userListByClass" parameterType="long" resultMap="userResultMap">
select * from t_user where DEPT_ID = #{userId}
</select>
嵌套结果
<resultMap type="Dept" id="deptResultMap">
<id column="DEPT_ID" property="deptId" />
<result column="DEPT_NAME" property="deptName" />
<collection column="DEPT_ID" property="users" javaType="list" ofType="User">
<id column="User_ID" property="userId" />
<result column="USER_NAME" property="userName" />
</collection>
</resultMap>
<sql id="deptAndUsersFields">
d.DEPT_ID AS DEPT_ID,
d.DEPT_NAME AS DEPT_NAME,
u.USER_ID AS USER_ID,
u.USER_NAME AS USER_NAME
</sql>
<select id="selectDeptById" parameterType="long" resultMap="deptResultMap">
SELECT
<include refid="deptAndUsersFields"></include>
FROM t_dept d LEFT JOIN t_user u ON d.DEPT_ID = u.DEPT_ID
WHERE
d.DEPT_ID = #{deptId}
</select>
此时的resultMap也可以写为如下形式。
<resultMap type="Dept" id="deptResultMap">
<id column="DEPT_ID" property="deptId" />
<result column="DEPT_NAME" property="deptName" />
<collection property="users" javaType="list" ofType="User" resultMap="userResultMap"> </collection>
</resultMap>
<resultMap type="User" id="userResultMap">
<id column="USER_ID" property="userId" />
<result column="USER_NAME" property="userName" />
</resultMap>
discriminator
<discriminator/>元素很像Java语言中的switch 语句,允许用户根据查询结果中指定字段的不同取值来执行不同的映射规则,比如:在查询一个部门的员工信息时,如果员工是性别为男则将其年龄信息一并查出,如果学生为女性则不查询其年龄信息而是将部门信息一并查出。
<resultMap type="User" id="usersResultMap">
<discriminator column="USER_GENDER" javaType="string">
<case value="男" resultMap="maleResultMap"/>
<case value="女" resultMap="femaleResultMap"/>
</discriminator>
</resultMap>
<resultMap type="User" id="maleResultMap">
<id column="USER_ID" property="userId" />
<result column="USER_NAME" property="userName" />
<result column="USER_AGE" property="userAge" />
</resultMap>
<resultMap type="User" id="femaleResultMap">
<id column="USER_ID" property="userId" />
<result column="USER_NAME" property="userName" />
<association column="DEPT_ID" property="dept" javaType="Dept">
<id column="DEPT_ID" property="deptId" />
<result column="DEPT_NAME" property="deptName" />
</association>
</resultMap>
此时的resultMap也可以写为如下形式。
<resultMap type="User" id="usersResultMap">
<id column="USER_ID" property="userId" />
<result column="USER_NAME" property="userName" />
<discriminator column="USER_GENDER" javaType="string">
<case value="男" resultType="User">
<result column="USER_AGE" property="userAge" />
</case>
<case value="女" resultType="User">
<association column="DEPT_ID" property="userDept" javaType="Dept">
<id column="DEPT_ID" property="deptId" />
<result column="DEPT_NAME" property="deptName" />
</association>
</case>
</discriminator>
</resultMap>