關(guān)于MyBatis中映射對(duì)象關(guān)系的舉例
MyBatis映射對(duì)象關(guān)系
雙向many2one/one2many關(guān)系中的組合關(guān)系(級(jí)聯(lián))
上面已經(jīng)看到了雙向many2one/one2many關(guān)系的映射。
但是我們說(shuō),在關(guān)聯(lián)關(guān)系中,還存在組合/聚合關(guān)系。
所謂聚合就是one方和many方可以獨(dú)立存在;組合關(guān)系是強(qiáng)聚合關(guān)系,one方和many方解除關(guān)系后,就沒(méi)有實(shí)際意義了。
在組合關(guān)系中,必須要處理的一個(gè)問(wèn)題就是級(jí)聯(lián)關(guān)系。
下面使用SaleBill和SaleBillItem對(duì)象來(lái)完成雙向的many2one/one2many的組合關(guān)系。
對(duì)象設(shè)計(jì)如下:
//銷售單對(duì)象 public class SaleBill { private Long id; private String sn; private Set<</span>SaleBillItem> items = new HashSet<</span>SaleBillItem>(); //getter & setter } //銷售單明細(xì)對(duì)象 public class SaleBillItem { private Long id; private Double price; private SaleBill bill; //getter & setter }
- 對(duì)于組合關(guān)系,數(shù)據(jù)表的結(jié)構(gòu)設(shè)計(jì)和many2one一樣,都是many方一個(gè)外鍵引用one方的主鍵。
- 在組合關(guān)系中,一般來(lái)說(shuō),明細(xì)項(xiàng)是不需要額外有一個(gè)Mapper文件的,所有的映射都應(yīng)該放在One方的映射文件中。
下面來(lái)看看映射文件:
<mapper namespace="cd.itcast.mybatis.salebill.SaleBillMapper"> <!-- 保存SaleBill對(duì)象,該調(diào)用必須放在SaleBillItem對(duì)象保存之前 --> <insert id="saveSaleBill" keyProperty="id" parameterType="SaleBill" useGeneratedKeys="true"> INSERT INTO salebill(sn) values (#{sn}) </insert> <!-- 保存SaleBillItem對(duì)象 --> <insert id="saveSaleBillItem" keyProperty="id" parameterType="SaleBillItem" useGeneratedKeys="true"> INSERT INTO salebillitem(price,bill_id) values (#{price},#{bill.id}) </insert> <!-- 內(nèi)嵌映射SaleBill對(duì)象,注意,因?yàn)槭墙M合關(guān)系,所以不會(huì)出現(xiàn)單獨(dú)去拿SaleBillItem對(duì)象的情況,所以只需要映射SaleBill主對(duì)象即可 --> <resultMap type="SaleBill" id="salebillmap"> <id property="id" column="id"/> <result column="sn" property="sn"/> <collection property="items" column="id" ofType="SaleBillItem" > <id column="item_id" property="id"/> <result column="item_price" property="price"/> <association property="bill" column="bill_id" resultMap="salebillmap" /> </collection> </resultMap> <!-- 因?yàn)榱斜砗蛦为?dú)的get方法都是采用內(nèi)聯(lián)的查詢,所以把公用的SQL提取出來(lái) --> <sql id="select"> SELECT b.*,item.id as item_id,item.price as item_price FROM salebill b LEFT JOIN salebillitem item ON b.id = item.bill_id </sql> <!-- get --> <select id="get" parameterType="long" resultMap="salebillmap"> <include refid="select"/> WHERE b.id = #{id} </select> <!-- 列表查詢 --> <select id="list" resultMap="salebillmap"> <include refid="select"/> </select> <!-- 刪除SaleBill對(duì)象 --> <delete id="delete" parameterType="long"> DELETE FROM salebill WHERE id = #{id} </delete> <!-- 刪除SaleBill對(duì)象對(duì)應(yīng)的Item對(duì)象,注意,因?yàn)橛型怄I的約束,這條刪除語(yǔ)句應(yīng)該放在delete SaleBill之前執(zhí)行。注意,傳入的這個(gè)id應(yīng)該是SaleBill的id --> <delete id="deleteItems" parameterType="long"> DELETE FROM salebillitem WHERE bill_id = #{id} </delete> </mapper>
映射文件的內(nèi)容都已經(jīng)注釋詳細(xì)。
主要注意一點(diǎn)的就是在設(shè)計(jì)映射文件的時(shí)候需要考慮組合關(guān)系的一般設(shè)計(jì)方式,所以在上面的配置文件中只做了SaleBill對(duì)象的映射。
這個(gè)配置文件還是很好理解的,其中所有的配置點(diǎn)在前面都已經(jīng)介紹過(guò)。
在組合關(guān)系中,主要的問(wèn)題就是一個(gè)級(jí)聯(lián)保存,但是注意,在mybatis中設(shè)計(jì)組合的級(jí)聯(lián)保存,刪除,這個(gè)過(guò)程應(yīng)該是我們自己來(lái)控制的。
下面來(lái)看看怎么測(cè)試組合關(guān)系:
//先創(chuàng)建一個(gè)Mapper接口,使用接口來(lái)完成映射 public interface SaleBillMapper { ? ? void saveSaleBill(SaleBill sb); ? ? void saveSaleBillItem(SaleBillItem sbi); ? ? SaleBill get(Long id); ? ? //在接口中可以直接映射分頁(yè)對(duì)象 ? ? List<</span>SaleBill> list(RowBounds rb); ? ? void delete(Long id); ? ? void deleteItems(Long billId); }
具體測(cè)試代碼:
@Test public void testSave() { ? ? //創(chuàng)建SaleBill對(duì)象 ? ? SaleBill sb = new SaleBill(); ? ? sb.setSn("001"); ? ? //創(chuàng)建一個(gè)明細(xì)對(duì)象 ? ? SaleBillItem sbi = new SaleBillItem(); ? ? sbi.setPrice(100d); ? ? sbi.setBill(sb); ? ? //創(chuàng)建一個(gè)明細(xì)對(duì)象 ? ? SaleBillItem sbi2 = new SaleBillItem(); ? ? sbi2.setPrice(300d); ? ? sbi2.setBill(sb); ? ? //處理關(guān)系 ? ? sb.getItems().add(sbi); ? ? sb.getItems().add(sbi2); ? ? //保存 ? ? SqlSession session = MyBatisUtil.openSession(); ? ? SaleBillMapper mapper = session.getMapper(SaleBillMapper.class); ? ? //注意,先保存SaleBill對(duì)象 ? ? mapper.saveSaleBill(sb); ? ? //保存明細(xì)對(duì)象 ? ? mapper.saveSaleBillItem(sbi); ? ? mapper.saveSaleBillItem(sbi2); ? ? session.commit(); ? ? session.close(); } ? @Test public void testGet() { ? ? SqlSession session = MyBatisUtil.openSession(); ? ? //得到SaleBill對(duì)象 ? ? SaleBillMapper mapper = session.getMapper(SaleBillMapper.class); ? ? SaleBill sb = mapper.get(5l); ? ? Set<</span>SaleBillItem> items = sb.getItems(); ? ? for (SaleBillItem item : items) { ? ? ? ? ? ? System.out.println(item.getPrice()); ? ? } ? ? session.close(); } ? @Test public void testDelete() { ? ? SqlSession session = MyBatisUtil.openSession(); ? ? SaleBillMapper mapper = session.getMapper(SaleBillMapper.class); ? ? mapper.deleteItems(1l); ? ? mapper.delete(1l); ? ? session.commit(); ? ? session.close(); } ? ?
Mybatis映射原理
MyBatis的真正強(qiáng)大之處在于它的映射語(yǔ)句,這也是它的魔力所在。由于它的映射語(yǔ)句異常強(qiáng)大,映射器的 XML 文件就顯得相對(duì)簡(jiǎn)單。
MyBatis3.0相比2.0版本的一個(gè)最大變化,就是支持使用接口來(lái)調(diào)用方法。
- 以前使用 SqlSession 通過(guò)命名空間調(diào)用 MyBatis 方法時(shí),首先需要用到命名空間和方法id 組成的字符串來(lái)調(diào)用相應(yīng)的方法 。
- 當(dāng)參數(shù)多于 1 個(gè)的時(shí)候,需要將所有參數(shù)放到一個(gè) Map對(duì)象中 。 通過(guò) Map 傳遞多個(gè)參數(shù),使用起來(lái)很不方便,而且還無(wú)法避免很多重復(fù)的代碼。
- 使用接口調(diào)用方式就會(huì)方便很多, MyBatis 使用 Java 的動(dòng)態(tài)代理可以直接通過(guò)接口來(lái)調(diào)用 相應(yīng) 的方法,不需要提供接口的實(shí)現(xiàn)類,更不需要在實(shí)現(xiàn)類中使用 SqlSess 工∞以通過(guò)命名空 間間接調(diào)用 。
- 另外,當(dāng)有多個(gè)參數(shù)的時(shí)候,通過(guò)參數(shù)注解@ Par am 設(shè)置參數(shù)的名字省去了 手動(dòng)構(gòu)造 Map 參數(shù)的過(guò)程,尤其在 Spring 中使用的時(shí)候,可以配置為自動(dòng)掃描所有的接口類 ,直接將接口注入需要用到的地方。
mapper文件示例:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.cnpiec.ireader.dao.GetBookDataDao"> ? ? <insert id="batchInsertBook" parameterType="Book"> ? ? ? ? INSERT INTO BOOK (BOOKID,NAME,PARTNERNAME) VALUES ? ? ? ? <foreach collection="list" item="book" separator=","> ? ? ? ? ? ? (#{book.bookId}, #{book.name}, #{book.partnerName}) ? ? ? ? </foreach> ? ? </insert> </mapper>
需要注意的是<mapper>根標(biāo)簽的 name space 屬性。當(dāng) Mapper 接口和 XML 文件關(guān)聯(lián)的時(shí)候,命名空間口amespace 的值就需要配置成接口的全限定名稱,例如 UserMapper 接口對(duì)應(yīng)的 tk. mybatis . simple .mapper . UserMapper, MyBatis 內(nèi)部就是通過(guò)這個(gè)值將接口和XML 關(guān)聯(lián)起來(lái)的。
mybati s-config.xml 配置文件中的 mappers 元素中配置所有的 mapper ,部分配置代碼如下 。
<mappers> ? ? <mapper resource=” tk/mybatis/simple/mapper/CountryMapper.xml ” / > ? ? <mapper resource=” tk/mybatis/simple/mapper/UserMapper . xml ” / > ? ? <mapper resource=” tk/mybatis/simple/mapper/RoleMapper.xml ” /> ? ? <mapper resource=” tk/mybatis/simple/mapper/PrivilegeMapper . xml ” /> ? ? <mapper resource=” tk/mybatis/simple/mapper/UserRoleMapper . xml ” /> ? ? <mapper resource=” tk/mybatis/simple/mapper / RolePrivilegeMapper.xml ” /> </mappers>
更簡(jiǎn)單的配置方式,代碼如下
<mappers> ? ? <package name= ” tk.mybatis . simple . mapper ” /> </mappers>
這種配置方式會(huì)先查找 tk.mybatis.simple . mapper 包下所有的接口,循環(huán)對(duì)接口進(jìn)行如下操作。這種配置方式會(huì)先查找 tk.mybatis.simple . mapper 包下所有的接口,循環(huán)對(duì)接口進(jìn)行判斷接口對(duì)應(yīng)的命名 空 間是否己經(jīng)存在,如果不存在就拋出異常,存在就繼續(xù)進(jìn)行接下來(lái)的操作。加載接口對(duì)應(yīng)的卻也映射文件 , 將接口全限定名轉(zhuǎn)換為路徑.
為什么 Mapper 接口沒(méi)有實(shí)現(xiàn)類卻能被正常調(diào)用呢?
這是因?yàn)镸yBaits 在 Mapper 接口上使用了動(dòng)態(tài)代理的一種非常規(guī)的用法,熟悉這動(dòng)態(tài)代理的用法不僅有利于理解 MyBatis 接口和 XML 的關(guān)系,還能開(kāi)闊思路 。
從代理類中可以看到,當(dāng)調(diào)用 一個(gè)接口的方法時(shí),會(huì)先通過(guò)接口的全限定名稱和當(dāng)前調(diào)用的方法名的組合得到一個(gè)方法 id,這個(gè) id 的值就是映射 XML 中口arnespa ce 和具體方法 id的組合。所以可以在代理方法中使用 sqlSession 以命名空間的方式調(diào)用方法。通過(guò)這種方式可以將接口和 XML 文件中的方法關(guān)聯(lián)起來(lái)。這種代理方式和常規(guī)代理的不同之處在于,這里沒(méi)有對(duì)某個(gè)具體類進(jìn)行代理,而是通過(guò)代理轉(zhuǎn)化成了對(duì)其他代碼的調(diào)用。
由于方法參數(shù)和返回值存在很多種情況,因此 MyBatis 的內(nèi)部實(shí)現(xiàn)會(huì)比上面的邏輯復(fù)雜得多,正是因?yàn)?MyBatis 對(duì)接口動(dòng)態(tài)代理的實(shí)現(xiàn),我們?cè)谑褂媒涌诜绞降臅r(shí)候才會(huì)如此容易。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot 接收List 入?yún)⒌膸追N方法
本文主要介紹了springboot 接收List 入?yún)⒌膸追N方法,本文主要介紹了7種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-03-03Shell重啟SpringBoot項(xiàng)目腳本的示例代碼(含服務(wù)守護(hù))
本文介紹了如何使用?Bash?腳本來(lái)管理和守護(hù)運(yùn)行服務(wù),將展示一個(gè)示例腳本,該腳本可以停止、啟動(dòng)和守護(hù)運(yùn)行一個(gè)服務(wù),并提供了相應(yīng)的解釋和用法說(shuō)明,文章通過(guò)代碼示例介紹的非常詳細(xì),需要的朋友可以參考下2023-11-11springmvc接收json串,轉(zhuǎn)換為實(shí)體類List方法
今天小編就為大家分享一篇springmvc接收json串,轉(zhuǎn)換為實(shí)體類List方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08java啟動(dòng)jar包修改JVM默認(rèn)內(nèi)存問(wèn)題
這篇文章主要介紹了java啟動(dòng)jar包修改JVM默認(rèn)內(nèi)存問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02spring boot 添加admin監(jiān)控的方法
這篇文章主要介紹了spring boot 添加admin監(jiān)控的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-02-02Java使用POI導(dǎo)出Excel(一):?jiǎn)蝧heet
這篇文章介紹了Java使用POI導(dǎo)出Excel的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10SpringCloud中數(shù)據(jù)認(rèn)證加密的方法總結(jié)
在當(dāng)今分布式系統(tǒng)的日益復(fù)雜和信息傳遞的廣泛網(wǎng)絡(luò)化環(huán)境中,數(shù)據(jù)的加密和認(rèn)證作為保障信息傳遞安全的關(guān)鍵手段,Spring?Cloud,作為一套構(gòu)建微服務(wù)架構(gòu)的強(qiáng)大框架,提供了多種靈活而強(qiáng)大的數(shù)據(jù)加密和認(rèn)證方式,本文給大家總結(jié)了SpringCloud數(shù)據(jù)認(rèn)證加密的方法2024-03-03Maven默認(rèn)使用JDK1.5的問(wèn)題及解決方案
這篇文章主要介紹了Maven默認(rèn)使用JDK1.5的問(wèn)題及解決方案,本文給大家分享兩種方式,通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04Springboot如何同時(shí)裝配兩個(gè)相同類型數(shù)據(jù)庫(kù)
這篇文章主要介紹了Springboot如何同時(shí)裝配兩個(gè)相同類型數(shù)據(jù)庫(kù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11