Mybatis源码之美:3.5.解析处理resultMap元素

800 阅读6分钟

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

名为user的resultMap

type

resultMap元素还有一个必填的type属性,type属性的取值应该是一个具体类的完全限定名称或者是类别名。

type属性用于表明当前resultMap元素所对应的PO对象类型。

比如,在上面的示例中,名为userresultMap定义,所对应的是完全限定名称为名为org.apache.learning.result_map.UserPO:

User对象的resultMap定义

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属性进行了单独配置,所以最终SubUsername属性对应的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>

名为userresultMapautoMapping属性配置可以省略,具体原因,后面的文章会提到。

最后编写单元测试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 是否为本结果映射开启或者关闭自动映射

关注我,一起学习更多知识

关注我