MyBatis實現一對一關聯對映

新夢想IT發表於2022-08-02


 

在現實生活中,一對一關聯關係是十分常見的。例如,一個人只能有一個身份證,同時一個身份證也只會對應一個人。

那麼使用 MyBatis是怎麼處理這種一對一關聯關係的呢?在前面講解的<resultMap>元素中,包含了一

<association>子元素,MyBatis就是透過該元素來處理一對一關聯關係的。

<association>元素中,通常可以配置以下屬性。

·  property:指定對映到的實體類物件屬性,與表欄位一一對應。

·  column:指定表中對應的欄位。

·  javaType:指定對映到實體物件屬性的型別。

·  select:指定引入巢狀查詢的子SQL語句,該屬性用於關聯對映中的巢狀查詢。

·  fetchType:指定在關聯查詢時是否啟用延遲載入。fetchType屬性有lazy和eager兩個屬性值,預設值為lazy(即預設關聯對映延遲載入)。

 

<association>元素的使用非常簡單,只需要參考如下兩種示例配置即可,具體如下。

1 <!-- 方式一:巢狀查詢-->

2   <resultMap type="IdCard" id="IdCardById">

3         <id property="id" column="id" />

4         <result property="userName" column="userName" />

5         <!-- 一對一:association使用select屬性引入另外一條SQL語句 -->

6         <association property="user" column="uid" javaType="User"

7             select="cn.dsscm.dao.UserMapper.findUserById" />

8    </resultMap>

9 <!-- 方式二:巢狀結果 -->

10     <resultMap type="IdCard" id="userRoleResult2">

11         <id property="id" column="id"/>

12         <result property="code" column="code"/>

13         <association property="user" javaType="User">

14             <id property="id" column="cid"/>

15             <result property="userName" column="userName" />

16         </association>

17 </resultMap>

 

MyBatis在對映檔案中載入關聯關係物件主要透過兩種方式:巢狀查詢和巢狀結果。巢狀查詢是指透過執行另外一條SQL對映語句來返回預期的複雜型別;巢狀結果是使用巢狀結果對映來處理重複的聯合結果的子集。開發人員可以使用上述任意一種方式實現對關聯關係的載入。

 

示例:使用者和身份證間關聯

瞭解了 MyBatis中處理一對一關聯關係的元素和方式後,接下來就以使用者和身份證之間的一對一關聯關係為例,進行詳細講解。

查詢個人及其關聯的身份證資訊是先透過查詢個人表中的主鍵來獲個人資訊,然後透過表中的外來鍵,來獲取證件表中的身份證號資訊。其具體實現步驟如下。

建立資料表,在 dsscm資料庫中重新建立名為tb_idcard的資料表,同時預先插入兩條資料。其執行的SQL語句如下所示。

1 USE dsscm;

2 #建立一個名稱為tb idcard 的表

3 CREATE TABLE `tb_idcard` (

4    `id` int(11) NOT NULL AUTO_INCREMENT,

5    `uid` int(11) NOT NULL,

6    `CODE` varchar(18) DEFAULT NULL,

7    PRIMARY KEY (`id`)

8  );

9 #插入n條資料

10 INSERT INTO tb_idcard(uid ,CODE) VALUES (1,'430101200001011234');

11 INSERT INTO tb_idcard(uid ,CODE) VALUES (2,'430101200001014321');

12 INSERT INTO tb_idcard(uid ,CODE) VALUES (3,'430101200001011235');

13 INSERT INTO tb_idcard(uid ,CODE) VALUES (4,'430101200001014326');

14 ......

 

完成上述操作後,資料庫 tb_idcard表中的資料。

在專案的 cn.dsscm.pojo包下建立持久化類IdCard,編輯後的程式碼,如示例所示。

1  【示例1】  IdCard.java

2 public class IdCard {

3     private Integer id; // id

4     private Integer uid; // 使用者id

5     private String code;// 身份證號碼

6

7     private User user;// 一對一

8     //省略getter和setter方法

9 }

 

在上述示例中,分別定義了各自的屬性以及對應的 getter/setter方法,同時為了方便檢視輸出結果還重寫了toString()方法。

cn.dsscm.mapper包中,建立證件對映檔案IdCardMapper.xml和使用者對映檔案UserMapper.xml,並在兩個對映檔案中編寫一對一關聯對映查詢的配置資訊,如示例所示。

1  【示例2】  IdCardMapper.xml

2 <?xml version="1.0" encoding="UTF-8"?>

3 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

4 "

5 <mapper namespace="cn.dsscm.dao.IdCardMapper">

6   <!-- 巢狀查詢:透過執行另外一條SQL對映語句來返回預期的特殊型別 -->

7   <select id="findCodeById" parameterType="Integer" resultMap="IdCardById">

8       SELECT * FROM tb_idcard WHERE id=#{id}

9   </select>

10   <resultMap type="IdCard" id="IdCardById">

11         <id property="id" column="id" />

12         <result property="userName" column="userName" />

13         <!-- 一對一:association使用select屬性引入另外一條SQL語句 -->

14         <association property="user" column="uid" javaType="User"

15             select="cn.dsscm.dao.UserMapper.findUserById" />

16    </resultMap>

17 </mapper>

 

1 【示例3】  UserMapper.xml

2 <?xml version="1.0" encoding="UTF-8"?>

3 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

4 "

5 <mapper namespace="cn.dsscm.dao.UserMapper">

6      <!-- 根據id查詢使用者資訊 -->

7     <select id="findUserById" parameterType="Integer" resultType="User">

8         SELECT * from tb_user where id=#{id}

9     </select>

10 </mapper>

 

在上述兩個對映檔案中,使用了 MyBatis中的巢狀查詢方式進行了個人及其關聯的證件資訊查詢,因為返回的個人物件中除了基本屬性外還有一個關聯的uid屬性,所以需要手動編寫結果對映。從對映檔案IdCardMapper.xml中可以看出,巢狀查詢的方法是先執行一個簡單的SQL語句,然後在進行結果對映時,將關聯物件在<association>元素中使用select屬性執行另一條SQL語句(即 IdCardMapper.xml中的SQL )。

建立對映檔案的對應介面如下:

public  List< IdCard >  findCodeById (@Param(" id ")Integer id);

 

在核心配置檔案 mybatis-config.xml中,引入Mapper對映檔案並定義別名,如下所示。

1 【示例4】  mybatis-config.xml

2     <!-- 將mapper檔案加入到配置檔案中 -->

3     <mappers>

4         <mapper resource="cn/dsscm/dao/UserMapper.xml" />

5         <mapper resource="cn/dsscm/dao/IdCardMapper.xml" />

6     </mappers>

 

在上述核心配置檔案中,首先引入了資料庫連線的配置檔案,然後使用掃描包的形式自定義別名,接下來進行環境的配置,最後配置了 Mapper對映檔案的位置資訊。

在測試包中,建立測試類 UserMapperTest,並在類中編寫 測試方法 getUserListByIdTest (),如下所示。

 

1  【示例5】  UserMapperTest.java

2     @Test

3     public void getUserListByIdTest(){

4         SqlSession sqlSession = null;

5         List<IdCard> userList = new ArrayList<IdCard>();

6         Integer id = 3;

7         try {

8             sqlSession = MyBatisUtils.createSqlSession();

9             userList = sqlSession.getMapper(IdCardMapper.class).findCodeById(id);

10         } catch (Exception e) {

11             // TODO: handle exception

12             e.printStackTrace();

13         }finally{

14             MyBatisUtils.closeSqlSession(sqlSession);

15         }

16

17          logger.debug("getUserListByRoleIdTest userList.size : " + userList.size());

18         for(IdCard user:userList){

19             logger.debug(user);

20         }

21 }

 

getUserListByIdTest()方法中,首先透過MybatisUtils工具類獲取了SqlSession物件,然後透過SqlSession物件的介面方法獲取了使用者資訊。為了檢視結果,這裡使用了輸出語句輸出查詢結果資訊。最後程式執行完畢時,關閉了SqlSession。

使用 JUnit4執行getUserListByIdTest()方法後,控制檯的輸出結果如下:

1 cn.dsscm.dao.IdCardMapper.findCodeById - ==>  Preparing: SELECT * FROM tb_idcard WHERE id=? 

2 cn.dsscm.dao.IdCardMapper.findCodeById - ==> Parameters: 3(Integer)

3 ......

4 cn.dsscm.dao.UserMapper.findUserById - ==>  Preparing: SELECT * from tb_user where id=? 

5 cn.dsscm.dao.UserMapper.findUserById - ==> Parameters: 3(Integer)

6 ......

7 cn.dsscm.test.UserMapperTest - getUserListByRoleIdTest userList.size : 1

8 cn.dsscm.test.UserMapperTest - IdCard [id=3, uid=null, code=430101200001011235, user=User [id=3, userCode=zhangwei, userName=張偉,userPassword=0000000, birthday=Sun Jun 05 00:00:00 CST 1994, gender=2, phone=18567542321, email=null, address=北京市朝陽區,userDesc=null, userRole=2, createdBy=1, imgPath=null, creationDate=Thu Oct 24 13:01:51 CST 2019, modifyBy=null, modifyDate=null,age=null, userRoleName=null]]

 

從控制檯的輸出結果可以看出,使用 MyBatis巢狀查詢的方式查詢出了使用者身份證資訊及其使用者的資訊,這就是MyBatis中的一對一關聯查詢。

修改程式碼,使用 MyBatis巢狀結果的方式查詢出了使用者身份證資訊及其使用者的資訊。

建立對映檔案的對應介面如下:

1 public List<IdCard> findCodeById2(@Param("uid")Integer id);

 

cn.dsscm.mapper包中,修改證件對映檔案IdCardMapper.xml,並在對映檔案中使用MyBatis巢狀結果編寫一對一關聯對映查詢的配置資訊,如示例所示。

1  【示例6】  IdCardMapper.xml

2     <!-- 根據roleId獲取使用者列表 association start-->

3     <resultMap type="IdCard" id="userRoleResult2">

4         <id property="id" column="id"/>

5         <result property="code" column="code"/>

6         <association property="user" javaType="User">

7             <id property="id" column="cid"/>

8             <result property="userName" column="userName" />

9         </association>

10     </resultMap>

11     <select id="findCodeById2" parameterType="Integer" resultMap="userRoleResult2">

12         SELECT u.* ,c.id cid ,c.code

13           FROM tb_user u, tb_idcard c

14         WHERE u.id=c.uid

15                    AND c.uid= #{uid}

16     </select>

 

在測試包中,在類中編寫 測試方法 getUserListByIdTest2(),如下所示。

1  【示例7】  UserMapperTest.java

2     @Test

3     public void getUserListByIdTest2(){

4         SqlSession sqlSession = null;

5         List<IdCard> userList = new ArrayList<IdCard>();

6         Integer id = 3;

7         try {

8             sqlSession = MyBatisUtils.createSqlSession();

9             userList = sqlSession.getMapper(IdCardMapper.class).findCodeById2(id);

10         } catch (Exception e) {

11             // TODO: handle exception

12             e.printStackTrace();

13         }finally{

14             MyBatisUtils.closeSqlSession(sqlSession);

15         }

16         logger.debug("getUserListByRoleIdTest userList.size : " + userList.size());

17         for(IdCard user:userList){

18             logger.debug(user);

19         }

20 }

 

使用 JUnit4執行getUserListByIdTest2()方法後,控制檯的輸出結果如下:

1 cn.dsscm.dao.IdCardMapper.findCodeById2 - ==>  Preparing: SELECT u.* ,c.id cid ,c.code FROM tb_user u, tb_idcard c WHERE u.id=c.uid AND c.uid= ? 

2 cn.dsscm.dao.IdCardMapper.findCodeById2 - ==> Parameters: 3(Integer)

3 ......

4 cn.dsscm.test.UserMapperTest - getUserListByRoleIdTest userList.size : 1

5 cn.dsscm.test.UserMapperTest - IdCard [id=3, uid=null, code=430101200001011235, user=User [id=3, userCode=null, userName=張偉, userPassword=null, birthday=null, gender=null, phone=null, email=null, address=null, userDesc=null, userRole=null, createdBy=null, imgPath=null, creationDate=null, modifyBy=null, modifyDate=null, age=null, userRoleName=null]]

 

上述示例使用身份證類關聯使用者資訊,改變實體類用使用者類關聯身份證類也可以實現同樣效果,此處不再贅述。

雖然使用巢狀查詢的方式比較簡單,但是從控制檯的輸出結果中可以看出, MyBatis巢狀查詢的方式要執行多條SQL語句,這對於大型資料集合和列表展示不是很好,因為這樣可能會導致成百上千條關聯的SQL語句被執行,從而極大地消耗資料庫效能並且會降低查詢效率。這並不是開發人員所期望的。為此,我們可以使用MyBatis提供的巢狀結果方式,來進行關聯查詢。

 

示例:使用者和使用者角色關聯

association:對映到JavaBean的某個"複雜型別"屬性,比如JavaBean類,即JavaBean內部巢狀一個複雜資料型別(JavaBean)屬性,這種情況就屬於複雜型別的關聯。但是需要注意:association僅處理一對一的關聯關係。

 

在實際的開發專案中此類絕對的雙向的一對一的關聯比較少見,很多時候是單向的。比如使用者角色和使用者列表關係,從不同角度看對映關係不一樣,這裡面涉及使用者表( tb_user)和使用者許可權表(tb_role),從使用者角度關聯許可權資訊這是一對一,從使用者許可權關聯使用者資訊是一對多。如果根據使用者角色id獲取該角色下的使用者列表的情況,我們只需要根據使用者表關聯使用者角色表,association便可以處理此種情況下一對一的關聯關係,那麼對於使用者角色關聯使用者資訊的一對多的關聯關係的處理,則需要collection元素來實現了,這個後面介紹。

建立資料表,在 dsscm資料庫中重新建立名為tb_idcard的資料表,同時預先插入兩條資料。其執行的SQL語句如下所示。

1 #使用者表

2 CREATE TABLE `tb_user` (

3    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',

4    `userCode` varchar(15) NOT NULL COMMENT '使用者編碼',

5    `userName` varchar(15) NOT NULL COMMENT '使用者名稱稱',

6    `userPassword` varchar(15) NOT NULL COMMENT '使用者密碼',

7    `gender` int(10) DEFAULT NULL COMMENT '性別(1:女、 2:男)',

8    `birthday` date DEFAULT NULL COMMENT '出生日期',

9    `email` varchar(50) DEFAULT NULL COMMENT '郵箱',

10    `phone` varchar(15) COMMENT '手機',

11    `address` varchar(30) COMMENT '地址',

12    `userDesc` text COMMENT '簡介',

13    `userRole` int(10) DEFAULT NULL COMMENT '使用者角色(取自角色表-角色id)',

14    `imgPath` varchar(100) DEFAULT NULL COMMENT '使用者照片',

15    `createdBy` bigint(20) DEFAULT NULL COMMENT '建立者(userId)',

16    `creationDate` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',

17   `modifyBy` bigint(20) DEFAULT NULL COMMENT '更新者(userId)',

18   `modifyDate` datetime DEFAULT NULL COMMENT '更新時間',

19    PRIMARY KEY (`id`),

20    UNIQUE KEY `userCode` (`userCode`)

21 );

22 

23 #使用者許可權表

24 CREATE TABLE `tb_role` (

25    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',

26    `roleCode` varchar(50) NOT NULL COMMENT '角色編碼',

27    `roleName` varchar(50) NOT NULL COMMENT '角色名稱',

28    `createdBy` bigint(20) DEFAULT NULL COMMENT '建立者',

29    `creationDate` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',

30    `modifyBy` bigint(20) DEFAULT NULL COMMENT '修改者',

31    `modifyDate` datetime DEFAULT NULL COMMENT '修改時間',

32    PRIMARY KEY (`id`),

33    UNIQUE KEY `roleCode` (`roleCode`)

34  );

35

 

首先建立 Role類,並增加相應的getter和setter方法,示例程式碼如下:

1  【示例8】  Role.java

2 public class Role {

3     private Integer id; // id

4     private String roleCode; // 角色編碼

5     private String roleName; // 角色名稱

6     private Integer createdBy; // 建立者

7     private Date creationDate; // 建立時間

8     private Integer modifyBy; // 更新者

9     private Date modifyDate;// 更新時間

10     //省略getter和setter方法

11 }

 

修改 User類,增加角色屬性(Role role),並增加相應的getter和setter方法;註釋掉使用者角色名稱屬性(String userRoleName),並註釋掉其getter和setter方法,示例程式碼如下:

1  【示例9】  User.java

2 public class User {

3     private Integer id; //id 

4     private String userCode; //使用者編碼

5     private String userName; //使用者名稱稱

6     private String userPassword; //使用者密碼

7     private Integer gender;  //性別

8     private Date birthday;  //出生日期

9     private String phone;   //電話

10     private String address; //地址

11     private Integer userRole;    //使用者角色ID

12     private Integer createdBy;   //建立者

13     private Date creationDate; //建立時間

14     private Integer modifyBy;     //更新者

15     private Date modifyDate;   //更新時間

16

17     private Integer age;//年齡

18     //private String userRoleName; //使用者角色名稱

19

20     //association

21     private Role role; //使用者角色

22

23     //省略getter和setter方法

24 }

 

透過以上改造,我們的 JavaBean:User物件內部巢狀了一個複雜資料型別的屬性:role。接下來在UserMapper介面裡增加根據角色id獲取使用者列表的方法,程式碼如下:

1 public List<User> getUserListByRoleId(@Param("userRole")Integer roleId);

 

修改對應 UserMapper.xml,增加getUserListByRoleId,該select查詢語句返回型別為resultMap,並且外部引用的resultMap的型別為User。由於User物件內嵌JavaBean物件(Role),因此需要使用association來實現結果對映。程式碼如下:

1  【示例10】  UserMapper.xml

2     <!-- 根據roleId獲取使用者列表 association start-->

3     <resultMap type="User" id="userRoleResult">

4         <id property="id" column="id"/>

5         <result property="userCode" column="userCode" />

6         <result property="userName" column="userName" />

7         <result property="userRole" column="userRole" />

8         <association property="role" javaType="Role">

9             <id property="id" column="r_id"/>

10             <result property="roleCode" column="roleCode"/>

11             <result property="roleName" column="roleName"/>

12         </association>

13     </resultMap>

14     <select id="getUserListByRoleId" parameterType="Integer" resultMap="userRoleResult">

15         select u.*,r.id as r_id,r.roleCode,r.roleName

16           from tb_user u,tb_role r 

17         where u.userRole = #{userRole} and u.userRole = r.id

18     </select>

 

從上述程式碼,簡單分析 association的屬性。

 

·  javaType:完整Java類名或者別名。若對映到一個JavaBean,則MyBatis通常會自行檢測到其型別;若對映到一個HashMap,則應該明確指定javaType,來確保所需行為。此處為Role。

·  property :對映資料庫列的實體物件的屬性。此處為在User裡定義的屬性:role。

·  association的子元素如下: id; result;property:對映資料庫列的實體物件的屬性。此處為Role的屬性; column:資料庫列名或別名。

 

在做結果對映的過程中,需要注意:要確保所有的列名都是唯一且無歧義的。 id子元素在巢狀結果對映中扮演了非常重要的角色,應該指定一個或者多個屬性來唯一標識這個結果集。實際上,即便沒有指定id,MyBatis也會工作,但是會導致嚴重的效能開銷,所以最好選擇儘量少的屬性來唯一標識結果,主鍵或者聯合主鍵均可。

最後修改測試類 UserMapperTest.java,增加測試方法,示例程式碼如下:

1  【示例11】  UserMapperTest.java

2     @Test

3     public void getUserListByRoleIdTest(){

4         SqlSession sqlSession = null;

5         List<User> userList = new ArrayList<User>();

6         Integer roleId = 3;

7         try {

8             sqlSession = MyBatisUtils.createSqlSession();

9             userList = sqlSession.getMapper(UserMapper.class).getUserListByRoleId(roleId);

10         } catch (Exception e) {

11             // TODO: handle exception

12             e.printStackTrace();

13         }finally{

14             MyBatisUtils.closeSqlSession(sqlSession);

15         }

16 

17         logger.debug("getUserListByRoleIdTest userList.size : " + userList.size());

18         for(User user:userList){

19             logger.debug("userList =====> userName: " + user.getUserName() 

20                 +", <未做對映欄位>userPassworD. " + user.getUserPassword()

21                 + ", Role: " + user.getRole().getId() + " --- " 

22                 + user.getRole().getRoleCode() +" --- " + user.getRole().getRoleName());

23         }

24     }

 

測試方法 中呼叫 getUserListByRoleId()方法獲取userList,並進行結果輸出,關鍵是對映的使用者角色相關資訊。

1 cn.dsscm.dao.UserMapper.getUserListByRoleId - ==>  Preparing: select u.*,r.id as r_id, r.roleCode ,r.roleName from tb_user u,tb_role r where u.userRole = ? and u.userRole = r.id 

2 cn.dsscm.dao.UserMapper.getUserListByRoleId - ==> Parameters: 3(Integer)

3 ......

4 cn.dsscm.test.UserMapperTest - getUserListByRol

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69940641/viewspace-2908628/,如需轉載,請註明出處,否則將追究法律責任。

相關文章