面試官:請講一下MyBatis是如何關(guān)聯(lián)關(guān)系?
文末本文轉(zhuǎn)載自微信公眾號「程序員千羽」,作者程序員千羽。轉(zhuǎn)載本文請聯(lián)系程序員千羽公眾號。
“GitHub:https://github.com/nateshao/ssm/tree/master/115-mybatis-associated-one-many
1. 關(guān)聯(lián)關(guān)系概述
為什么學(xué)習(xí)MyBatis關(guān)聯(lián)關(guān)系?
“實際的開發(fā)中,對數(shù)據(jù)庫的操作常常會涉及到多張表,這在面向?qū)ο笾芯蜕婕暗搅藢ο笈c對象之間的關(guān)聯(lián)關(guān)系。針對多表之間的操作,MyBatis提供了關(guān)聯(lián)映射,通過關(guān)聯(lián)映射就可以很好的處理對象與對象之間的關(guān)聯(lián)關(guān)系。所以,,這里將對MyBatis的關(guān)聯(lián)關(guān)系映射進行詳細的講解。
在關(guān)系型數(shù)據(jù)庫中,多表之間存在著三種關(guān)聯(lián)關(guān)系,分別為一對一、一對多和多對多,如下圖所示:
一對一:在任意一方引入對方主鍵作為外鍵;
一對多:在“多”的一方,添加“一”的一方的主鍵作為外鍵;
多對多:產(chǎn)生中間關(guān)系表,引入兩張表的主鍵作為外鍵,兩個主鍵成為聯(lián)合主鍵或使用新的字段作為主鍵。
在Java中,通過對象也可以進行關(guān)聯(lián)關(guān)系描述,如圖下圖所示:
2. 一對一
在現(xiàn)實生活中,一對一關(guān)聯(lián)關(guān)系是十分常見的。例如,一個人只能有一個身份證,同時一個身份證也只會對應(yīng)一個人。
那么使用MyBatis是怎么處理圖中的這種一對一關(guān)聯(lián)關(guān)系的呢?
在前面所講解的< resultMap >元素中,包含了一個< association >子元素,MyBatis就是通過該元素來處理一對一關(guān)聯(lián)關(guān)系的。
在< association >元素中,通常可以配置以下屬性:
property:指定映射到的實體類對象屬性,與表字段一 一對應(yīng)
column:指定表中對應(yīng)的字段
javaType:指定映射到實體對象屬性的類型
select:指定引入嵌套查詢的子SQL語句,該屬性用于關(guān)聯(lián)映射中的嵌套查詢
fetchType:指定在關(guān)聯(lián)查詢時是否啟用延遲加載。該屬性有l(wèi)azy和eager兩個屬性值,默認值為lazy(即默認關(guān)聯(lián)映射延遲加載)
MyBatis加載關(guān)聯(lián)關(guān)系對象主要通過兩種方式:嵌套查詢和嵌套結(jié)果。
第一種: 嵌套查詢是通過執(zhí)行另外一條SQL映射語句來返回預(yù)期的復(fù)雜類型。
- 嵌套查詢是在查詢SQL中嵌入一個子查詢SQL;
- 嵌套查詢會執(zhí)行多條SQL語句;
- 嵌套查詢SQL語句編寫較為簡單;
第二種: 嵌套結(jié)果是使用嵌套結(jié)果映射來處理重復(fù)的聯(lián)合結(jié)果的子集。
- 嵌套結(jié)果是一個嵌套的多表查詢SQL;
- 嵌套結(jié)果只會執(zhí)行一條復(fù)雜的SQL語句;
- 嵌套結(jié)果SQL語句編寫比較復(fù)雜;
“雖然使用嵌套查詢的方式比較簡單,但是嵌套查詢的方式要執(zhí)行多條SQL語句,這對于大型數(shù)據(jù)集合和列表展示不是很好,因為這樣可能會導(dǎo)致成百上千條關(guān)聯(lián)的SQL語句被執(zhí)行,從而極大的消耗數(shù)據(jù)庫性能并且會降低查詢效率。
多學(xué)一招:MyBatis延遲加載的配置
使用MyBatis的延遲加載在一定程度上可以降低運行消耗并提高查詢效率。MyBatis默認沒有開啟延遲加載,需要在核心配置文件中的< settings >元素內(nèi)進行配置,具體配置方式如下:
- <settings>
- <setting name="lazyLoadingEnabled" value="true" />
- <setting name="aggressiveLazyLoading" value="false"/>
- </settings>
在映射文件中,< association > 元素和< collection > 元素中都已默認配置了延遲加載屬性,即默認屬性fetchType="lazy"(屬性fetchType="eager"表示立即加載),所以在配置文件中開啟延遲加載后,無需在映射文件中再做配置。
使用< association >元素進行一對一關(guān)聯(lián)映射非常簡單,只需要參考如下兩種示例配置即可。
代碼實現(xiàn):
- 第一種:
- <!-- 嵌套查詢:通過執(zhí)行另外一條SQL映射語句來返回預(yù)期的特殊類型 -->
- <select id="findPersonById" parameterType="Integer"
- resultMap="IdCardWithPersonResult">
- SELECT * from tb_person where id=#{id}
- </select>
- <resultMap type="Person" id="IdCardWithPersonResult">
- <id property="id" column="id"/>
- <result property="name" column="name"/>
- <result property="age" column="age"/>
- <result property="sex" column="sex"/>
- <!-- 一對一:association使用select屬性引入另外一條SQL語句 -->
- <association property="card" column="card_id" javaType="IdCard"
- select="com.nateshao.mapper.IdCardMapper.findCodeById"/>
- </resultMap>
- 第二種:
- <!-- 嵌套結(jié)果:使用嵌套結(jié)果映射來處理重復(fù)的聯(lián)合結(jié)果的子集 -->
- <select id="findPersonById2" parameterType="Integer"
- resultMap="IdCardWithPersonResult2">
- SELECT p.*,idcard.code
- from tb_person p,tb_idcard idcard
- where p.card_id=idcard.id
- and p.id= #{id}
- </select>
- <resultMap type="Person" id="IdCardWithPersonResult2">
- <id property="id" column="id"/>
- <result property="name" column="name"/>
- <result property="age" column="age"/>
- <result property="sex" column="sex"/>
- <association property="card" javaType="IdCard">
- <id property="id" column="card_id"/>
- <result property="code" column="code"/>
- </association>
- </resultMap>
Person.java
- @Data
- public class Person {
- private Integer id;
- private String name;
- private Integer age;
- private String sex;
- private IdCard card; //個人關(guān)聯(lián)的證件
- }
IdCard.java
- @Data
- public class IdCard {
- private Integer id;
- private String code;
- }
3. 一對多
開發(fā)人員接觸更多的關(guān)聯(lián)關(guān)系是一對多(或多對一)。例如,一個用戶可以有多個訂單,同時多個訂單歸一個用戶所有。
那么使用MyBatis是怎么處理這種一對多關(guān)聯(lián)關(guān)系的呢?
在前面所講解的< resultMap >元素中,包含了一個< collection >子元素,MyBatis就是通過該元素來處理一對多關(guān)聯(lián)關(guān)系的。
< collection >子元素的屬性大部分與< association>元素相同,但其還包含一個特殊屬性--ofType 。
ofType:ofType屬性與javaType屬性對應(yīng),它用于指定實體對象中集合類屬性所包含的元素類型。
代碼實現(xiàn):
- /**
- * 一對多
- */
- @Test
- public void findUserTest() {
- // 1、通過工具類生成SqlSession對象
- SqlSession session = MybatisUtils.getSession();
- // 2、查詢id為1的用戶信息
- User user = session.selectOne("com.nateshao.mapper."
- + "UserMapper.findUserWithOrders", 1);
- // 3、輸出查詢結(jié)果信息
- System.out.println(user);
- // 4、關(guān)閉SqlSession
- session.close();
- }
UserMapper.xml
- <!-- 一對多:查看某一用戶及其關(guān)聯(lián)的訂單信息
- 注意:當(dāng)關(guān)聯(lián)查詢出的列名相同,則需要使用別名區(qū)分 -->
- <select id="findUserWithOrders" parameterType="Integer"
- resultMap="UserWithOrdersResult">
- SELECT u.*,o.id as orders_id,o.number
- from tb_user u,tb_orders o
- WHERE u.id=o.user_id
- and u.id=#{id}
- </select>
- <resultMap type="User" id="UserWithOrdersResult">
- <id property="id" column="id"/>
- <result property="username" column="username"/>
- <result property="address" column="address"/>
- <!-- 一對多關(guān)聯(lián)映射:collection
- ofType表示屬性集合中元素的類型,List<Orders>屬性即Orders類 -->
- <collection property="ordersList" ofType="Orders">
- <id property="id" column="orders_id"/>
- <result property="number" column="number"/>
- </collection>
- </resultMap>
User.java
- @Data
- public class User {
- private Integer id; // 用戶編號
- private String username; // 用戶姓名
- private String address; // 用戶地址
- private List<Orders> ordersList; //用戶關(guān)聯(lián)的訂單
- }
Orders.java
- @Data
- public class Orders {
- private Integer id; //訂單id
- private String number;//訂單編號
- //關(guān)聯(lián)商品集合信息
- private List<Product> productList;
- }
運行結(jié)果:
- User(id=1, username=詹姆斯, address=克利夫蘭, ordersList=[Orders(id=1, number=1000011, productList=null), Orders(id=2, number=1000012, productList=null)])
4. 多對多
在實際項目開發(fā)中,多對多的關(guān)聯(lián)關(guān)系也是非常常見的。以訂單和商品為例,一個訂單可以包含多種商品,而一種商品又可以屬于多個訂單。
在數(shù)據(jù)庫中,多對多的關(guān)聯(lián)關(guān)系通常使用一個中間表來維護,中間表中的訂單id作為外鍵參照訂單表的id,商品id作為外鍵參照商品表的id。
在MyBatis中,多對多的關(guān)聯(lián)關(guān)系查詢,同樣可以使用前面介紹的< collection >元素進行處理(其用法和一對多關(guān)聯(lián)關(guān)系查詢語句用法基本相同)。
MybatisAssociatedTest.java
- /**
- * 多對多
- */
- @Test
- public void findOrdersTest() {
- // 1、通過工具類生成SqlSession對象
- SqlSession session = MybatisUtils.getSession();
- // 2、查詢id為1的訂單中的商品信息
- Orders orders = session.selectOne("com.nateshao.mapper."
- + "OrdersMapper.findOrdersWithPorduct", 1);
- // 3、輸出查詢結(jié)果信息
- System.out.println(orders);
- // 4、關(guān)閉SqlSession
- session.close();
- }
OrdersMapper.xml
- <!-- 多對多嵌套結(jié)果查詢:查詢某訂單及其關(guān)聯(lián)的商品詳情 -->
- <select id="findOrdersWithPorduct2" parameterType="Integer"
- resultMap="OrdersWithPorductResult2">
- select o.*,p.id as pid,p.name,p.price from tb_orders o,tb_product p,tb_ordersitem oi WHERE oi.orders_id=o.id and oi.product_id=p.id and o.id=#{id}
- </select>
- <!-- 自定義手動映射類型 -->
- <resultMap type="Orders" id="OrdersWithPorductResult2">
- <id property="id" column="id"/>
- <result property="number" column="number"/>
- <!-- 多對多關(guān)聯(lián)映射:collection -->
- <collection property="productList" ofType="Product">
- <id property="id" column="pid"/>
- <result property="name" column="name"/>
- <result property="price" column="price"/>
- </collection>
- </resultMap>
Orders.java
- @Data
- public class Orders {
- private Integer id; //訂單id
- private String number;//訂單編號
- //關(guān)聯(lián)商品集合信息
- private List<Product> productList;
- }
Product.java
- @Data
- public class Product {
- private Integer id; //商品id
- private String name; //商品名稱
- private Double price;//商品單價
- private List<Orders> orders; //與訂單的關(guān)聯(lián)屬性
- }
總結(jié):
這篇文章首先對開發(fā)中涉及到的數(shù)據(jù)表之間以及對象之間的關(guān)聯(lián)關(guān)系作了簡要介紹,并由此引出了MyBatis框架中對關(guān)聯(lián)關(guān)系的處理;
然后通過案例對MyBatis框架處理實體對象之間的三種關(guān)聯(lián)關(guān)系進行了詳細講解。
通過本章的學(xué)習(xí),我們可以了解數(shù)據(jù)表以及對象中所涉及到的三種關(guān)聯(lián)關(guān)系,并能夠使用MyBatis框架對三種關(guān)聯(lián)關(guān)系的查詢進行處理。MyBatis中的關(guān)聯(lián)查詢操作在實際開發(fā)中非常普遍,熟練掌握這三種關(guān)聯(lián)查詢方式有助于提高項目的開發(fā)效率。