Spring?Boot?整合JPA?數(shù)據(jù)模型關(guān)聯(lián)使用操作(一對一、一對多、多對多)
表關(guān)聯(lián)
上一篇介紹了JPA的簡單使用,這一篇介紹JPA在表關(guān)聯(lián)上的使用
一對一
配置參數(shù)
JPA對于數(shù)據(jù)實(shí)體一對一映射使用的是@OneToOne
注解。
代碼
User用戶表
/** * 用戶信息 * @author daify **/ @Data @Entity @Table(name = "cascade_user") public class User { @Id @GeneratedValue private Long id; private String name; private Integer age; private Double point; /** * CascadeType包含的類別 級聯(lián):給當(dāng)前設(shè)置的實(shí)體操作另一個(gè)實(shí)體的權(quán)限 * CascadeType.ALL 級聯(lián)所有操作 * CascadeType.PERSIST 級聯(lián)持久化(保存)操作 * CascadeType.MERGE 級聯(lián)更新(合并)操作 * CascadeType.REMOVE 級聯(lián)刪除操作 * CascadeType.REFRESH 級聯(lián)刷新操作 * CascadeType.DETACH 級聯(lián)分離操作,如果你要?jiǎng)h除一個(gè)實(shí)體,但是它有外鍵無法刪除,這個(gè)級聯(lián)權(quán)限會撤銷所有相關(guān)的外鍵關(guān)聯(lián)。 */ @OneToOne(targetEntity = UserCart.class, cascade = CascadeType.ALL, mappedBy = "user") private UserCart userCart; @OneToOne(targetEntity = UserInfo.class, cascade = CascadeType.ALL) @JoinTable(name = "user_info_table", joinColumns = @JoinColumn(name="user_id"), inverseJoinColumns = @JoinColumn(name = "info_id")) private UserInfo userInfo; }
UserCart用戶購物車表
@Data @Entity @Table(name = "cascade_user_cart") public class UserCart { @Id @GeneratedValue private Long id; private Integer count; private Double totalMoney; private Date updateTime; /** * CascadeType包含的類別 級聯(lián):給當(dāng)前設(shè)置的實(shí)體操作另一個(gè)實(shí)體的權(quán)限 * CascadeType.ALL 級聯(lián)所有操作 * CascadeType.PERSIST 級聯(lián)持久化(保存)操作 * CascadeType.MERGE 級聯(lián)更新(合并)操作 * CascadeType.REMOVE 級聯(lián)刪除操作 * CascadeType.REFRESH 級聯(lián)刷新操作 * CascadeType.DETACH 級聯(lián)分離操作,如果你要?jiǎng)h除一個(gè)實(shí)體,但是它有外鍵無法刪除,這個(gè)級聯(lián)權(quán)限會撤銷所有相關(guān)的外鍵關(guān)聯(lián)。 */ @OneToOne(targetEntity = User.class, cascade = {}, fetch = FetchType.LAZY) private User user; }
用戶信息
@Data @Entity @Table(name = "cascade_user_info") public class UserInfo { @Id @GeneratedValue private Long id; private String userInfo; private String config; }
上面例子中嘗試使用了兩種方式來維護(hù)一對一的關(guān)系,首先在User實(shí)體中同樣標(biāo)注了@OneToOne
但是配置了mappedBy,
這樣生成的表數(shù)據(jù)中,User和UserCart的關(guān)系將由UserCart負(fù)責(zé)維護(hù)。User表中并不會維護(hù)UserCart的信息。
而在User和UserInfo的關(guān)系中使用了中間表user_info_table
來維護(hù)雙方的關(guān)系,UserInfo實(shí)體中并沒有保存任何User的信息。
權(quán)限
在User、UserCart、UserInfo三者中User為數(shù)據(jù)存在的主體,其他兩個(gè)實(shí)體都是依托于User數(shù)據(jù)的存在而存在。
所以在權(quán)限中給User實(shí)體提供了全部全部權(quán)限。
注解
@OneToOne主要提供了下面的參數(shù)內(nèi)容
注解 | 參數(shù) | 描述 |
---|---|---|
@OneToOne | 描述一個(gè) 一對一的關(guān)聯(lián) | |
targetEntity | 目標(biāo)類的實(shí)體 | |
cascade | 關(guān)聯(lián)到目標(biāo)的操作 | |
fetch | 是否使用延遲加載 | |
mappedBy | 反向關(guān)聯(lián) |
測試
因?yàn)樯厦嬉粚σ坏睦又袡?quán)限被賦予給User表中,UserCart并沒有賦予任何權(quán)限,所以保存用戶的時(shí)候可以關(guān)聯(lián)保存用戶購物車,刪除購物車的時(shí)候并不會刪除用戶,但是刪除用戶的時(shí)候會刪除購物車
通過保存用戶關(guān)聯(lián)保存購物車
@Test public void testUserSave() { User defUser1 = UserMock.getDefUser1(); UserCart defUserCart1 = UserCartMock.getDefUserCart1(); defUser1.setUserCart(defUserCart1); defUserCart1.setUser(defUser1); // 此時(shí)保存用戶,用戶購物車應(yīng)該也被保存 userRepository.save(defUser1); List <UserCart> all = userCartRepository.findAll(); Assert.assertNotNull(all); Assert.assertTrue(all.size() == 1); }
刪除用戶購物車的時(shí)候,用戶不會被刪除
@Test public void testUserCartDelete() { User defUser1 = UserMock.getDefUser1(); UserCart defUserCart1 = UserCartMock.getDefUserCart1(); defUser1.setUserCart(defUserCart1); // 此時(shí)保存用戶,用戶購物車應(yīng)該也被保存 userRepository.save(defUser1); // 此時(shí)刪除用戶購物車并不會刪除用戶 userCartRepository.deleteAll(); List <User> all1 = userRepository.findAll(); Assert.assertNotNull(all1); Assert.assertTrue(all1.size() == 1); }
刪除用戶的時(shí)候,購物車會被刪除
@Test public void testUserDelete() { User defUser1 = UserMock.getDefUser1(); UserCart defUserCart1 = UserCartMock.getDefUserCart1(); defUser1.setUserCart(defUserCart1); // 此時(shí)保存用戶,用戶購物車應(yīng)該也被保存 userRepository.save(defUser1); // 此時(shí)刪除用戶購物車并不會刪除用戶 userRepository.delete(defUser1); List <UserCart> all = userCartRepository.findAll(); Assert.assertTrue(all.size() == 0); }
一對多和多對一
通過@OneToMany和@ManyToOne的組合我們可以實(shí)現(xiàn)雙向關(guān)聯(lián)。根據(jù)JPA規(guī)范,我們使用多方來維護(hù)關(guān)系。
通過在多方維護(hù)@JoinColumn
來注釋屬性。
代碼
訂單表
@Data @Entity @Table(name = "cascade_order") public class Order { @Id @GeneratedValue private Long id; private String orderNo; private Integer count; private Double money; @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.EAGER) private List<OrderItem> orderItems; /** * 重寫toString防止死循環(huán) * @return */ @Override public String toString() { return "OrderItem{" + "id=" + id + ", productId='" + productId + '\'' + ", money=" + money + ", count=" + count + ", productName='" + productName + '\'' + ", order=" + order + '}'; } }
訂單子項(xiàng)
@Data @Entity @Table(name = "cascade_order_item") public class OrderItem { @Id @GeneratedValue private Long id; private String productId; private Double money; private Integer count; private String productName; @ManyToOne(targetEntity = Order.class, cascade = {}, fetch = FetchType.EAGER) @JoinColumn( // 設(shè)置在OrderItem表中的關(guān)聯(lián)字段(外鍵) name="order_id" ) // 防止json序列化死循環(huán)問題解決 @JsonBackReference private Order order; }
上門的例子中,訂單方為一端,訂單子項(xiàng)為多端,在多端除了使用了@ManyToOne
還使用了@JoinColumn
注解來標(biāo)識Order主鍵創(chuàng)建到OrderItem表的列的名稱,
當(dāng)然沒有此注解的時(shí)候JPA會根據(jù)默認(rèn)規(guī)則生成一個(gè)列名稱。
權(quán)限
根據(jù)JPA規(guī)范一對多時(shí)候,關(guān)系的維護(hù)交給了多方來進(jìn)行,但是很多時(shí)候多方的存在是依靠一方的。
比如(訂單、訂單子項(xiàng))所以更新刪除的權(quán)限需要授權(quán)給一方(Order)。
@ManyToOne,@OneToMany
注解 | 參數(shù) | 描述 |
---|---|---|
@ManyToOne | 一個(gè)多對一的映射,該注解標(biāo)注的屬性通常是數(shù)據(jù)庫表的外鍵. | |
targetEntity | 目標(biāo)類的實(shí)體 | |
cascade | 關(guān)聯(lián)到目標(biāo)的操作 | |
fetch | 是否使用延遲加載 | |
@OneToMany | 一個(gè) 一對多的關(guān)聯(lián),該屬性應(yīng)該為集體類型,在數(shù)據(jù)庫中并沒有實(shí)際字段 | |
targetEntity | 目標(biāo)類的實(shí)體 | |
cascade | 關(guān)聯(lián)到目標(biāo)的操作 | |
fetch | 是否使用延遲加載 | |
mappedBy | 反向關(guān)聯(lián) | |
orphanRemoval | 講移除操作級聯(lián)到關(guān)聯(lián)的實(shí)體中 |
測試
一對多的時(shí)候雖然多方維持了兩者關(guān)系,但是我們把權(quán)限賦予了一方所以,刪除多方并不會級聯(lián)操作,刪除一方可以移除多方數(shù)據(jù)。所以下的測試可以通過
@Test public void testOrder() { Order defOrder1 = OrderMock.getDefOrder1(); List <OrderItem> allDefOrder = OrderItemMock.getAllDefOrder(); // order 維持orderItem關(guān)系 defOrder1.setOrderItems(allDefOrder); // orderItem維持order關(guān)系 allDefOrder.forEach(o -> o.setOrder(defOrder1)); Order save = orderRepository.save(defOrder1); List <OrderItem> all = orderItemRepository.findAll(); Assert.assertTrue(all.size() == allDefOrder.size()); orderItemRepository.delete(all.get(0)); Order order = orderRepository.findById(save.getId()).get(); Assert.assertNotNull(order); orderRepository.deleteById(order.getId()); List <OrderItem> all1 = orderItemRepository.findAll(); Assert.assertTrue(all1.size() == 0); }
多對多
配置方法
和一對多、一對一不同,多對多沒法只使用兩個(gè)數(shù)據(jù)實(shí)體完成相互之間的關(guān)系維護(hù),這個(gè)時(shí)候需要一個(gè)關(guān)聯(lián)的中間表來維護(hù)他們之間的關(guān)系。
對于中間表的配置,你大可不去進(jìn)行額外的配置讓JPA自動生成,當(dāng)然你也可以使用之前介紹的@JoinTable
注解進(jìn)行配置。
權(quán)限
和一對多、一對一不同,多對多一般是沒有辦法設(shè)置級聯(lián)操作的。因?yàn)殡p方對應(yīng)的都是集合對象,
而雙方某一條數(shù)據(jù)都可能被對方多條數(shù)據(jù)關(guān)聯(lián)。
代碼
用戶表
@Data @Entity @Table(name = "cascade_user") public class User { @Id @GeneratedValue private Long id; private String name; private Integer age; private Double point; @ManyToMany(targetEntity = UserTag.class, cascade = {}, fetch = FetchType.LAZY) // 假如不定義,jpa會生成“表名1”+“_”+“表名2”的中間表 @JoinTable( // 定義中間表的名稱 name = "cascade_user_tag_table", // 定義中間表中關(guān)聯(lián)User表的外鍵名 joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") }, // 定義中間表中關(guān)聯(lián)UserTag表的外鍵名 inverseJoinColumns = { @JoinColumn(name = "tags_id", referencedColumnName = "id") } ) private Set<UserTag> tags; }
用戶標(biāo)簽表
@Data @Entity @Table(name = "cascade_user_tag") public class UserTag { @Id @GeneratedValue private Long id; private String tagName; @ManyToMany(mappedBy = "tags", cascade = {}, fetch = FetchType.LAZY) private List<User> userList; }
@ManyToMany
下面是@OneToOne
以及與其配合的@JoinTable
提供的注解參數(shù)
注解 | 參數(shù) | 描述 |
---|---|---|
@ManyToMany | 描述一個(gè)多對多的關(guān)聯(lián).多對多關(guān)聯(lián)上是兩個(gè)一對多關(guān)聯(lián),但是在ManyToMany描述中,中間表是由ORM框架自動處理 | |
targetEntity | 目標(biāo)類的實(shí)體 | |
cascade | 關(guān)聯(lián)到目標(biāo)的操作 | |
fetch | 是否使用延遲加載 | |
mappedBy | 反向關(guān)聯(lián) | |
@JoinTable | JoinTable在many-to-many關(guān)系的所有者一邊定義。如果沒有定義JoinTable,使用JoinTable的默認(rèn)值。 | |
catalog | 表的目錄 | |
foreignKey | 外鍵約束,創(chuàng)建表的時(shí)候使用 | |
indexes | 表的索引,在生成表的時(shí)候使用 | |
@JoinColumns | 關(guān)系存在多個(gè)JoinColumn,用JoinColumns定義多個(gè)JoinColumn的屬性。 | |
foreignKey | 此列的外鍵約束 | |
value | JoinColumn的集合 | |
inverseJoinColumns | 聯(lián)接表的外鍵列,該列引用不擁有關(guān)聯(lián)的實(shí)體的主表 | |
joinColumns | 聯(lián)接表的外鍵列,該列引用擁有關(guān)聯(lián)的實(shí)體的主表 | |
name | 進(jìn)行連接的表名稱 | |
schema | 表的命名空間 |
測試
多對多的情況下,我們使用雖然User和UserTag的關(guān)系由中間表維護(hù),但是我們在User中配置了中間表的關(guān)系維護(hù),所以此時(shí)刪除用戶的時(shí)候可以成功刪除,且可以成功移除中間表數(shù)據(jù),但是這個(gè)時(shí)候移除UserTag數(shù)據(jù)的時(shí)候,卻會拋出DataIntegrityViolationException異常。只能通過User移除中間關(guān)系。
@Test public void testUserTag() { User defUser1 = UserMock.getDefUser1(); UserTag defTag1 = UserTagMock.getDefTag1(); UserTag defTag2 = UserTagMock.getDefTag2(); defUser1 = userRepository.save(defUser1); defTag1 = userTagRepository.save(defTag1); defTag2 = userTagRepository.save(defTag2); defUser1.getTags().add(defTag1); defUser1.getTags().add(defTag2); defUser1 = userRepository.save(defUser1); // 此時(shí)會報(bào)錯(cuò),因?yàn)橹虚g表關(guān)系為User維護(hù) try { userTagRepository.delete(defTag2); } catch (Exception e) { log.info(e.getCause().getMessage()); Assert.assertTrue(e instanceof DataIntegrityViolationException); } // 修改User表中關(guān)系 defUser1.setTags(new HashSet <>()); defUser1.getTags().add(defTag1); defUser1 = userRepository.save(defUser1); // 此時(shí)可以刪除 userTagRepository.delete(defTag2); // 直接刪除用戶沒問題 userRepository.delete(defUser1); }
本篇文章涉及的源碼下載地址:https://gitee.com/daifyutils/springboot-samples
到此這篇關(guān)于Spring Boot 整合JPA 數(shù)據(jù)模型關(guān)聯(lián)操作(一對一、一對多、多對多)的文章就介紹到這了,更多相關(guān)Spring Boot 整合JPA 數(shù)據(jù)模型關(guān)聯(lián)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring Boot集成MinIO對象存儲服務(wù)器操作步驟
通過Spring Boot集成MinIO,你可以在應(yīng)用中方便地進(jìn)行文件的存儲和管理,本文給大家分享Spring Boot集成MinIO對象存儲服務(wù)器詳細(xì)操作步驟,感興趣的朋友一起看看吧2024-01-01關(guān)于BufferedReader讀取文件指定字符集問題
這篇文章主要介紹了關(guān)于BufferedReader讀取文件指定字符集問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12Java8 將List轉(zhuǎn)換為用逗號隔開的字符串的多種方法
這篇文章主要介紹了Java8 將List轉(zhuǎn)換為用逗號隔開的字符串的幾種方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03詳解Spring Cloud中Hystrix 線程隔離導(dǎo)致ThreadLocal數(shù)據(jù)丟失
這篇文章主要介紹了詳解Spring Cloud中Hystrix 線程隔離導(dǎo)致ThreadLocal數(shù)據(jù)丟失,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03Java利用剪貼板實(shí)現(xiàn)交換程序間數(shù)據(jù)的方法
這篇文章主要介紹了Java利用剪貼板實(shí)現(xiàn)交換程序間數(shù)據(jù)的方法,需要的朋友可以參考下2014-07-07java 數(shù)據(jù)結(jié)構(gòu)單鏈表的實(shí)現(xiàn)
這篇文章主要介紹了java 數(shù)據(jù)結(jié)構(gòu)單鏈表的實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2017-07-07將Springboot項(xiàng)目升級成Springcloud項(xiàng)目的圖文教程
本文主要介紹了將Springboot項(xiàng)目升級成Springcloud項(xiàng)目,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06