Spring?Boot?整合JPA?數(shù)據(jù)模型關聯(lián)使用操作(一對一、一對多、多對多)
表關聯(lián)
上一篇介紹了JPA的簡單使用,這一篇介紹JPA在表關聯(lián)上的使用
一對一
配置參數(shù)
JPA對于數(shù)據(jù)實體一對一映射使用的是@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):給當前設置的實體操作另一個實體的權限 * CascadeType.ALL 級聯(lián)所有操作 * CascadeType.PERSIST 級聯(lián)持久化(保存)操作 * CascadeType.MERGE 級聯(lián)更新(合并)操作 * CascadeType.REMOVE 級聯(lián)刪除操作 * CascadeType.REFRESH 級聯(lián)刷新操作 * CascadeType.DETACH 級聯(lián)分離操作,如果你要刪除一個實體,但是它有外鍵無法刪除,這個級聯(liá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):給當前設置的實體操作另一個實體的權限 * CascadeType.ALL 級聯(lián)所有操作 * CascadeType.PERSIST 級聯(lián)持久化(保存)操作 * CascadeType.MERGE 級聯(lián)更新(合并)操作 * CascadeType.REMOVE 級聯(lián)刪除操作 * CascadeType.REFRESH 級聯(lián)刷新操作 * CascadeType.DETACH 級聯(lián)分離操作,如果你要刪除一個實體,但是它有外鍵無法刪除,這個級聯(liá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; }
上面例子中嘗試使用了兩種方式來維護一對一的關系,首先在User實體中同樣標注了@OneToOne
但是配置了mappedBy,
這樣生成的表數(shù)據(jù)中,User和UserCart的關系將由UserCart負責維護。User表中并不會維護UserCart的信息。
而在User和UserInfo的關系中使用了中間表user_info_table
來維護雙方的關系,UserInfo實體中并沒有保存任何User的信息。
權限
在User、UserCart、UserInfo三者中User為數(shù)據(jù)存在的主體,其他兩個實體都是依托于User數(shù)據(jù)的存在而存在。
所以在權限中給User實體提供了全部全部權限。
注解
@OneToOne主要提供了下面的參數(shù)內(nèi)容
注解 | 參數(shù) | 描述 |
---|---|---|
@OneToOne | 描述一個 一對一的關聯(lián) | |
targetEntity | 目標類的實體 | |
cascade | 關聯(lián)到目標的操作 | |
fetch | 是否使用延遲加載 | |
mappedBy | 反向關聯(lián) |
測試
因為上面一對一的例子中權限被賦予給User表中,UserCart并沒有賦予任何權限,所以保存用戶的時候可以關聯(lián)保存用戶購物車,刪除購物車的時候并不會刪除用戶,但是刪除用戶的時候會刪除購物車
通過保存用戶關聯(lián)保存購物車
@Test public void testUserSave() { User defUser1 = UserMock.getDefUser1(); UserCart defUserCart1 = UserCartMock.getDefUserCart1(); defUser1.setUserCart(defUserCart1); defUserCart1.setUser(defUser1); // 此時保存用戶,用戶購物車應該也被保存 userRepository.save(defUser1); List <UserCart> all = userCartRepository.findAll(); Assert.assertNotNull(all); Assert.assertTrue(all.size() == 1); }
刪除用戶購物車的時候,用戶不會被刪除
@Test public void testUserCartDelete() { User defUser1 = UserMock.getDefUser1(); UserCart defUserCart1 = UserCartMock.getDefUserCart1(); defUser1.setUserCart(defUserCart1); // 此時保存用戶,用戶購物車應該也被保存 userRepository.save(defUser1); // 此時刪除用戶購物車并不會刪除用戶 userCartRepository.deleteAll(); List <User> all1 = userRepository.findAll(); Assert.assertNotNull(all1); Assert.assertTrue(all1.size() == 1); }
刪除用戶的時候,購物車會被刪除
@Test public void testUserDelete() { User defUser1 = UserMock.getDefUser1(); UserCart defUserCart1 = UserCartMock.getDefUserCart1(); defUser1.setUserCart(defUserCart1); // 此時保存用戶,用戶購物車應該也被保存 userRepository.save(defUser1); // 此時刪除用戶購物車并不會刪除用戶 userRepository.delete(defUser1); List <UserCart> all = userCartRepository.findAll(); Assert.assertTrue(all.size() == 0); }
一對多和多對一
通過@OneToMany和@ManyToOne的組合我們可以實現(xiàn)雙向關聯(lián)。根據(jù)JPA規(guī)范,我們使用多方來維護關系。
通過在多方維護@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 + '}'; } }
訂單子項
@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( // 設置在OrderItem表中的關聯(lián)字段(外鍵) name="order_id" ) // 防止json序列化死循環(huán)問題解決 @JsonBackReference private Order order; }
上門的例子中,訂單方為一端,訂單子項為多端,在多端除了使用了@ManyToOne
還使用了@JoinColumn
注解來標識Order主鍵創(chuàng)建到OrderItem表的列的名稱,
當然沒有此注解的時候JPA會根據(jù)默認規(guī)則生成一個列名稱。
權限
根據(jù)JPA規(guī)范一對多時候,關系的維護交給了多方來進行,但是很多時候多方的存在是依靠一方的。
比如(訂單、訂單子項)所以更新刪除的權限需要授權給一方(Order)。
@ManyToOne,@OneToMany
注解 | 參數(shù) | 描述 |
---|---|---|
@ManyToOne | 一個多對一的映射,該注解標注的屬性通常是數(shù)據(jù)庫表的外鍵. | |
targetEntity | 目標類的實體 | |
cascade | 關聯(lián)到目標的操作 | |
fetch | 是否使用延遲加載 | |
@OneToMany | 一個 一對多的關聯(lián),該屬性應該為集體類型,在數(shù)據(jù)庫中并沒有實際字段 | |
targetEntity | 目標類的實體 | |
cascade | 關聯(lián)到目標的操作 | |
fetch | 是否使用延遲加載 | |
mappedBy | 反向關聯(lián) | |
orphanRemoval | 講移除操作級聯(lián)到關聯(lián)的實體中 |
測試
一對多的時候雖然多方維持了兩者關系,但是我們把權限賦予了一方所以,刪除多方并不會級聯(lián)操作,刪除一方可以移除多方數(shù)據(jù)。所以下的測試可以通過
@Test public void testOrder() { Order defOrder1 = OrderMock.getDefOrder1(); List <OrderItem> allDefOrder = OrderItemMock.getAllDefOrder(); // order 維持orderItem關系 defOrder1.setOrderItems(allDefOrder); // orderItem維持order關系 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); }
多對多
配置方法
和一對多、一對一不同,多對多沒法只使用兩個數(shù)據(jù)實體完成相互之間的關系維護,這個時候需要一個關聯(lián)的中間表來維護他們之間的關系。
對于中間表的配置,你大可不去進行額外的配置讓JPA自動生成,當然你也可以使用之前介紹的@JoinTable
注解進行配置。
權限
和一對多、一對一不同,多對多一般是沒有辦法設置級聯(lián)操作的。因為雙方對應的都是集合對象,
而雙方某一條數(shù)據(jù)都可能被對方多條數(shù)據(jù)關聯(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", // 定義中間表中關聯(lián)User表的外鍵名 joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") }, // 定義中間表中關聯(lián)UserTag表的外鍵名 inverseJoinColumns = { @JoinColumn(name = "tags_id", referencedColumnName = "id") } ) private Set<UserTag> tags; }
用戶標簽表
@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 | 描述一個多對多的關聯(lián).多對多關聯(lián)上是兩個一對多關聯(lián),但是在ManyToMany描述中,中間表是由ORM框架自動處理 | |
targetEntity | 目標類的實體 | |
cascade | 關聯(lián)到目標的操作 | |
fetch | 是否使用延遲加載 | |
mappedBy | 反向關聯(lián) | |
@JoinTable | JoinTable在many-to-many關系的所有者一邊定義。如果沒有定義JoinTable,使用JoinTable的默認值。 | |
catalog | 表的目錄 | |
foreignKey | 外鍵約束,創(chuàng)建表的時候使用 | |
indexes | 表的索引,在生成表的時候使用 | |
@JoinColumns | 關系存在多個JoinColumn,用JoinColumns定義多個JoinColumn的屬性。 | |
foreignKey | 此列的外鍵約束 | |
value | JoinColumn的集合 | |
inverseJoinColumns | 聯(lián)接表的外鍵列,該列引用不擁有關聯(lián)的實體的主表 | |
joinColumns | 聯(lián)接表的外鍵列,該列引用擁有關聯(lián)的實體的主表 | |
name | 進行連接的表名稱 | |
schema | 表的命名空間 |
測試
多對多的情況下,我們使用雖然User和UserTag的關系由中間表維護,但是我們在User中配置了中間表的關系維護,所以此時刪除用戶的時候可以成功刪除,且可以成功移除中間表數(shù)據(jù),但是這個時候移除UserTag數(shù)據(jù)的時候,卻會拋出DataIntegrityViolationException異常。只能通過User移除中間關系。
@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); // 此時會報錯,因為中間表關系為User維護 try { userTagRepository.delete(defTag2); } catch (Exception e) { log.info(e.getCause().getMessage()); Assert.assertTrue(e instanceof DataIntegrityViolationException); } // 修改User表中關系 defUser1.setTags(new HashSet <>()); defUser1.getTags().add(defTag1); defUser1 = userRepository.save(defUser1); // 此時可以刪除 userTagRepository.delete(defTag2); // 直接刪除用戶沒問題 userRepository.delete(defUser1); }
本篇文章涉及的源碼下載地址:https://gitee.com/daifyutils/springboot-samples
到此這篇關于Spring Boot 整合JPA 數(shù)據(jù)模型關聯(lián)操作(一對一、一對多、多對多)的文章就介紹到這了,更多相關Spring Boot 整合JPA 數(shù)據(jù)模型關聯(lián)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot中的6種API請求參數(shù)讀取方式總結
使用Spring Boot開發(fā)API的時候,讀取請求參數(shù)是服務端編碼中最基本的一項操作,Spring Boot中也提供了多種機制來滿足不同的API設計要求,通過本文,為大家總結6種常用的請求參數(shù)讀取方式,需要的朋友可以參考下2024-07-07使用Eclipse開發(fā)工具如何解決Java Compiler中Annotation Processin不出現(xiàn)的問題
這篇文章主要介紹了使用Eclipse開發(fā)工具如何解決Java Compiler中Annotation Processin不出現(xiàn)的相關資料,需要的朋友可以參考下2015-11-11Java Date類常用示例_動力節(jié)點Java學院整理
在JDK1.0中,Date類是唯一的一個代表時間的類,但是由于Date類不便于實現(xiàn)國際化,所以從JDK1.1版本開始,推薦使用Calendar類進行時間和日期處理。這里簡單介紹一下Date類的使用,需要的朋友可以參考下2017-05-05關于@GetMapping和@GetMapping(value=““)的區(qū)別
這篇文章主要介紹了關于@GetMapping和@GetMapping(value=““)的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05java并發(fā)編程專題(十)----(JUC原子類)基本類型詳解
這篇文章主要介紹了java JUC原子類基本類型詳解的相關資料,文中示例代碼非常詳細,幫助大家更好的理解和學習,感興趣的朋友可以了解下2020-07-07