負責(zé)一對一映射的association元素和負責(zé)一對多映射的collection元素
負責(zé)一對一映射的association元素
association
元素的簡單應(yīng)用
在大多數(shù)業(yè)務(wù)場景下,我們的PO
都是一個簡單的javaBean
定義钱贯,他的屬性定義基本都是簡單屬性定義测僵。
但是有些時候,我們可能會需要定義一個較為復(fù)雜的PO
谬以,這個PO
中的某些屬性可能會是另一個PO
定義。
association
元素就被應(yīng)用在這種場景下,它用于關(guān)聯(lián)兩個具有一對一關(guān)系的復(fù)雜java
對象。
為了簡化描述和理解贵少,我將外層對象稱之為父對象,被關(guān)聯(lián)的內(nèi)部對象稱之為子對象堆缘。
我們通過一個簡單的示例來看一下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
對象為子對象吼肥。
他們對應(yīng)的表結(jié)構(gòu)和初始數(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
兩個對象之間的關(guān)系:
<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>
運行結(jié)果:
具體的代碼可以參見單元測試:單元測試AssociationTest
的one2One()
方法录平。
在上面的示例代碼中,我們使用association
元素綁定了User
對象和Role
對象之間的關(guān)系缀皱,并成功的在一次方法調(diào)用中獲得了兩個完整的對象斗这。
association
元素的DTD
定義
association
元素的DTD
定義看起來要比result
元素復(fù)雜的多:
<!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
應(yīng)該如何利用現(xiàn)有數(shù)據(jù)獲取到子對象所需的數(shù)據(jù)握童,以及如何將所需數(shù)據(jù)轉(zhuǎn)換為子對象姆怪。
association
元素提供了三種方式來描述這一過程,他們分別是:嵌套查詢語句
澡绩、嵌套結(jié)果映射
以及多結(jié)果集配置
稽揭。
上面三種方式可能聽起來比較陌生,沒關(guān)系肥卡,我們接下來就會詳細的了解這三種不同的方式溪掀。
association
元素的屬性定義
association
元素具有十三個屬性定義,這些屬性根據(jù)作用可以分為四類:
- 通用型功能性查詢屬性定義
- 描述嵌套查詢語句的屬性定義
- 描述嵌套結(jié)果映射的屬性定義
- 描述多結(jié)果集的屬性定義
通用型功能性查詢屬性定義
我們先來看通用型功能性查詢屬性定義步鉴。
和result
元素一樣揪胃,association
元素也定義了property
璃哟、javaType
、jdbcType
和TypeHandler
四個屬性喊递。
這四個屬性在定義和作用上都和result
元素中完全一致随闪,因此這里就不在贅述。
描述嵌套查詢語句的屬性定義
負責(zé)配置嵌套查詢語句的是三個可選的屬性骚勘,他們分別是column
铐伴、select
以及fetchType
。
在使用嵌套查詢語句的場景下
column
俏讹、select
兩個屬性均是必填的当宴。
select
屬性指向一個標準的select
語句,比如:
<select id="selectRoleById" resultMap="role">
SELECT *
FROM role r
WHERE r.id = #{id}
</select>
在select
語句中可能會包含一些行內(nèi)參數(shù)映射泽疆,比如selectRoleById
中的#{id}
定義,行內(nèi)參數(shù)映射所需的數(shù)據(jù)我們可以通過column
屬性來進行配置户矢。
association
元素的column
屬性的作用和result
元素中的稍有不同,association
元素的column
屬性可以是普通的列名稱定義于微,比如column="id"
,也可以是一個復(fù)合的屬性描述逗嫡,比如:column="{prop1=col1,prop2=col2}"
青自。
復(fù)合屬性描述的語法定義為:以{
開始株依,}
結(jié)尾,中間通過,
分隔多個屬性描述延窜,每個屬性描述均由行內(nèi)參數(shù)映射名
,=
,列名稱
三部分構(gòu)成恋腕。
行內(nèi)參數(shù)映射名
對應(yīng)的是select
語句中的行內(nèi)參數(shù)映射,列名稱則對應(yīng)著父對象中的數(shù)據(jù)列名稱逆瑞。
最后一個fetchType
屬性用于控制子對象的加載行為荠藤,他有lazy
和eager
兩個取值,分別對應(yīng)著懶加載和立即加載获高。
fetchType
屬性的優(yōu)先級要高于配置全局懶加載的屬性lazyLoadingEnabled
,當(dāng)指定了fetchType
屬性之后哈肖,lazyLoadingEnabled
的配置將會被忽略。
我們在上文中創(chuàng)建的單元測試中繼續(xù)進行簡單的測試工作念秧。
測試常規(guī)嵌套查詢
新增一個配置了嵌套查詢的resultMap
以及resultMap
對應(yīng)的兩個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());
}
測試復(fù)合屬性描述
創(chuàng)建一個association
元素的column
屬性為{id=role_id}
的resultMap
定義:
<!-- 測試嵌套查詢 - 復(fù)合屬性描述-->
<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());
}
測試懶加載屬性
復(fù)用上面的代碼創(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());
}
運行結(jié)果:
可以看到淤井,雖然我們禁用了全局懶加載配置,但是在本次方法調(diào)用中依然成功啟用了懶加載摊趾。
描述嵌套結(jié)果映射的屬性定義
在本篇文章的最開始我們就已經(jīng)接觸到了嵌套結(jié)果映射
的使用方式币狠。
負責(zé)配置嵌套結(jié)果映射
的是四個可選的屬性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>
查詢到的結(jié)果:
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());
}
關(guān)鍵運行日志:
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
屬性沒有設(shè)置,所以他的role
也沒有被實例化巷挥。
除了上面的三個屬性之外桩卵,association
元素還有一個比較特殊的屬性autoMapping
。
我們前面說過association
元素是一個特殊的resultMap
元素倍宾,它具有和resultMap
元素一樣的子元素定義,因此我們可以直接通過association
元素的子元素來聲明一個嵌套結(jié)果映射:
<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
元素的類似雏节,都是用于配置當(dāng)前結(jié)果映射的自動映射行為。
需要注意的是高职,通過select
和resultMap
屬性引用的結(jié)果映射是不受該屬性的影響的钩乍。
描述多結(jié)果集的屬性定義
association
元素的最后一類屬性是用來描述多結(jié)果集的.
多結(jié)果集就目前來看,在實際業(yè)務(wù)中,我?guī)缀鯖]有用到過.但是這并不妨礙我們?nèi)W(xué)習(xí)和了解他,有些時候,這些偏門的知識可能會有大用處喲.
用于描述多結(jié)果集的屬性有三個,他們分別是column
,foreignColumn
以及resultSet
.
多結(jié)果集
在了解這些屬性的作用之前,我們先了解一下什么是多結(jié)果集?
多結(jié)果集就是:我們可以通過執(zhí)行一次數(shù)據(jù)庫操作,獲取到多個ResultSet
對象.
根據(jù)JDBC
規(guī)范,我們可以通過connection.getMetaData().supportsMultipleResultSets();
方法來查看當(dāng)前數(shù)據(jù)源是否支持多結(jié)果集:
通常來講,我們一次數(shù)據(jù)庫操作只能得到一個ResultSet
對象,但是部分數(shù)據(jù)庫支持在一次查詢中返回多個結(jié)果集.
還有部分數(shù)據(jù)庫支持在存儲過程中返回多個結(jié)果集,或者支持一次性執(zhí)行多個語句,每個語句都對應(yīng)一個結(jié)果集.
對應(yīng)的場景可能有些多,這里我們主要還是看存儲過程中的多結(jié)果集配置:
我們先創(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
對象.
關(guān)于更多
hsqldb
存儲過程的內(nèi)容,可以訪問鏈接進行學(xué)習(xí):http://hsqldb.org/doc/2.0/guide/sqlroutines-chapt.html#src_psm_handlers.
之后我們在CreateDB.sql
新增一條關(guān)于存儲過程的配置:
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
,并打印出來.
關(guān)鍵運行日志:
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()
成功的返回兩個結(jié)果集.
屬性
在了解resultSet
屬性之前,我們需要簡單補充一下select
元素的resultSets
屬性相關(guān)的知識.
默認情況下,一條select
語句對應(yīng)一個結(jié)果集,因此我們不需要關(guān)注結(jié)果集相關(guān)的問題.
但是,通過實驗,我們已經(jīng)成功的在一條select
語句中返回了多個結(jié)果集,如果我們想操作不同的結(jié)果集的數(shù)據(jù),我們就有必要區(qū)分出每個結(jié)果集對象.
mybaits
為這種場景提供了一個解決方案,它允許我們在配置select
元素的時候,通過配置其resultSets
屬性來為每個結(jié)果集指定名稱.
結(jié)果集的名稱和resultSets
屬性定義順序?qū)?yīng).如果有多個結(jié)果集的名稱需要配置,名稱之間使用,
進行分隔.
比如,在下面的示例代碼中,第一個ResultSet
名為users
,第二個ResultSet
名為roles
:
<select id="selectAllUserAndRole" resultSets="users,roles" resultMap="userRoleWithResultSet"
statementType="CALLABLE">
{call getAllUserAndRoles() }
</select>
association
元素提供的resultSet
屬性讀取的就是resultSets
屬性定義的名稱,當(dāng)前association
元素將會使用resultSet
屬性對應(yīng)的ResultSet
對象來加載.
需要注意的是,association
元素的column
屬性在多結(jié)果集模式
下的表現(xiàn)和在嵌套查詢語句模式
下的表現(xiàn)稍有不同.
在多結(jié)果集模式
下,column
屬性將會配合著foreignColumn
屬性一起使用.
foreignColumn
屬性用于指定在映射時需要使用的父對象的數(shù)據(jù)列名稱,如果有多個數(shù)據(jù)列,使用,
進行分隔.
column
屬性的命名規(guī)則同foreignColumn
屬性一致,它用于指定在映射時需要使用的子對象的數(shù)據(jù)列名稱.
foreignColumn
屬性和column
屬性之間是順序關(guān)聯(lián)的.
多結(jié)果集模式
的應(yīng)用
最后,通過一個簡單的測試,來實際看一下多結(jié)果集模式
的應(yīng)用.
復(fù)用之前的代碼,我們在AssociationMapper.xml
文件中新增一個調(diào)用存儲過程的方法聲明以及相應(yīng)的resultMap
配置:
<!-- 測試多結(jié)果集-->
<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
中添加對應(yīng)的方法聲明:
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;
}
單元測試成功運行,并輸出下列關(guān)鍵日志:
...省略
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))]
...省略
總結(jié)
到這里我們就了解了association
元素的所有屬性定義.
至于association
元素的子元素定義,因為在定義上和用法上都和resultMap
元素完全一致.
所以在我們了解完resultMap
元素的子元素之后,自然而然就了解了關(guān)于association
元素的子元素定義.
最后,我們總結(jié)一下association
元素的屬性作用:
-
通用型功能性查詢屬性定義
屬性名稱 必填 類型 描述 property false String PO
對象的屬性名稱javaType false String PO
對象的屬性類型jdbcType false String 數(shù)據(jù)庫中的列類型 typeHandler false String 負責(zé)將數(shù)據(jù)庫數(shù)據(jù)轉(zhuǎn)換為 PO
對象的類型轉(zhuǎn)換器 -
描述嵌套查詢語句的屬性定義
屬性名稱 必填 類型 描述 column true String 用于配置行內(nèi)參數(shù)映射,column屬性可以是普通的列名稱定義,比如column="id",也可以是一個復(fù)合的屬性描述怔锌,比如:column="{prop1=col1,prop2=col2}" select true String 用于加載復(fù)雜類型屬性的映射語句的 ID寥粹,它會從 column 屬性指定的列中檢索數(shù)據(jù),作為參數(shù)傳遞給目標 select 語句产禾。 fetchType false String fetchType屬性用于控制子對象的加載行為排作,他有l(wèi)azy和eager兩個取值,分別對應(yīng)著懶加載和立即加載. fetchType屬性的優(yōu)先級要高于配置全局懶加載的屬性lazyLoadingEnabled,當(dāng)指定了fetchType屬性之后亚情,lazyLoadingEnabled的配置將會被忽略妄痪。 -
描述嵌套結(jié)果映射的屬性定義
屬性名稱 必填 類型 描述 resultMap false String 它指向了一個標準的resultMap元素配置 columnPrefix false String columnPrefix屬性的值將會作用在被引用的resultMap配置上,在匹配其column屬性時楞件,會先添加統(tǒng)一的前綴衫生,之后再進行匹配操作裳瘪。 notNullColumn false String notNullColumn屬性的取值是以,分隔的多個屬性名稱,只有在這些屬性均不為空的前提下罪针,子對象才會被創(chuàng)建. autoMapping false boolean autoMapping屬性的行為和resultMap元素的類似彭羹,都是用于配置當(dāng)前結(jié)果映射的自動映射行為阱高。 需要注意的是悲靴,通過select和resultMap屬性引用的結(jié)果映射是不受該屬性的影響的。 -
描述多結(jié)果集的屬性定義
屬性名稱 必填 類型 描述 resultSet true String 當(dāng)前association元素將會使用resultSet屬性對應(yīng)的ResultSet對象來加載 foreignColumn true String foreignColumn屬性用于指定在映射時需要使用的父對象的數(shù)據(jù)列名稱,如果有多個數(shù)據(jù)列,使用,進行分隔. column true String column屬性的命名規(guī)則同foreignColumn屬性一致,它用于指定在映射時需要使用的子對象的數(shù)據(jù)列名稱.
負責(zé)一對多映射的collection元素
既然有一對一的復(fù)雜對象關(guān)系,那自然也會有一對多的復(fù)雜對象關(guān)系,association
元素用來配置一對一的復(fù)雜關(guān)系,collection
元素則是用來配置一對多的復(fù)雜對象關(guān)系.
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
元素用于表示一對多的復(fù)雜對象關(guān)系,根據(jù)javaType
屬性的定義,javaType
屬性應(yīng)該指向一個集合類型,因此,我們需要一個字段來描述集合中存儲的對象類型.
mybatis
為collection
元素添加了一個額外的ofType
屬性,這個屬性的作用就是用來描述集合中對象的類型的.
我們看一個簡單的完整示例.
我們變更在association
中User
和Role
對象的關(guān)系,改為一個用戶可以擁有多個角色.
@Data
public class Role {
private Integer id;
private String name;
}
@Data
public class User {
private Integer id;
private String name;
private List<Role> roles;
}
用戶和角色關(guān)系通過一張用戶角色關(guān)系表來維護:
/* ======================== 插入用戶數(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);
編寫對應(yīng)的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
元素的映射結(jié)果:
@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);
}
單元測試運行的關(guān)鍵日志為:
... 省略 ...
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ù):
結(jié)束
至此,我們也算是初步了解了association
元素和collection
元素了.