Mybatis源码之美:3.5.2.负责一对一映射的association元素和负责一对多映射的collection元素

负责一对一映射的association元素和负责一对多映射的collection元素

集合啦

负责一对一映射的association元素

association元素的简单应用

在大多数业务场景下,我们的PO都是一个简单的javaBean定义,他的属性定义基本都是简单属性定义。

但是有些时候,我们可能会需要定义一个较为复杂的PO,这个PO中的某些属性可能会是另一个PO定义。

association元素就被应用在这种场景下,它用于关联两个具有一对一关系的复杂java对象。

为了简化描述和理解,我将外层对象称之为父对象,被关联的内部对象称之为子对象

翘脚

我们通过一个简单的示例来看一下association元素的用法:

在一个简单的用户对象中嵌套了一个角色对象:


@Data
public class Role {
    private Integer id;
    private String name;
}

@Data
public class User {
    private Integer id;
    private String name;
    private Role role;
}

在此处,User对象为父对象,Role对象为子对象。

他们对应的表结构和初始数据如下:

/* ========================  插入用户数据   =============================*/
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);

/* ========================  插入角色数据   =============================*/
drop table ROLE if exists;
create table ROLE
(
    id   int,
    name varchar(20)
);
insert into ROLE (id, name) values (1, '普通用户');

利用association元素配置UserRole两个对象之间的关系:

<resultMap id="role" type="org.apache.learning.result_map.association.Role" autoMapping="true"/>

<resultMap id="user" type="org.apache.learning.result_map.association.User" autoMapping="true">
    <association property="role" column="role_id" resultMap="role" columnPrefix="role_"/>
</resultMap>

提供一个包含了UserRole数据的查询语句:

<select id="selectUserRoleById" resultMap="user">
    SELECT u.*,r.id as role_id,r.name as role_name
    FROM USER u
                LEFT JOIN ROLE r ON r.id = u.role_id
    WHERE u.id = #{id}
</select>

运行结果:

运行结果

具体的代码可以参见单元测试:单元测试AssociationTestone2One()方法

详细访问地址:https://gitee.com/topanda/mybatis-3/tree/master/src/test/java/org/apache/learning/result_map/association

在上面的示例代码中,我们使用association元素绑定了User对象和Role对象之间的关系,并成功的在一次方法调用中获得了两个完整的对象。

工作使我快乐

association元素的DTD定义

association元素的DTD定义看起来要比result元素复杂的多:

<!ELEMENT association (constructor?,id*,result*,association*,collection*, discriminator?)>
<!ATTLIST association
property CDATA #REQUIRED
column CDATA #IMPLIED
javaType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
select CDATA #IMPLIED
resultMap CDATA #IMPLIED
typeHandler CDATA #IMPLIED
notNullColumn CDATA #IMPLIED
columnPrefix CDATA #IMPLIED
resultSet CDATA #IMPLIED
foreignColumn CDATA #IMPLIED
autoMapping (true|false) #IMPLIED
fetchType (lazy|eager) #IMPLIED
>
睁大眼睛

如果仔细看上面的DTD定义,我们会发现association元素和resultMap元素具有完全相同的子元素的定义:

<!ELEMENT association (constructor?,id*,result*,association*,collection*, discriminator?)>
<!ELEMENT resultMap   (constructor?,id*,result*,association*,collection*, discriminator?)>

这一点意味着在某种角度上来讲association元素就是一个特殊的resultMap元素。

事实上也是如此,association元素除了不能像resultMap元素一样单独存在外,它具有resultMap元素所拥有的所有特性

除此之外,相较于resultMap元素,association元素还具有一些独有的属性定义,这些属性定义使得association元素甚至比resultMap元素更为强大。

要想让mybatis成功的处理一个子对象,我们就要明确的告知mybatis应该如何利用现有数据获取到子对象所需的数据,以及如何将所需数据转换为子对象

推眼镜

association元素提供了三种方式来描述这一过程,他们分别是:嵌套查询语句嵌套结果映射以及多结果集配置

上面三种方式可能听起来比较陌生,没关系,我们接下来就会详细的了解这三种不同的方式。

association元素的属性定义

association元素具有十三个属性定义,这些属性根据作用可以分为四类:

  • 通用型功能性查询属性定义
  • 描述嵌套查询语句的属性定义
  • 描述嵌套结果映射的属性定义
  • 描述多结果集的属性定义

通用型功能性查询属性定义

我们先来看通用型功能性查询属性定义。

result元素一样,association元素也定义了propertyjavaTypejdbcTypeTypeHandler四个属性。

这四个属性在定义和作用上都和result元素中完全一致,因此这里就不在赘述。

描述嵌套查询语句的属性定义

负责配置嵌套查询语句的是三个可选的属性,他们分别是columnselect以及fetchType

在使用嵌套查询语句的场景下columnselect两个属性均是必填的。

select属性指向一个标准select语句,比如:

<select id="selectRoleById" resultMap="role">
    SELECT *
    FROM role r
    WHERE r.id = #{id}
</select>

select语句中可能会包含一些行内参数映射,比如selectRoleById中的#{id}定义,行内参数映射所需的数据我们可以通过column属性来进行配置。

association元素的column属性的作用和result元素中的稍有不同,association元素的column属性可以是普通的列名称定义,比如column="id",也可以是一个复合的属性描述,比如:column="{prop1=col1,prop2=col2}"

复合属性描述的语法定义为:以{开始,}结尾,中间通过,分隔多个属性描述,每个属性描述均由行内参数映射名,=,列名称三部分构成。

行内参数映射名对应的是select语句中的行内参数映射,列名称则对应着父对象中的数据列名称。

最后一个fetchType属性用于控制子对象的加载行为,他有lazyeager两个取值,分别对应着懒加载和立即加载。

fetchType属性的优先级要高于配置全局懒加载的属性lazyLoadingEnabled,当指定了fetchType属性之后,lazyLoadingEnabled的配置将会被忽略。

我们在上文中创建的单元测试中继续进行简单的测试工作。

测试常规嵌套查询

新增一个配置了嵌套查询的resultMap以及resultMap对应的两个select元素:

<resultMap id="userNestedQuery" type="org.apache.learning.result_map.association.User" autoMapping="true">
    <association property="role" column="role_id" select="selectRoleById"/>
</resultMap>

<select id="selectRoleById" resultMap="role">
    SELECT *
    FROM ROLE r
    WHERE r.id = #{id}
</select>

<select id="selectUserByIdNestedQuery" resultMap="userNestedQuery">
    SELECT *
    FROM USER u
    WHERE u.id = #{id}
</select>

编写单元测试:

@Test
public void nestedQueryTest() {
    @Cleanup
    SqlSession sqlSession = sqlSessionFactory.openSession();
    associationMapper = sqlSession.getMapper(AssociationMapper.class);

    User user=associationMapper.selectUserByIdNestedQuery(1);
    Assertions.assertNotNull(user.getRole());
}
测试复合属性描述

创建一个association元素的column属性为{id=role_id}resultMap定义:

<!--  测试嵌套查询 - 复合属性描述-->
<resultMap id="userNestedQueryWithCompoundProperty" type="org.apache.learning.result_map.association.User" autoMapping="true">
    <association property="role" column="role_id" select="selectRoleById"/>
</resultMap>

<select id="selectUserNestedQueryWithCompoundProperty" resultMap="userNestedQueryWithCompoundProperty">
    SELECT *
    FROM USER u
    WHERE u.id = #{id}
</select>

编写单元测试:

@Test
public void selectUserNestedQueryWithCompoundPropertyTest() {
    sqlSessionFactory.getConfiguration().addMapper(AssociationMapper.class);
    @Cleanup
    SqlSession sqlSession = sqlSessionFactory.openSession();
    associationMapper = sqlSession.getMapper(AssociationMapper.class);
    User user = associationMapper.selectUserNestedQueryWithCompoundProperty(1);
    Assertions.assertNotNull(user.getRole());
}
测试懒加载属性

复用上面的代码创建一个启用了懒加载resultMap:

<!--  测试嵌套查询 - 懒加载 -->
<resultMap id="userNestedQueryWithLazy" type="org.apache.learning.result_map.association.User" autoMapping="true">
    <association property="role" column="role_id" select="selectRoleById" fetchType="lazy"/>
</resultMap>

<select id="selectUserNestedQueryWithLazy" resultMap="userNestedQueryWithLazy">
    SELECT *
    FROM USER u
    WHERE u.id = #{id}
</select>

编写单元测试:

@Test
public void selectUserNestedQueryWithLazyTest() {
    // 禁用全局懒加载
    sqlSessionFactory.getConfiguration().setLazyLoadingEnabled(false);
    sqlSessionFactory.getConfiguration().addMapper(AssociationMapper.class);

    @Cleanup
    SqlSession sqlSession = sqlSessionFactory.openSession();
    associationMapper = sqlSession.getMapper(AssociationMapper.class);
    User user = associationMapper.selectUserNestedQueryWithLazy(1);

    System.out.println("====   Lazy Load  ====");

    Assertions.assertNotNull(user.getRole());
}

运行结果:

懒加载

可以看到,虽然我们禁用了全局懒加载配置,但是在本次方法调用中依然成功启用了懒加载。

描述嵌套结果映射的属性定义

在本篇文章的最开始我们就已经接触到了嵌套结果映射的使用方式。

负责配置嵌套结果映射的是四个可选的属性resultMap,columnPrefix,notNullColumn以及autoMapping

属性resultMap指向了一个标准的resultMap元素配置。

mybatis将会根据resultMap元素配置将查询到的数据映射为子对象

比如在本篇开始使用的示例中:

<resultMap id="role" type="org.apache.learning.result_map.association.Role" autoMapping="true"/>

<resultMap id="user" type="org.apache.learning.result_map.association.User" autoMapping="true">
    <association property="role" column="role_id" resultMap="role" columnPrefix="role_"/>
</resultMap>

根据association元素的配置,User对象的role属性将会根据名为roleresultMap配置来生成。

在示例中,association元素还配置了columnPrefix属性的值为role_,这是因为我们的USERROLE两张表中都定义了idname属性:

create table USER
(
    id   int,
    name varchar(20),
    role_id int
);

create table ROLE
(
    id   int,
    name varchar(20)
);

为了区分二者的区别,我们在查询数据时为ROLE表中的列指定了别名,别名的生成规则是统一添加role_前缀:

<select id="selectUserRoleById" resultMap="user">
    SELECT u.*,r.id as role_id,r.name as role_name
    FROM USER u
                LEFT JOIN ROLE r ON r.id = u.role_id
    WHERE u.id = #{id}
</select>

查询到的结果:

id name role_id role_name
1 Panda 1 普通用户

但是添加了role_前缀之后,查询到的数据就无法和Role中的属性定义相匹配。

名称不匹配
咋办

为了解决这个问题,association元素提供了columnPrefix属性。

columnPrefix属性的值将会作用在被引用的resultMap配置上,在匹配其column属性时,会先添加统一的前缀,之后再进行匹配操作。

association元素还有一个可选的notNullColumn属性,默认情况下,只有在至少一个属性不为空的前提下才会创建子对象,但是我们可以通过notNullColumn属性来控制这一行为,notNullColumn属性的取值是以,分隔的多个属性名称,只有在这些属性均不为空的前提下,子对象才会被创建。

比如在我们的示例代码中,如果我们为association元素指定了notNullColumn的值为name:

<resultMap id="userWithNotNullColumn" type="org.apache.learning.result_map.association.User" autoMapping="true">
    <association property="role" column="role_id" resultMap="role" columnPrefix="role_" notNullColumn="name"/>
</resultMap>

<select id="selectUserRoleByIdWithNotNullColumn" resultMap="userWithNotNullColumn">
    SELECT u.*, r.id as role_id, r.name as role_name
    FROM USER u
                LEFT JOIN ROLE r ON r.id = u.role_id
    WHERE u.id = #{id}
</select>

那么只有在ROLE表的name列不为null时才会实例化User对象的role属性,我们新增两条数据:

insert into USER (id, name,role_id) values (2, 'Panda2', 2);
insert into ROLE (id, name) values (2, null);

编写一个新的单元测试:

@Test
public void selectUserRoleByIdWithNotNullColumnTest() {

    sqlSessionFactory.getConfiguration().addMapper(AssociationMapper.class);
    @Cleanup
    SqlSession sqlSession = sqlSessionFactory.openSession();
    associationMapper = sqlSession.getMapper(AssociationMapper.class);

    User u = associationMapper.selectUserRoleByIdWithNotNullColumn(1);
    log.debug("id为1的User对象:{}",u);
    Assertions.assertNotNull(u.getRole());

    User u2 = associationMapper.selectUserRoleByIdWithNotNullColumn(2);
    log.debug("id为2的User对象:{}",u2);
    Assertions.assertNull(u2.getRole());
}

关键运行日志:

DEBUG [main] - id为1的User对象:User(id=1, name=Panda, role=Role(id=1, name=普通用户))
DEBUG [main] - id为2的User对象:User(id=2, name=Panda2, role=null)

数据:

id name role_id role_name
1 Panda 1 普通用户
2 Panda2 2

我们会发现id2的用户数据,因为Rolename属性没有设置,所以他的role也没有被实例化。

除了上面的三个属性之外,association元素还有一个比较特殊的属性autoMapping

我们前面说过association元素是一个特殊的resultMap元素,它具有和resultMap元素一样的子元素定义,因此我们可以直接通过association元素的子元素来声明一个嵌套结果映射:

<association property="role" column="role_id"  columnPrefix="role_" javaType="org.apache.learning.result_map.association.Role">
  <result property="id" column="id"/>
  <result property="name" column="name"/>
</association>

association元素的autoMapping属性的行为和resultMap元素的类似,都是用于配置当前结果映射的自动映射行为。

需要注意的是,通过selectresultMap属性引用的结果映射是不受该属性的影响的。

描述多结果集的属性定义

association元素的最后一类属性是用来描述多结果集的.

多结果集就目前来看,在实际业务中,我几乎没有用到过.但是这并不妨碍我们去学习和了解他,有些时候,这些偏门的知识可能会有大用处哟.

摊手

用于描述多结果集的属性有三个,他们分别是column,foreignColumn以及resultSet.

多结果集

在了解这些属性的作用之前,我们先了解一下什么是多结果集?


路过

多结果集就是:我们可以通过执行一次数据库操作,获取到多个ResultSet对象.

根据JDBC规范,我们可以通过connection.getMetaData().supportsMultipleResultSets();方法来查看当前数据源是否支持多结果集:

hsqldb

通常来讲,我们一次数据库操作只能得到一个ResultSet对象,但是部分数据库支持在一次查询中返回多个结果集.

还有部分数据库支持在存储过程中返回多个结果集,或者支持一次性执行多个语句,每个语句都对应一个结果集.


脑袋一片空白

对应的场景可能有些多,这里我们主要还是看存储过程中的多结果集配置:

我们先创建一个MultiResultSetStoredProcedures对象,该对象用来给hsqldb提供一个存储过程实现:

public class MultiResultSetStoredProcedures {

    public static void getAllUserAndRoles(Connection connection, ResultSet[] resultSets ,ResultSet[] resultSets2) throws SQLException {
        Statement statement=connection.createStatement();
        resultSets[0] = statement.executeQuery("SELECT * FROM USER");
        resultSets2[0] = statement.executeQuery("SELECT * FROM ROLE");

    }
}

MultiResultSetStoredProceduresgetAllUserAndRoles()方法在实现上会分别查询出USERROLE两个表中的数据赋值给两个ResultSet对象.

关于更多hsqldb存储过程的内容,可以访问链接进行学习:http://hsqldb.org/doc/2.0/guide/sqlroutines-chapt.html#src_psm_handlers.

之后我们在CreateDB.sql新增一条关于存储过程的配置:

DROP PROCEDURE getAllUserAndRoles IF EXISTS;
CREATE PROCEDURE getAllUserAndRoles()
    READS SQL DATA
    LANGUAGE JAVA
    DYNAMIC RESULT SETS 1
    EXTERNAL NAME 'CLASSPATH:org.apache.learning.result_map.association.MultiResultSetStoredProcedures.getAllUserAndRoles';

最后,我们创建一个名为testMultiResultSet()的单元测试:

@Test
@SneakyThrows
public void testMultiResultSet() {
    @Cleanup
    Connection connection = sqlSessionFactory.openSession().getConnection();

    CallableStatement statement = connection.prepareCall("call getAllUserAndRoles()");

    ResultSet resultSet = statement.executeQuery();
    log.debug("===========ResultSet FOR USER   ===============");
    while (resultSet.next()) {
        log.debug("USER={id:{},name:{},roleId:{}}", resultSet.getInt("id"),resultSet.getString("name"),resultSet.getString("role_id"));
    }

    log.debug("===========ResultSet FOR ROLE   ===============");
    assert statement.getMoreResults();
    resultSet = statement.getResultSet();
    while (resultSet.next()) {
        log.debug("ROLE={id:{},name:{}}", resultSet.getInt("id"),resultSet.getString("name"));
    }
}

在该单元测试中,我们将会依次读取存储过程getAllUserAndRoles()返回的两个ResultSet,并打印出来.

关键运行日志:

DEBUG [main] - ===========ResultSet FOR USER   ===============
DEBUG [main] - USER={id:1,name:Panda,roleId:1}
DEBUG [main] - USER={id:2,name:Panda2,roleId:2}
DEBUG [main] - ===========ResultSet FOR ROLE   ===============
DEBUG [main] - ROLE={id:1,name:普通用户}
DEBUG [main] - ROLE={id:2,name:null}

由此可见,我们的存储过程getAllUserAndRoles()成功的返回两个结果集.

属性

在了解resultSet属性之前,我们需要简单补充一下select元素的resultSets属性相关的知识.

默认情况下,一条select语句对应一个结果集,因此我们不需要关注结果集相关的问题.

但是,通过实验,我们已经成功的在一条select语句中返回了多个结果集,如果我们想操作不同的结果集的数据,我们就有必要区分出每个结果集对象.

mybaits为这种场景提供了一个解决方案,它允许我们在配置select元素的时候,通过配置其resultSets属性来为每个结果集指定名称.

抱住我的小猪猪

结果集的名称和resultSets属性定义顺序对应.如果有多个结果集的名称需要配置,名称之间使用,进行分隔.

比如,在下面的示例代码中,第一个ResultSet名为users,第二个ResultSet名为roles:

<select id="selectAllUserAndRole" resultSets="users,roles" resultMap="userRoleWithResultSet"
        statementType="CALLABLE">
    {call getAllUserAndRoles() }
</select>

association元素提供的resultSet属性读取的就是resultSets属性定义的名称,当前association元素将会使用resultSet属性对应的ResultSet对象来加载.

需要注意的是,association元素的column属性在多结果集模式下的表现和在嵌套查询语句模式下的表现稍有不同.

多结果集模式下,column属性将会配合着foreignColumn属性一起使用.

有趣

foreignColumn属性用于指定在映射时需要使用的父对象的数据列名称,如果有多个数据列,使用,进行分隔.

column属性的命名规则同foreignColumn属性一致,它用于指定在映射时需要使用的子对象的数据列名称.

foreignColumn属性和column属性之间是顺序关联的.

多结果集模式的应用

最后,通过一个简单的测试,来实际看一下多结果集模式的应用.

复用之前的代码,我们在AssociationMapper.xml文件中新增一个调用存储过程的方法声明以及相应的resultMap配置:

<!-- 测试多结果集-->
<resultMap id="userRoleWithResultSet" type="org.apache.learning.result_map.association.User" autoMapping="true">
    <association property="role" resultSet="roles" column="role_id" foreignColumn="id"
                    javaType="org.apache.learning.result_map.association.Role"/>
</resultMap>

<select id="selectAllUserAndRole" resultSets="users,roles" resultMap="userRoleWithResultSet"
        statementType="CALLABLE">
    {call getAllUserAndRoles() }
</select>

并在AssociationMapper.java中添加对应的方法声明:

List<User> selectAllUserAndRole();

最后编辑一个单元测试,来看一下实际运行情况:

@Test
public void selectAllUserAndRoleTest() {
    sqlSessionFactory.getConfiguration().addMapper(AssociationMapper.class);
    @Cleanup
    SqlSession sqlSession = sqlSessionFactory.openSession();
    associationMapper = sqlSession.getMapper(AssociationMapper.class);
    List<User> users = associationMapper.selectAllUserAndRole();
    log.debug("users-{}",users);
    assert  users.get(0).getRole().getId()==1;
    assert  users.get(1).getRole().getId()==2;
}

单元测试成功运行,并输出下列关键日志:

...省略

DEBUG [main] - ==>  Preparing: {call getAllUserAndRoles() }
DEBUG [main] - ==> Parameters:
DEBUG [main] - <==      Total: 2
DEBUG [main] - <==      Total: 2
DEBUG [main] - users-[User(id=1, name=Panda, role=Role(id=1, name=普通用户)), User(id=2, name=Panda2, role=Role(id=2, name=null))]

...省略

总结

到这里我们就了解了association元素的所有属性定义.

至于association元素的子元素定义,因为在定义上和用法上都和resultMap元素完全一致.

所以在我们了解完resultMap元素的子元素之后,自然而然就了解了关于association元素的子元素定义.

最后,我们总结一下association元素的属性作用:

  • 通用型功能性查询属性定义

    属性名称 必填 类型 描述
    property false String PO对象的属性名称
    javaType false String PO对象的属性类型
    jdbcType false String 数据库中的列类型
    typeHandler false String 负责将数据库数据转换为PO对象的类型转换器
  • 描述嵌套查询语句的属性定义

    属性名称 必填 类型 描述
    column true String 用于配置行内参数映射,column属性可以是普通的列名称定义,比如column="id",也可以是一个复合的属性描述,比如:column="{prop1=col1,prop2=col2}"
    select true String 用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。
    fetchType false String fetchType属性用于控制子对象的加载行为,他有lazy和eager两个取值,分别对应着懒加载和立即加载. fetchType属性的优先级要高于配置全局懒加载的属性lazyLoadingEnabled,当指定了fetchType属性之后,lazyLoadingEnabled的配置将会被忽略。
  • 描述嵌套结果映射的属性定义

    属性名称 必填 类型 描述
    resultMap false String 它指向了一个标准的resultMap元素配置
    columnPrefix false String columnPrefix属性的值将会作用在被引用的resultMap配置上,在匹配其column属性时,会先添加统一的前缀,之后再进行匹配操作。
    notNullColumn false String notNullColumn属性的取值是以,分隔的多个属性名称,只有在这些属性均不为空的前提下,子对象才会被创建.
    autoMapping false boolean autoMapping属性的行为和resultMap元素的类似,都是用于配置当前结果映射的自动映射行为。 需要注意的是,通过select和resultMap属性引用的结果映射是不受该属性的影响的。
  • 描述多结果集的属性定义

    属性名称 必填 类型 描述
    resultSet true String 当前association元素将会使用resultSet属性对应的ResultSet对象来加载
    foreignColumn true String foreignColumn属性用于指定在映射时需要使用的父对象的数据列名称,如果有多个数据列,使用,进行分隔.
    column true String column属性的命名规则同foreignColumn属性一致,它用于指定在映射时需要使用的子对象的数据列名称.

负责一对多映射的collection元素

既然有一对一的复杂对象关系,那自然也会有一对多的复杂对象关系,association元素用来配置一对一的复杂关系,collection元素则是用来配置一对多的复杂对象关系.

collection元素和association元素几乎完全一样:

<!ELEMENT collection (constructor?,id*,result*,association*,collection*, discriminator?)>
<!ATTLIST collection
property CDATA #REQUIRED
column CDATA #IMPLIED
javaType CDATA #IMPLIED
ofType CDATA #IMPLIED
jdbcType CDATA #IMPLIED
select CDATA #IMPLIED
resultMap CDATA #IMPLIED
typeHandler CDATA #IMPLIED
notNullColumn CDATA #IMPLIED
columnPrefix CDATA #IMPLIED
resultSet CDATA #IMPLIED
foreignColumn CDATA #IMPLIED
autoMapping (true|false) #IMPLIED
fetchType (lazy|eager) #IMPLIED
>

除了collection元素多了一个ofType属性之外,二者的子元素和属性定义完全一致.

两者的属性含义也完全相同,因此,本篇不会再大费笔墨的去一个个的了解collection元素的完整定义,而是对比着association元素来看二者的不同之处.

靓仔开车

因为collection元素用于表示一对多的复杂对象关系,根据javaType属性的定义,javaType属性应该指向一个集合类型,因此,我们需要一个字段来描述集合中存储的对象类型.

mybatiscollection元素添加了一个额外的ofType属性,这个属性的作用就是用来描述集合中对象的类型的.

我们看一个简单的完整示例.

我们变更在associationUserRole对象的关系,改为一个用户可以拥有多个角色.

@Data
public class Role {
    private Integer id;
    private String name;
}

@Data
public class User {
    private Integer id;
    private String name;
    private List<Role> roles;
}

用户和角色关系通过一张用户角色关系表来维护:

/* ========================  插入用户数据   =============================*/
drop table USER if exists;
create table USER
(
    id      int,
    name    varchar(20)
);
insert into USER (id, name)
values (1, 'Panda');

/* ========================  插入角色数据   =============================*/
drop table ROLE if exists;
create table ROLE
(
    id   int,
    name varchar(20)
);
insert into ROLE (id, name) values (1, '管理员');
insert into ROLE (id, name) values (2, '普通用户');

/* ========================  插入用户角色数据   =============================*/
drop table USER_ROLE if exists;
create table USER_ROLE
(
    user_id   int,
    role_id   int
);
insert into USER_ROLE (user_id, role_id) values (1, 1);
insert into USER_ROLE (user_id, role_id) values (1, 2);

编写对应的Mapper对象及其配置文件:

CollectionMapper.java:

public interface CollectionMapper {
    User selectUserRoleById(Integer id);
}

CollectionMapper.xml:

<!-- 简单属性映射 -->
<resultMap id="role" type="org.apache.learning.result_map.collection.Role" autoMapping="true"/>


<resultMap id="user" type="org.apache.learning.result_map.collection.User" autoMapping="true">
    <collection property="roles" column="{id=id}" select="selectRolesByUserID"/>
</resultMap>

<select id="selectRolesByUserID" resultMap="role">
    SELECT *
    FROM ROLE r
                LEFT JOIN USER_ROLE ur ON r.id = ur.role_id
    WHERE ur.user_id = #{id}
</select>


<select id="selectUserRoleById" resultMap="user">
    SELECT *
    FROM USER u
    WHERE u.id = #{id}
</select>

需要注意的是,我们在配置collection元素的时候,定义了他的column属性为:{id=id},这样做的原因是因为如果我们直接将列名称id赋值给column属性,User对象的id属性将不会被赋值.

产生这种差异的原因在于,为column属性直接赋值列名称将会覆盖指定列的默认行为.

最后我们编写一个单元测试,查看我们collection元素的映射结果:

@Test
public void selectUserTest() {
    sqlSessionFactory.getConfiguration().addMapper(CollectionMapper.class);
    @Cleanup
    SqlSession sqlSession = sqlSessionFactory.openSession();

    CollectionMapper collectionMapper = sqlSession.getMapper(CollectionMapper.class);

    User user = collectionMapper.selectUserRoleById(1);

    assert user.getId() == 1;
    assert user.getRoles() != null;
    assert user.getRoles().size() == 2;
    log.debug("user={}",user);
}

单元测试运行的关键日志为:

... 省略 ...
DEBUG [main] - ==>  Preparing: SELECT * FROM USER u WHERE u.id = ? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - ====>  Preparing: SELECT * FROM ROLE r LEFT JOIN USER_ROLE ur ON r.id = ur.role_id WHERE ur.user_id = ? 
DEBUG [main] - ====> Parameters: 1(Integer)
DEBUG [main] - <====      Total: 2
DEBUG [main] - <==      Total: 1
DEBUG [main] - user=User(id=1, name=Panda, roles=[Role(id=1, name=管理员), Role(id=2, name=普通用户)])
... 省略 ...

user对象的数据:

User对象

结束

至此,我们也算是初步了解了association元素和collection元素了.

告辞

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

关注我
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容