Mybatis源碼之美:3.5.2.負責(zé)一對一映射的association元素和負責(zé)一對多映射的collection元素

負責(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元素配置UserRole兩個對象之間的關(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>

提供一個包含了UserRole數(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é)果:

運行結(jié)果

具體的代碼可以參見單元測試:單元測試AssociationTestone2One()方法录平。

詳細訪問地址:https://gitee.com/topanda/mybatis-3/tree/master/src/test/java/org/apache/learning/result_map/association

在上面的示例代碼中,我們使用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璃哟、javaTypejdbcTypeTypeHandler四個屬性喊递。

這四個屬性在定義和作用上都和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屬性用于控制子對象的加載行為荠藤,他有lazyeager兩個取值,分別對應(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ù)名為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)
);

為了區(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)id2的用戶數(shù)據(jù)藻治,因為Rolename屬性沒有設(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é)果映射的自動映射行為。

需要注意的是高职,通過selectresultMap屬性引用的結(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é)果集:

hsqldb

通常來講,我們一次數(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");

    }
}

MultiResultSetStoredProceduresgetAllUserAndRoles()方法在實現(xiàn)上會分別查詢出USERROLE兩個表中的數(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)該指向一個集合類型,因此,我們需要一個字段來描述集合中存儲的對象類型.

mybatiscollection元素添加了一個額外的ofType屬性,這個屬性的作用就是用來描述集合中對象的類型的.

我們看一個簡單的完整示例.

我們變更在associationUserRole對象的關(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ù):

User對象

結(jié)束

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

告辭

關(guān)注我,一起學(xué)習(xí)更多知識

關(guān)注我
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坚踩,一起剝皮案震驚了整個濱河市墓阀,隨后出現(xiàn)的幾起案子毡惜,更是在濱河造成了極大的恐慌,老刑警劉巖斯撮,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件经伙,死亡現(xiàn)場離奇詭異,居然都是意外死亡勿锅,警方通過查閱死者的電腦和手機帕膜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溢十,“玉大人垮刹,你說我怎么就攤上這事〔柘” “怎么了危纫?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵宗挥,是天一觀的道長乌庶。 經(jīng)常有香客問我,道長契耿,這世上最難降的妖魔是什么瞒大? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮搪桂,結(jié)果婚禮上透敌,老公的妹妹穿的比我還像新娘。我一直安慰自己踢械,他們只是感情好酗电,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著内列,像睡著了一般撵术。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上话瞧,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天嫩与,我揣著相機與錄音寝姿,去河邊找鬼。 笑死划滋,一個胖子當(dāng)著我的面吹牛饵筑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播处坪,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼根资,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了同窘?” 一聲冷哼從身側(cè)響起嫂冻,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎塞椎,沒想到半個月后桨仿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡案狠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年服傍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骂铁。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡吹零,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拉庵,到底是詐尸還是另有隱情灿椅,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布钞支,位于F島的核電站茫蛹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏烁挟。R本人自食惡果不足惜婴洼,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望撼嗓。 院中可真熱鬧柬采,春花似錦、人聲如沸且警。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斑芜。三九已至肩刃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背树酪。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工浅碾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人续语。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓垂谢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疮茄。 傳聞我的和親對象是個殘疾皇子滥朱,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 1. 簡介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL力试、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,429評論 0 4
  • SQL 映射文件的頂級元素(按照它們應(yīng)該被定義的順序): cache – 給定命名空間的緩存配置徙邻。 cache-r...
    悠揚前奏閱讀 694評論 0 0
  • 基于Vue.js 2.x系列 + Element UI 的后臺管理系統(tǒng)解決方案。 前言 之前在公司用了Vue + ...
    前端一菜鳥閱讀 848評論 0 18
  • 5月11日 西寧 塔爾寺 早上高鐵從嘉峪關(guān)到西寧 出站,青海的天氣很涼怖糊,像是初春帅容,陽光卻是很明媚,這是我最喜歡的天...
    跟著啦啦去旅行閱讀 264評論 0 2
  • 晚自習(xí)的時候伍伤,教室里很安靜并徘,空氣中彌漫著一種恬淡的氣息,孩子們在認真地寫作業(yè)扰魂,我在發(fā)呆麦乞,但是窗外汽車的鳴笛聲,附近...
    anxiangyqn閱讀 279評論 0 2