“resultMap 元素是 MyBatis 中最重要最强大的元素…ResultMap
的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了”
—— MyBatis 官方文档
一、 resultmap是mybatis中最复杂的元素之一,它描述如何从结果集中加载对象,主要作用是定义映射规则、级联更新、定制类型转化器。
元素 | 子元素 | 作用 |
---|---|---|
constructor | idArg、arg | 用于配置构造器方法 |
id | 将结果集标记为id,以方便全局调用 | |
result | 配置pojo到数据库列名映射关系 | |
association | 级联使用 | 代表一对一关系 |
collection | 级联使用 | 代表一对多关系 |
discriminator | 级联使用 | 鉴别器,根据实际选择实例,可以通过特定条件确定结果集 |
1、其中id、result是最简单的映射,id为主键映射;result为其他基本数据库表字段到实体类属性的映射,语句属性配置细节:
属性 | 描述 |
---|---|
property | 需要映射到javabean的属性名称。 |
id | 数据表的列名或者标签别名。 |
javaType | 一个完整的类名,或者是要给类型别名。如果你匹配的是一个JavaBean,那么MyBatis通常会自行检测到。然后,如果你是要映射到一个HashMap,那么需要指定javaType要达到的目的。 |
jdbcType | 数据表支持的类型列表。这个属性只在insert、update、或delete的时候针对允许为空的列有用。jdbc需要这项,但mybaits不需要。如果是针对jdbc编码,且允许有空的列,需要指定这项。 |
typeHandler | 使用这个属性可以覆写类型处理器。这项值可以是一个完整的类名,也可以是一个类型别名。 |
此时resultmap的xml如下:
<resultMap type="User" id="userMap">
<id property="UserId" column="user_id" javaType="int" jdbcType="int"/>
<result property="name" column="name" javaType="String" jdbcType="VARCHAR"/>
</resultMap>
2、一个 resultMap 中常用的写法
<resultMap id="唯一标识" type="映射的entity对象的绝对路径">
<id column="表主键字段" jdbcType="字段类型" property="映射entity对象的主键属性" />
<result column="表某个字段(要注意这个column的值,对应的其实是指实际查询出来的字段,所以如果取了别名要写别名)" jdbcType="字段类型" property="映射entity对象的某个属性"/>
<!-- 指的是entity对象中的对象属性(一般一对一关联查询时使用) -->
<association property="entity中某个对象属性" javaType="这个对象的绝对路径">
<id column="这个对象属性对应的表的主键字段" jdbcType="字段类型" property="这个对象属性内的主键属性"/>
<result column="表某个字段(要注意这个column的值,对应的其实是指实际查询出来的字段,所以如果取了别名要写别名)" jdbcType="字段类型" property="这个对象属性内的某个属性"/>
</association>
<!-- 指的是entity对象中的对象属性(通过单独sql查询时使用,一对一级联) -->
<association property="entity中某个对象属性" column="将这边查询到的字段作为参数传递到新的sql,如id、name等(这里直接填字段名)" select="新的查询sql方法路径,取到方法名"/>
<!-- 指的是entity对象中的集合属性(一般一对多关联查询时使用) -->
<collection property="entity中的某个集合属性" ofType="这个集合泛型所存实体类的绝对路径">
<id column="这个集合属性中泛型所存实体类对象对应表的主键字段" jdbcType="字段类型" property="这个集合属性中泛型所存实体类对象的主键属性"/>
<result column="表某个字段(要注意这个column的值,对应的其实是指实际查询出来的字段,所以如果取了别名要写别名)" jdbcType="字段类型" property="这个集合属性泛型所存实体类对象的属性" />
</collection>
<!-- 这里也是指的是entity对象中的集合属性(通过单独sql查询时使用,一对多级联) -->
<collection property="entity中的某个集合属性" column="将这边查询到的字段作为参数传递到新的sql,如id、name等(这里直接填字段名)" select="新的查询sql方法路径,取到方法名"/>
<!-- 引用另一个resultMap (套娃) -->
<collection property="entity中的某个集合属性" resultMap="这个引用的resultMap的type,就是这个集合属性泛型所存实体类的绝对路径" />
<!-- discriminator鉴别器级联,相当于java中的switch语句,可以看作是通过某字段不同的值返回不同的pojo,就像继承关系一样,resultMap元素也可以继承,加入自己的属性,是对当前resultMap的一个拓展,前提是resultMap1、resultMap2对应的实体都继承了resultMap对应的实体 -->
<discriminator javaType="筛选的字段类型(int、long等)" column="筛选的字段(这里直接填字段名)">
<case value="筛选的字段的值(1、2、3等)" resultMap="引入resultMap1(resultMap1对应的实体继承了resultMap对应的实体)"></case>
<case value="筛选的字段的值(1、2、3等)" resultMap="引入resultMap1(resultMap2对应的实体继承了resultMap对应的实体)"></case>
</discriminator>
</resultMap>
(1)用法部分示例
association和collection都有有两种配置方式:(1)、嵌套数据 (2)、嵌套查询
a、association一对一级联嵌套查询示例如下:
<select id="getUser" resultMap="userMap" parameterType="long">
SELECT id,username,password,email FROM USER WHERE id=#{id}
</select>
<resultMap id="userMap" type="user">
<id property="id" column="id"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
<association property="card" column="id" select="com.changzhen.mybatis.mapper.CardMapper.findCardByUserId"/>
</resultMap>
b、association一对一嵌套数据示例如下:
<resultMap id="ordermap" type="Order">
<id property="id" column="id" />
<result property="user_id" column="user_id" />
<result property="price" column="price" />
<association property="user" javaType="User">
<id property="id" column="uid" />
<result property="username" column="user_name" />
<result property="age" column="age" />
<result property="bir" column="bir" />
</association>
</resultMap>
c、collection一对多嵌套查询示例如下:
<mapper namespace="com.changzhen.mybatis.mapper.CardMapper">
<resultMap id="cardMap" type="card">
<id property="id" column="id"></id>
<result property="userId" column="user_id"/>
<result property="name" column="name"/>
<result property="address" column="address"/>
<collection property="creditCards" column="id" select="com.changzhen.mybatis.mapper.BankCardMapper.findBankCardsByUserId"/>
</resultMap>
<select id="findCardByUserId" parameterType="long" resultMap="cardMap">
SELECT id, user_id, name, address FROM card WHERE user_id = #{userId}
</select>
</mapper>
d、collection一对多嵌套数据示例如下:
<resultMap id="UserRolesMap" type="com.example.server.bean.User">
<id column="u_id" property="userId" jdbcType="INTEGER"/>
<result column="u_name" property="userName" jdbcType="VARCHAR"/>
<result column="password" property="password" jdbcType="VARCHAR"/>
<collection property="roleList" javaType="java.util.List" ofType="com.example.server.bean.Role" >
<id column="r_id" property="roleId" jdbcType="INTEGER"/>
<id column="r_name" property="roleName" jdbcType="VARCHAR"/>
</collection>
</resultMap>
e、discriminator鉴别器级联示例如下:
<mapper namespace="com.changzhen.mybatis.mapper.BankCardMapper">
<resultMap id="bankCardMapper" type="bankCard">
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="bankName" column="bank_name"/>
<result property="types" column="types"/>
<discriminator javaType="int" column="types">
<case value="1" resultMap="debitCardMapper"></case>
<case value="2" resultMap="creditCardMapper"></case>
</discriminator>
</resultMap>
<select id="findBankCardsByUserId" parameterType="long" resultMap="bankCardMapper">
SELECT id, user_id, bank_name, types FROM bank_card WHERE user_id = #{userId}
</select>
<resultMap id="debitCardMapper" type="debitCard" extends="bankCardMapper"/>
<resultMap id="creditCardMapper" type="creditCard" extends="bankCardMapper"/>
</mapper>
3、那么对于实体类中的各个属性使用建议:
主键id则建议使用 <id column="" ../>
一般的字段建议使用 <result column="" ../>
引用了另外某个实体类对象 <association ... />
引用了由某个实体类组成的集合 <collection ... />
二、使用过程中需要注意的地方
1、如果是单表映射,column默认是对应数据库字段pojo属性与数据库字段对应一致时,中可以不用写映射,如果有个别的字段不一致,可以只写不一致的字段
例如:
<resultMap id="usermap" type="User">
<result property="username" column="user_name" />
</resultMap>
<select id="selectUserById" parameterType="integer" resultMap="usermap">
select * from `user` where id = #{value}
</select>
结果:User{id=1, username=‘zhangsan’, age=18, bir=Tue Oct 08 00:00:00 CST 2019}
如果查询的字段起了别名,那么column就是别名
例如:
<resultMap id="usermap" type="User">
<result property="username" column="u"></result>
</resultMap>
<select id="findUserById" parameterType="integer" resultMap="usermap">
select id,user_name u,age,bir from `user` where id = #{value}
</select>
结果:User{id=1, username=‘zhangsan’, age=18, bir=Tue Oct 08 00:00:00 CST 2019}
反例:
<resultMap id="usermap" type="User">
<result property="username" column="user_name"></result>
</resultMap>
<select id="findUserById" parameterType="integer" resultMap="usermap">
select id,user_name u,age,bir from `user` where id = #{value}
</select>
结果:User{id=1, username=‘null’, age=18, bir=Tue Oct 08 00:00:00 CST 2019}
总结:column值实际上是指查询出的字段!默认情况下是数据库字段。
2、如果是多表关联查询(特征就是resultMap中使用association或collection标签),可能会出现两个表某字段一致的情况!需要对column值进行修改
a. 首先多表关联查询时,主表查询出的字段必须要写全(与单表查询不同,多表查询不能省略,即便数据库列名与pojo属性名一致也要写!否则该字段查询出的数据为null!)
(1)也就是说,凡是查询出来的字段都要写,没有查询的字段不需要写,因为都没有查询这个字段,写了也没用。但是一般都写全,因为一个xml文件一般只配置一个(可以配多个),那么不同的查询语句可能查询出来的字段不一致,尽量补全。
(2)单表查询时可以省略一致的字段。
(3)property的值是属性名,属性名不是类里面定义的变量名,而是set/get方法的方法名去掉set/get,然后首字母小写。
例如:
<resultMap id="ordermap" type="Order">
<!-- 字段一定要写全 -->
<id property="id" column="id" />
<result property="user_id" column="user_id" />
<result property="price" column="price" />
<result property="name" column="name" />
<!-- property是指order类中关联的另一个pojo属性(User),user是它的变量名 -->
<!-- javaType一定要写,同理<collection>内的ofType也要写,否则会报空指针异常! -->
<association property="user" javaType="User">
</association>
</resultMap>
反例:
<resultMap id="ordermap" type="Order">
<!-- 少写了name -->
<id property="id" column="id" />
<result property="user_id" column="user_id" />
<result property="price" column="price" />
<!-- property是指order类中关联的另一个pojo属性(User),user是它的变量名 -->
<association property="user" javaType="User">
</association>
</resultMap>
<select id="findOrderAndUser" resultMap="ordermap">
select o.* from `order` o,`user` u where u.id = o.user_id
</select>
List<Order> list = sqlSession.selectList("findOrderAndUser");
for (Order or : list ) {
System.out.println(or);
}
输出结果为:
Order{id=1, user_id=1, name=‘null’, price=2}
Order{id=2, user_id=1, name=‘null’, price=3}
Order{id=3, user_id=2, name=‘null’, price=3}
Order{id=4, user_id=2, name=‘null’, price=4}
b. 同样需要把字段补全,少写一个字段,则该字段就为null!
例如:
<association property="user" javaType="User">
<!-- 这是的property是指User实体内的属性,column是指查询出来的字段,如果有别名就是别名 -->
<id property="id" column="uid" />
<result property="username" column="user_name" />
<result property="age" column="age" />
<result property="bir" column="bir" />
</association>
<select id="findOrderAndUser" resultMap="ordermap">
select ??? from `order` o,`user` u where u.id = o.user_id
</select>
注意:此时两个表关联查询出的所有字段,其中order的id与user表的id字段名一致,输出时需要对其中一个字段取别名,否则值会被覆盖!