負責一對一映射的association元素和負責一對多映射的collection元素

負責一對一映射的association元素
association元素的簡單應用
在大多數(shù)業(yè)務場景下,我們的PO都是一個簡單的javaBean定義,他的屬性定義基本都是簡單屬性定義。
但是有些時候,我們可能會需要定義一個較為復雜的PO,這個PO中的某些屬性可能會是另一個PO定義。
association元素就被應用在這種場景下,它用于關聯(lián)兩個具有一對一關系的復雜java對象。
為了簡化描述和理解,我將外層對象稱之為父對象,被關聯(lián)的內部對象稱之為子對象。

我們通過一個簡單的示例來看一下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對象為子對象。
他們對應的表結構和初始數(shù)據(jù)如下:
/* ======================== 插入用戶數(shù)據(jù) =============================*/
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);
/* ======================== 插入角色數(shù)據(jù) =============================*/
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數(shù)據(jù)的查詢語句:
<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定義,我們會發(fā)現(xiàn)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應該如何利用現(xiàn)有數(shù)據(jù)獲取到子對象所需的數(shù)據(jù),以及如何將所需數(shù)據(jù)轉換為子對象。

association元素提供了三種方式來描述這一過程,他們分別是:嵌套查詢語句、嵌套結果映射以及多結果集配置。
上面三種方式可能聽起來比較陌生,沒關系,我們接下來就會詳細的了解這三種不同的方式。
association元素的屬性定義
association元素具有十三個屬性定義,這些屬性根據(jù)作用可以分為四類:
- 通用型功能性查詢屬性定義
- 描述嵌套查詢語句的屬性定義
- 描述嵌套結果映射的屬性定義
- 描述多結果集的屬性定義
通用型功能性查詢屬性定義
我們先來看通用型功能性查詢屬性定義。
和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語句中可能會包含一些行內參數(shù)映射,比如selectRoleById中的#{id}定義,行內參數(shù)映射所需的數(shù)據(jù)我們可以通過column屬性來進行配置。
association元素的column屬性的作用和result元素中的稍有不同,association元素的column屬性可以是普通的列名稱定義,比如column="id",也可以是一個復合的屬性描述,比如:column="{prop1=col1,prop2=col2}"。
復合屬性描述的語法定義為:以{開始,}結尾,中間通過,分隔多個屬性描述,每個屬性描述均由行內參數(shù)映射名,=,列名稱三部分構成。
行內參數(shù)映射名對應的是select語句中的行內參數(shù)映射,列名稱則對應著父對象中的數(shù)據(jù)列名稱。
最后一個fetchType屬性用于控制子對象的加載行為,他有lazy和eager兩個取值,分別對應著懶加載和立即加載。
fetchType屬性的優(yōu)先級要高于配置全局懶加載的屬性lazyLoadingEnabled,當指定了fetchType屬性之后,lazyLoadingEnabled的配置將會被忽略。
我們在上文中創(chuàng)建的單元測試中繼續(xù)進行簡單的測試工作。
測試常規(guī)嵌套查詢
新增一個配置了嵌套查詢的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());
}
測試復合屬性描述
創(chuàng)建一個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());
}
測試懶加載屬性
復用上面的代碼創(chuàng)建一個啟用了懶加載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());
}
運行結果:

可以看到,雖然我們禁用了全局懶加載配置,但是在本次方法調用中依然成功啟用了懶加載。
描述嵌套結果映射的屬性定義
在本篇文章的最開始我們就已經(jīng)接觸到了嵌套結果映射的使用方式。
負責配置嵌套結果映射的是四個可選的屬性resultMap,columnPrefix,notNullColumn以及autoMapping。
屬性resultMap指向了一個標準的resultMap元素配置。
mybatis將會根據(jù)resultMap元素配置將查詢到的數(shù)據(jù)映射為子對象。
比如在本篇開始使用的示例中:
<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>
根據(jù)association元素的配置,User對象的role屬性將會根據(jù)名為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)
);
為了區(qū)分二者的區(qū)別,我們在查詢數(shù)據(jù)時為ROLE表中的列指定了別名,別名的生成規(guī)則是統(tǒng)一添加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_前綴之后,查詢到的數(shù)據(jù)就無法和Role中的屬性定義相匹配。


為了解決這個問題,association元素提供了columnPrefix屬性。
columnPrefix屬性的值將會作用在被引用的resultMap配置上,在匹配其column屬性時,會先添加統(tǒng)一的前綴,之后再進行匹配操作。
association元素還有一個可選的notNullColumn屬性,默認情況下,只有在至少一個屬性不為空的前提下才會創(chuàng)建子對象,但是我們可以通過notNullColumn屬性來控制這一行為,notNullColumn屬性的取值是以,分隔的多個屬性名稱,只有在這些屬性均不為空的前提下,子對象才會被創(chuàng)建。
比如在我們的示例代碼中,如果我們?yōu)?code>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屬性,我們新增兩條數(shù)據(jù):
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)
數(shù)據(jù):
| id | name | role_id | role_name |
|---|---|---|---|
| 1 | Panda | 1 | 普通用戶 |
| 2 | Panda2 | 2 |
我們會發(fā)現(xiàn)id為2的用戶數(shù)據(jù),因為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元素的最后一類屬性是用來描述多結果集的.
多結果集就目前來看,在實際業(yè)務中,我?guī)缀鯖]有用到過.但是這并不妨礙我們去學習和了解他,有些時候,這些偏門的知識可能會有大用處喲.

用于描述多結果集的屬性有三個,他們分別是column,foreignColumn以及resultSet.
多結果集
在了解這些屬性的作用之前,我們先了解一下什么是多結果集?

多結果集就是:我們可以通過執(zhí)行一次數(shù)據(jù)庫操作,獲取到多個ResultSet對象.
根據(jù)JDBC規(guī)范,我們可以通過connection.getMetaData().supportsMultipleResultSets();方法來查看當前數(shù)據(jù)源是否支持多結果集:

通常來講,我們一次數(shù)據(jù)庫操作只能得到一個ResultSet對象,但是部分數(shù)據(jù)庫支持在一次查詢中返回多個結果集.
還有部分數(shù)據(jù)庫支持在存儲過程中返回多個結果集,或者支持一次性執(zhí)行多個語句,每個語句都對應一個結果集.

對應的場景可能有些多,這里我們主要還是看存儲過程中的多結果集配置:
我們先創(chuàng)建一個MultiResultSetStoredProcedures對象,該對象用來給hsqldb提供一個存儲過程實現(xiàn):
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()方法在實現(xiàn)上會分別查詢出USER和ROLE兩個表中的數(shù)據(jù)賦值給兩個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';
最后,我們創(chuàng)建一個名為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語句對應一個結果集,因此我們不需要關注結果集相關的問題.
但是,通過實驗,我們已經(jīng)成功的在一條select語句中返回了多個結果集,如果我們想操作不同的結果集的數(shù)據(jù),我們就有必要區(qū)分出每個結果集對象.
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屬性在多結果集模式下的表現(xiàn)和在嵌套查詢語句模式下的表現(xiàn)稍有不同.
在多結果集模式下,column屬性將會配合著foreignColumn屬性一起使用.

foreignColumn屬性用于指定在映射時需要使用的父對象的數(shù)據(jù)列名稱,如果有多個數(shù)據(jù)列,使用,進行分隔.
column屬性的命名規(guī)則同foreignColumn屬性一致,它用于指定在映射時需要使用的子對象的數(shù)據(jù)列名稱.
foreignColumn屬性和column屬性之間是順序關聯(lián)的.
多結果集模式的應用
最后,通過一個簡單的測試,來實際看一下多結果集模式的應用.
復用之前的代碼,我們在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 數(shù)據(jù)庫中的列類型 typeHandler false String 負責將數(shù)據(jù)庫數(shù)據(jù)轉換為 PO對象的類型轉換器 -
描述嵌套查詢語句的屬性定義
屬性名稱 必填 類型 描述 column true String 用于配置行內參數(shù)映射,column屬性可以是普通的列名稱定義,比如column="id",也可以是一個復合的屬性描述,比如:column="{prop1=col1,prop2=col2}" select true String 用于加載復雜類型屬性的映射語句的 ID,它會從 column 屬性指定的列中檢索數(shù)據(jù),作為參數(shù)傳遞給目標 select 語句。 fetchType false String fetchType屬性用于控制子對象的加載行為,他有l(wèi)azy和eager兩個取值,分別對應著懶加載和立即加載. fetchType屬性的優(yōu)先級要高于配置全局懶加載的屬性lazyLoadingEnabled,當指定了fetchType屬性之后,lazyLoadingEnabled的配置將會被忽略。 -
描述嵌套結果映射的屬性定義
屬性名稱 必填 類型 描述 resultMap false String 它指向了一個標準的resultMap元素配置 columnPrefix false String columnPrefix屬性的值將會作用在被引用的resultMap配置上,在匹配其column屬性時,會先添加統(tǒng)一的前綴,之后再進行匹配操作。 notNullColumn false String notNullColumn屬性的取值是以,分隔的多個屬性名稱,只有在這些屬性均不為空的前提下,子對象才會被創(chuàng)建. autoMapping false boolean autoMapping屬性的行為和resultMap元素的類似,都是用于配置當前結果映射的自動映射行為。 需要注意的是,通過select和resultMap屬性引用的結果映射是不受該屬性的影響的。 -
描述多結果集的屬性定義
屬性名稱 必填 類型 描述 resultSet true String 當前association元素將會使用resultSet屬性對應的ResultSet對象來加載 foreignColumn true String foreignColumn屬性用于指定在映射時需要使用的父對象的數(shù)據(jù)列名稱,如果有多個數(shù)據(jù)列,使用,進行分隔. column true String column屬性的命名規(guī)則同foreignColumn屬性一致,它用于指定在映射時需要使用的子對象的數(shù)據(jù)列名稱.
負責一對多映射的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元素用于表示一對多的復雜對象關系,根據(jù)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;
}
用戶和角色關系通過一張用戶角色關系表來維護:
/* ======================== 插入用戶數(shù)據(jù) =============================*/
drop table USER if exists;
create table USER
(
id int,
name varchar(20)
);
insert into USER (id, name)
values (1, 'Panda');
/* ======================== 插入角色數(shù)據(jù) =============================*/
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, '普通用戶');
/* ======================== 插入用戶角色數(shù)據(jù) =============================*/
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屬性將不會被賦值.
產(chǎn)生這種差異的原因在于,為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對象的數(shù)據(jù):

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