负责一对一映射的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
元素配置User
和Role
两个对象之间的关系:
<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>
提供一个包含了User
和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>
运行结果:
具体的代码可以参见单元测试:单元测试AssociationTest
的one2One()
方法。
在上面的示例代码中,我们使用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
元素也定义了property
、javaType
、jdbcType
和TypeHandler
四个属性。
这四个属性在定义和作用上都和result
元素中完全一致,因此这里就不在赘述。
描述嵌套查询语句的属性定义
负责配置嵌套查询语句的是三个可选的属性,他们分别是column
、select
以及fetchType
。
在使用嵌套查询语句的场景下
column
、select
两个属性均是必填的。
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
属性用于控制子对象的加载行为,他有lazy
和eager
两个取值,分别对应着懒加载和立即加载。
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
属性将会根据名为role
的resultMap
配置来生成。
在示例中,association
元素还配置了columnPrefix
属性的值为role_
,这是因为我们的USER
和ROLE
两张表中都定义了id
和name
属性:
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 |
我们会发现id
为2
的用户数据,因为Role
的name
属性没有设置,所以他的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
元素的类似,都是用于配置当前结果映射的自动映射行为。
需要注意的是,通过select
和resultMap
属性引用的结果映射是不受该属性的影响的。
描述多结果集的属性定义
association
元素的最后一类属性是用来描述多结果集的.
多结果集就目前来看,在实际业务中,我几乎没有用到过.但是这并不妨碍我们去学习和了解他,有些时候,这些偏门的知识可能会有大用处哟.
用于描述多结果集的属性有三个,他们分别是column
,foreignColumn
以及resultSet
.
多结果集
在了解这些属性的作用之前,我们先了解一下什么是多结果集?
多结果集就是:我们可以通过执行一次数据库操作,获取到多个ResultSet
对象.
根据JDBC
规范,我们可以通过connection.getMetaData().supportsMultipleResultSets();
方法来查看当前数据源是否支持多结果集:
通常来讲,我们一次数据库操作只能得到一个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");
}
}
MultiResultSetStoredProcedures
的getAllUserAndRoles()
方法在实现上会分别查询出USER
和ROLE
两个表中的数据赋值给两个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
属性应该指向一个集合类型,因此,我们需要一个字段来描述集合中存储的对象类型.
mybatis
为collection
元素添加了一个额外的ofType
属性,这个属性的作用就是用来描述集合中对象的类型的.
我们看一个简单的完整示例.
我们变更在association
中User
和Role
对象的关系,改为一个用户可以拥有多个角色.
@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
对象的数据:
结束
至此,我们也算是初步了解了association
元素和collection
元素了.