resultMap结果映射集详解
resultmap是mybatis中最复杂的元素之一,它描述如何从结果集中加载对象,主要作用是定义映射规则、级联的更新、定制类型转化器。
resultmap构成元素
元素 | 子元素 | 作用 |
constructor | idArg 、arg | 用于配置构造器方法 |
id | 将结果集标记为id,以方便全局调用 | |
result | 配置POJO到数据库列名映射关系 | |
association | 级联使用 | 代表一对一关系 |
collection | 级联使用 | 代表一对多关系 |
discriminator | 级联使用 | 鉴别器 根据实际选择实例,可以通过特定条件确定结果集 |
1.id和result元素
以User类为例:
class User{
private int userId;
private String name;
public User(long userId,String name){
this.userId = userId;
this.name = name;
}
}
id、result是最简单的映射,id为主键映射;result其他基本数据库表字段到实体类属性的映射。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>
id、result语句属性配置细节:
属性 | 描述 |
property | 需要映射到JavaBean 的属性名称。 |
column | 数据表的列名或者标签别名。 |
javaType | 一个完整的类名,或者是一个类型别名。如果你匹配的是一个JavaBean,那MyBatis 通常会自行检测到。然后,如果你是要映射到一个HashMap,那你需要指定javaType 要达到的目的。 |
jdbcType | 数据表支持的类型列表。这个属性只在insert,update 或delete 的时候针对允许空的列有用。JDBC 需要这项,但MyBatis 不需要。如果你是直接针对JDBC 编码,且有允许空的列,而你要指定这项。 |
typeHandler | 使用这个属性可以覆写类型处理器。这项值可以是一个完整的类名,也可以是一个类型别名。 |
2. CONSTRUCTOR 构造器
在resultMap中,通常使用id、result子元素把Java实体类的属性映射到数据库表的字段上。但是如果在遇到JavaBean没有无参构造函数时,我还需要使用构造器元素实现一个JavaBean的实例化和数据注入。
再以User为例,那么对应的resultmap就需要添加构造器:
<constructor>
<idArg column="user_id" javaType="long"/>
<arg column="name" javaType="String"/>
</constructor>
定义java实体类的属性映射到数据库表的字段上。我们也可以使用实体类的构造方法来实现值的映射,这是通过构造方法参数的书写的顺序来进行赋值的。
这样MyBatis就知道需要用这个构造方法构造了。
3.结果集处理方法
1. 使用map储存结果集
一般情况下,所有select语句都可以使用map储存,但是使用map就意味着可读性的下井,所以这不是推荐的方式。
<select id="findUserById" parameterType="long" resultType="map">
select user_id ,name form user where user_id=#{userId}
</select>
2. 使用POJO储存结果集(推荐)
一般我们都使用POJO储存查询结果。我们可以使用select自动映射,还可以使用select语句中的resultMap属性配置映射集合,不过需要提前定义resultMap。
那我们就可以将之前的select语句修改:
<select id="findUserById" parameterType="long" resultMap="userMap">
select user_id ,name form user where user_id=#{userId}
</select>
4.级联
在数据库中包含着一对多、一对一的关系。比如说一个人和他的身份证就是一对一的关系,但是他和他的银行卡就是一对多的关系。我们的生活中存在着很多这样的场景。我们也希望在获取这个人的信息的同时也可以把他的身份证信息一同查出,这样的情况我们就要使用级联。在级联中存在3种对应关系。
- 一对一的关系
- 一对多的关系
- 多对多的关系(这种情况由于比较复杂,我们通常会使用双向一对多的关系来降低复杂度)
1.association 一对一级联
我们继续使用User类,同时为其增加一个Card类,人和他的身份证就行成了一对一的关系。我们再创建一个Card类。
@Data
@Alias("card")
public class Card {
private Long id;
private Long userId;
private String name;
private String address;
}
这是需要在User中添加属性Card,这样就形成了一对一的级联。
@Data
@Alias("user")
public class User {
private Long id;
private String username;
private String password;
private String email;
private Card card;
}
这里使用了Lombok的Data注解省去了get和set等方法。使用Alias注解添加其别名。
Lombok 是一种 Java™ 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO)。它通过注解实现这一目的。以后会添加其使用方法的文章。
这时需要CardMapper提供findCardByUserId(Long userId)方法,定义其映射器如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<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"/>
</resultMap>
<select id="findCardByUserId" parameterType="long" resultMap="cardMap">
SELECT id, user_id, name, address FROM card WHERE user_id = #{userId}
</select>
</mapper>
有了CardMapper,我们将可以在UserMaper中使用 findCardByUserId
进行级联
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.changzhen.mybatis.mapper.UserMapper">
<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>
注意在getUser的时候,一定要将数据库的id获取到,开始我以为在传入id的时候就已经把id传入findCardByUserId的userId了,结果怎么都获取不到Card。后来发现原来是根据返回的id进而级联查询。
通过关联处理,其中select元素指定的sql查询,而column则是指定传递给select的参数,是user对象id。当取出User的时候,MyBatis就知道下面的sql取出我们需要的级联信息。
<association property="card" column="id" select="com.changzhen.mybatis.mapper.CardMapper.findCardByUserId"/>
其中参数是User的值,通过column配置,如果是多个参数则使用逗号隔开。
通过测试
可以看出执行了两条sql分别查询了User和Card。
2.collection 一对多级联
这一对多的级联,一个身份证可以办理多张银行卡。每个用户对应一个身份证,每个身份证对应多个银行卡。所以这两个级联,分别使用association和collection完成。
- 首先,创建BankCard类,为Card增加bankCardList属性
@Alias("bankCard")
@Data
public class BankCard {
private Long id;
private Long userId;
private String bankName;
private int type;
}
@Data
@Alias("card")
public class Card {
private Long id;
private Long userId;
private String name;
private String address;
private List<BankCard> bankCards;
}
-
创建BankCardMapper
@Mapper
public interface BankCardMapper {
public List findCreditCardsByUserId(Long userId);
} -
xml最简单的映射器
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.changzhen.mybatis.mapper.CreditCardMapper">
<select id="findCreditCardsByUserId" parameterType="long" resultType="creditCard">
SELECT id, user_id, bank_name, type FROM bank_card WHERE user_id = #{userId}
</select>
</mapper>
- 最后在Card映射器中添加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>
-通过测试
可以看出共执行了三条sql语句。
3.discriminator 鉴别器级联
鉴别器级联是在不公情况下使用不同的POJO。例如,在本例中我们每个人都有好多银行卡,但是银行卡的种类却不同,比如有借记卡(DebitCard)和信用卡(CreditCard),我们需要按需来创建不同的POJO。这时我们就需要在BankCard中添加types属性进行判断,确定使用哪个POJO。
接下来需要创建DebitCard和CreditCard两个继承BankCard的子类
@Data
public class CreditCard extends BankCard {
/**
* 消费额度
*/
private String creditLine;
}
@Data
public class DebitCard extends BankCard {
/**
* 存款金额
*/
private String deposit;
}
通过BankCard中types的属性判断使用哪种银行卡,例如types等于1为借记卡,等于2为信用卡。接下来需要修改bankCard.xml,添加discriminator鉴别器,它相当于java中的switch语句。
bankCard.xml修改为如下:
<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"/>
其中
<discriminator javaType="int" column="types">
<case value="1" resultMap="debitCardMapper"></case>
<case value="2" resultMap="creditCardMapper"></case>
</discriminator>
首先定义了 discriminator ,它对应的column为types,java类型(jdbcType)为int。
case配置了不同的resultMap,如value=1时,引入debitCardMapper,value=2时
,引入creditCardMapper他们都扩展了bankCardMapper。
就像继承关系一样,resultMap也可以继承,加入自己的属性。借记卡添加deposit属性,信用卡添加creditLine属性。
-通过运行测试
由图可以看出,返回的数据已经根据types使用了不同的POJO。