大家好,我是王有志。今天给大家带来的是一道来自京东的 MyBatis 面试题:MyBatis是中如何将结果集映射到Java持久化对象?都有哪些方式?有什么区别?
MyBatis 提供了两种实现结果集到 Java 持久化对象的映射方式:
- 自动映射,不显式指定映射关系;
- 主动映射,使用 resultTyp 或者 resultMap 指定映射关系。
通常在生产应用中,我们可以直接放弃使用自动映射这种方式,在数据库表中的字段与 Java 持久化对象中的字段是一一对应的场景下,且能够使用 MyBatis 插件“mapUnderscoreToCamelCase”实现映射,那么我们可以选择使用 resultType 属性进行配置,如果不能一一对应,或者不能通过 MyBatis 插件“mapUnderscoreToCamelCase”实现映射,以及映射关系较为复杂(例如:一对一关联,一对多关联登)的场景下,我们应该使用 resultMap 元素定义映射关系。
自动映射
自动映射指的是不显式指定 resultType 或 reusltMap,让 MyBatis 尝试自动根据查询结果的列名与 Java 持久化对象进行匹配进行映射,这需要保证数据库表中的字段名与 Java 持久化对象的字段名一致,也可以通过为查询语句中的字段取别名,或者是使用 MyBatis 插件“mapUnderscoreToCamelCase”的方式实现自动映射。
下面我们举个例子。
定义数据库表 order_item,代码如下:
create table order_item (
item_id int not null comment '订单商品表主键' primary key,
order_id int not null comment '订单表主键',
commodity_id int not null comment '商品表主键',
commodity_price decimal(18, 2) not null comment '商品价格',
commodity_count int not null comment '商品数量'
) comment '订单明细表';
定义 Java 持久化对象 OrderItemDO,代码如下:
public class OrderItemDO {
private Integer itemId;
private Integer orderId;
private Integer commodityId;
private BigDecimal commodityPrice;
private Integer commodityCount;
}
定义 OrderItemMapper 接口中的方法,代码如下:
public interface OrderItemMapper {
List<OrderItemDO> selectByAutoMap();
}
定义 OrderItemMapper 映射器中的 SQL 语句,代码如下:
<?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.wyz.mapper.OrderItemMapper">
<select id="selectByAutoMap">
select item_id as itemId,
order_id as orderId,
commodity_id as commodityId,
commodity_price as commodityPrice,
commodity_count as commodityCount
from order_item
</select>
</mapper>
注意,此时 OrderItemMapper 接口的中方法可能会报错,提示如下:
Result type not match for select id=“selectByAutoMap” srcType: targetType: com.wyz.entity.OrderItemDO
针对于这个报错,我们直接忽略就可以了。
最后来写单元测试,代码如下:
public void testSelectByAutoMap() throws IOException {
Reader mysqlReader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mysqlReader);
SqlSession sqlSession = sqlSessionFactory.openSession();
OrderItemMapper orderItemMapper = sqlSession.getMapper(OrderItemMapper.class);
List<OrderItemDO> orderItems = orderItemMapper.selectByAutoMap();
System.out.println(JSON.toJSONString(orderItems, JSONWriter.Feature.PrettyFormat));
}
执行单元测试,可以看到控制台正常输出了查询到的数据。
这种方式是在 MyBatis 应用程序构建 MappedStatement 时通过反射获取 OrderItemMapper 接口中的方法,解析方法返回值的方式来获取到结果集映射的 Java 持久化对象的,部分源码如下所示:
图中源码做了大量删减,重点关注下调用链路即可,高亮的部分就是通过反射获取数据集映射的 Java 持久化对象类型的部分。因为需要做额外的工作,并且使用到反射技术,因此在效率上较差,不建议使用这种方式。
主动映射
主动映射可以通过 select 元素中的 resultType 属性定义,也可以通过 resultMap 元素结合 select 元素的 resultMap 属性定义。
使用 reusltType 进行映射
修改 OrderItemMapper 映射器中的 SQL 语句,为其添加 resultType 属性,代码如下:
<select id="selectByAutoMap" resultType="com.wyz.entity.OrderItemDO">
select item_id as itemId,
order_id as orderId,
commodity_id as commodityId,
commodity_price as commodityPrice,
commodity_count as commodityCount
from order_item
</select>
再次执行单元测试,可以看到控制台能够正常输出查询结果。
这种方式与自动映射基本一致,只不过我们指定了 reusltType 的类型,MyBatis 只需要根据 resultType 的配置反射出相应的类型即可,而不需要反射出方法的返回值。同样的,使用 resultType 也需要保证数据库表中的字段名与 Java 持久化对象中的字段名完全一致,当然了,同样也可以通过为查询语句中的字段起别名,或者是使用 MyBatis 的插件“mapUnderscoreToCamelCase”解决。
这种方式 MyBatis 会直接读取 reusltType 的配置,并通过类型别名注册器 TypeAliasRegistry 去解析出配置对应的类名。
使用 resultMap 进行映射
使用 resulttMap 元素可以定义数据库表中的字段名与 Java 持久化对象中的字段名的映射器关系,因为 resultMap 元素强大的功能,它可以处理很多复杂的场景,具体用法可以参考我在掘金专栏中的文章:《MyBatis映射器:一对一关联查询》和《MyBatis映射器:一对多关联查询》。
下面我们使用 resulttMap 元素定义映射关系,代码如下:
<resultMap id="BaseResultMap" type="com.wyz.entity.OrderItemDO">
<id property="itemId" column="item_id" jdbcType="INTEGER"/>
<result property="orderId" column="order_id" jdbcType="INTEGER"/>
<result property="commodityId" column="commodity_id" jdbcType="INTEGER"/>
<result property="commodityPrice" column="commodity_price" jdbcType="DECIMAL"/>
<result property="commodityCount" column="commodity_count" jdbcType="INTEGER"/>
</resultMap>
修改 OrderItemMapper 映射器中的 SQL 语句,为其添加 resultMap 属性,代码如下:
<select id="selectByAutoMap" resultMap="BaseResultMap">
select item_id,
order_id,
commodity_id,
commodity_price,
commodity_count
from order_item
</select>
最后再来执行单元测试,可以看到控制台依旧可以正常输出结果。
使用 reusltMap 元素的优点是,灵活性高,能够适应复杂的场景,并且使用 resultMap 元素的可读性更好,能够清晰直观的看到数据库中表的字段与 Java 持久化对象中字段的映射关系,如果硬要说缺点,也只能是学习成本高(相对来说),配置及相对复杂。