resultMap元素的解析工作
了解resultMap元素
resultMap是Mybatis的核心元素之一,他是最重要最强大的一个元素,通常来讲每个resultMap元素的定义在java中都会有一个持久化对象PO(Persistent Object)与之相对应。
在运行过程中,Mybatis将会根据resultMap元素的配置来决定如何将数据库查询结果转换为PO对象。
官方文档是这么介绍resultMap元素的:
resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
上面这句话丝毫没有夸大,不过也正是因为resultMap元素的强大,所以resultMap元素的DTD定义和解析操作也就变得相应复杂了一些,因此我强烈建议在学习本篇文章之前,认真的阅读一下官方文档中对于resultMap元素的介绍和使用教程:
Mybatis Mapper文件中的ResultMap元素。
当然,为了文章的阅读流畅性,本文也会介绍resultMap元素的定义和使用方式。
根据resultMap元素的dtd定义,resultMap元素定义了四个属性和六个子元素:
<!ELEMENT resultMap (constructor?,id*,result*,association*,collection*, discriminator?)>
<!ATTLIST resultMap
id CDATA #REQUIRED
type CDATA #REQUIRED
extends CDATA #IMPLIED
autoMapping (true|false) #IMPLIED
>
这些属性和子元素相互结合,共同赋予了resultMap元素复杂且强大的能力。
resultMap元素的属性定义及作用
id属性
resultMap元素的id属性,是一个必填的属性,他的取值在同一个Mapper文件中应该是唯一的。
resultMap元素的id属性和所属Mapper文件的namespace属性相结合,应该能够构成一个在mybatis运行环境中独一无二的全局标志。
比如,下面这个mapper文件中resultMap元素定义:
<mapper namespace="org.apache.learning.result_map.Mapper">
<resultMap id="user" type="org.apache.learning.result_map.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="roleId" column="role_id"/>
</resultMap>
</mapper>
他在mybatis中的唯一标识是org.apache.learning.result_map.Mapper.user:
type
resultMap元素还有一个必填的type属性,type属性的取值应该是一个具体类的完全限定名称或者是类别名。
type属性用于表明当前resultMap元素所对应的PO对象类型。
比如,在上面的示例中,名为user的resultMap定义,所对应的是完全限定名称为名为org.apache.learning.result_map.User的PO:
User.java的代码:
package org.apache.learning.result_map;
import lombok.Data;
@Data
public class User {
private Integer id;
private String name;
private String roleId;
}
extends
非必填的extends属性可以使一个resultMap继承其他resultMap的配置信息。
如果继承者和被继承者同时定义了相同属性的配置,继承者的定义将会覆盖被继承者的定义。
<mapper namespace="org.apache.learning.result_map.Mapper">
<resultMap id="ParentUser" type="org.apache.learning.result_map.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="roleId" column="role_id"/>
</resultMap>
<resultMap id="SubUser" type="org.apache.learning.result_map.User" extends="ParentUser">
<result property="name" column="sub_name"/>
</resultMap>
</mapper>
在上面的配置中,SubUser继承了ParentUser,因此在运行过程中,SubUser将会获得ParentUser的三条属性配置:
同时因为SubUser针对name属性进行了单独配置,所以最终SubUser的name属性对应的column的取值将会是sub_name。
autoMapping
在前面的示例代码中,我们在声明resultMap元素时,针对resultMap元素对应的java类的每个属性定义,我们都提供了一条与之相对应的id或者result配置:
实际上,针对这种简单的场景,mybatis可以为我们自动映射查询结果:
当自动映射查询结果时,
MyBatis会获取结果中返回的列名并在Java类中查找相同名字的属性(忽略大小写)。这意味着如果发现了ID列和id属性,MyBatis会将列ID的值赋给id属性。 通常数据库列使用大写字母组成的单词命名,单词间用下划线分隔;而Java属性一般遵循驼峰命名法约定。为了在这两种命名方式之间启用自动映射,需要将mapUnderscoreToCamelCase设置为 true。
配置mapUnderscoreToCamelCase:
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- ...省略其余配置 -->
</configuration>
当我们开启了mybatis的下划线转驼峰的特性之后,如果发现了 role_id 列和 roleId属性,MyBatis 会将列 role_id 的值赋给 roleId 属性。
我们做一个实验,在前面创建的单元测试包下新增一个org.apache.learning.result_map包:
在包下新建一个User对象,定义id,name,roleId三个属性:
package org.apache.learning.result_map;
import lombok.Data;
@Data
public class User {
private Integer id;
private String name;
private String roleId;
}
新建一个CreateDB.sql的脚本文件,提供初始数据,脚本中的user表的列定义和User对象的属性一一对应,其中role_id列名称以下划线的形式出现:
/* ======================== 插入用户数据 =============================*/
drop table user if exists;
create table user
(
id int,
name varchar(20),
role_id int
);
insert into user (id, name,role_id) values (1, 'Panda', 1);
新建一个mybatis-config.xml配置文件,除了简单的数据源配置之外,启用下划线转驼峰的特性:
<configuration>
<settings>
<!-- 启用下划线转驼峰的特性 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="" value=""/>
</transactionManager>
<!-- 使用 hsql 内存数据库 -->
<dataSource type="UNPOOLED">
<property name="driver" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:type_handler_test"/>
<property name="username" value="sa"/>
</dataSource>
</environment>
</environments>
</configuration>
然后,编写对应的Mapper定义及其配置文件:
Mapper.java:
public interface Mapper {
/**
* 获取指定ID的用户信息
* @param id ID
* @return 用户信息
*/
User selectById(Integer id);
}
Mapper.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="org.apache.learning.result_map.Mapper">
<resultMap id="user" type="org.apache.learning.result_map.User" autoMapping="true"/>
<select id="selectById" resultMap="user">
SELECT *
FROM user u
WHERE u.id = #{id}
</select>
</mapper>
名为
user的resultMap的autoMapping属性配置可以省略,具体原因,后面的文章会提到。
最后编写单元测试ResultMapTest.java:
package org.apache.learning.result_map;
import lombok.Cleanup;
import lombok.SneakyThrows;
import org.apache.ibatis.BaseDataTest;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.Reader;
/**
* @author HanQi [Jpanda@aliyun.com]
* @version 1.0
* @since 2020/4/2 13:28:23
*/
public class ResultMapTest {
private static SqlSessionFactory sqlSessionFactory;
@BeforeEach
@SneakyThrows
public void setup() {
@Cleanup
Reader reader = Resources.getResourceAsReader("org/apache/learning/result_map/mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
"org/apache/learning/result_map/CreateDB.sql");
sqlSessionFactory.getConfiguration().addMapper(org.apache.learning.result_map.Mapper.class);
}
@Test
public void autoMapping() {
@Cleanup
SqlSession sqlSession = sqlSessionFactory.openSession();
Mapper mapper = sqlSession.getMapper(Mapper.class);
User u = mapper.selectById(1);
assert u.getId() == 1;
}
}
运行结果:
由此可以看到,mybatis不仅能帮我们完成简单的列名转属性名的工作,还能帮我们完成下划线形式的列名转驼峰属性名的工作:
总结
上面就是resultMap四个属性的作用和用法,现在总结一下:
| 属性名称 | 类型 | 描述 |
|---|---|---|
id |
String | resultMap在当前mapper文件中的唯一标志 |
type |
String | 当前resultMap所对应的类的完全限定名称或类型别名 |
extends |
String | 被继承的resultMap配置,resultMap本身的配置优先级高于被继承的resultMap配置 |
autoMapping |
Boolean | 是否为本结果映射开启或者关闭自动映射 |