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