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

負責一對一映射的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元素配置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數(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>

運行結果:

運行結果

具體的代碼可以參見單元測試:單元測試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定義,我們會發(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元素也定義了propertyjavaType、jdbcTypeTypeHandler四個屬性。

這四個屬性在定義和作用上都和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屬性用于控制子對象的加載行為,他有lazyeager兩個取值,分別對應著懶加載和立即加載。

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ù)名為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>

查詢到的結果:

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)id2的用戶數(shù)據(jù),因為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元素的最后一類屬性是用來描述多結果集的.

多結果集就目前來看,在實際業(yè)務中,我?guī)缀鯖]有用到過.但是這并不妨礙我們去學習和了解他,有些時候,這些偏門的知識可能會有大用處喲.

攤手

用于描述多結果集的屬性有三個,他們分別是column,foreignColumn以及resultSet.

多結果集

在了解這些屬性的作用之前,我們先了解一下什么是多結果集?


路過

多結果集就是:我們可以通過執(zhí)行一次數(shù)據(jù)庫操作,獲取到多個ResultSet對象.

根據(jù)JDBC規(guī)范,我們可以通過connection.getMetaData().supportsMultipleResultSets();方法來查看當前數(shù)據(jù)源是否支持多結果集:

hsqldb

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

    }
}

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

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;
}

用戶和角色關系通過一張用戶角色關系表來維護:

/* ========================  插入用戶數(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ù):

User對象

結束

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

告辭

關注我,一起學習更多知識

關注我
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

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

友情鏈接更多精彩內容