Spring Boot 整合JPA 數據模型關聯使用操作(一對一、一對多、多對多)
表關聯
上一篇介紹瞭JPA的簡單使用,這一篇介紹JPA在表關聯上的使用
一對一
配置參數
JPA對於數據實體一對一映射使用的是@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包含的類別 級聯:給當前設置的實體操作另一個實體的權限 * CascadeType.ALL 級聯所有操作 * CascadeType.PERSIST 級聯持久化(保存)操作 * CascadeType.MERGE 級聯更新(合並)操作 * CascadeType.REMOVE 級聯刪除操作 * CascadeType.REFRESH 級聯刷新操作 * CascadeType.DETACH 級聯分離操作,如果你要刪除一個實體,但是它有外鍵無法刪除,這個級聯權限會撤銷所有相關的外鍵關聯。 */ @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包含的類別 級聯:給當前設置的實體操作另一個實體的權限 * CascadeType.ALL 級聯所有操作 * CascadeType.PERSIST 級聯持久化(保存)操作 * CascadeType.MERGE 級聯更新(合並)操作 * CascadeType.REMOVE 級聯刪除操作 * CascadeType.REFRESH 級聯刷新操作 * CascadeType.DETACH 級聯分離操作,如果你要刪除一個實體,但是它有外鍵無法刪除,這個級聯權限會撤銷所有相關的外鍵關聯。 */ @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,
這樣生成的表數據中,User和UserCart的關系將由UserCart負責維護。User表中並不會維護UserCart的信息。
而在User和UserInfo的關系中使用瞭中間表user_info_table
來維護雙方的關系,UserInfo實體中並沒有保存任何User的信息。
權限
在User、UserCart、UserInfo三者中User為數據存在的主體,其他兩個實體都是依托於User數據的存在而存在。
所以在權限中給User實體提供瞭全部全部權限。
註解
@OneToOne主要提供瞭下面的參數內容
註解 | 參數 | 描述 |
---|---|---|
@OneToOne | 描述一個 一對一的關聯 | |
targetEntity | 目標類的實體 | |
cascade | 關聯到目標的操作 | |
fetch | 是否使用延遲加載 | |
mappedBy | 反向關聯 |
測試
因為上面一對一的例子中權限被賦予給User表中,UserCart並沒有賦予任何權限,所以保存用戶的時候可以關聯保存用戶購物車,刪除購物車的時候並不會刪除用戶,但是刪除用戶的時候會刪除購物車
通過保存用戶關聯保存購物車
@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的組合我們可以實現雙向關聯。根據JPA規范,我們使用多方來維護關系。
通過在多方維護@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防止死循環 * @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表中的關聯字段(外鍵) name="order_id" ) // 防止json序列化死循環問題解決 @JsonBackReference private Order order; }
上門的例子中,訂單方為一端,訂單子項為多端,在多端除瞭使用瞭@ManyToOne
還使用瞭@JoinColumn
註解來標識Order主鍵創建到OrderItem表的列的名稱,
當然沒有此註解的時候JPA會根據默認規則生成一個列名稱。
權限
根據JPA規范一對多時候,關系的維護交給瞭多方來進行,但是很多時候多方的存在是依靠一方的。
比如(訂單、訂單子項)所以更新刪除的權限需要授權給一方(Order)。
@ManyToOne,@OneToMany
註解 | 參數 | 描述 |
---|---|---|
@ManyToOne | 一個多對一的映射,該註解標註的屬性通常是數據庫表的外鍵. | |
targetEntity | 目標類的實體 | |
cascade | 關聯到目標的操作 | |
fetch | 是否使用延遲加載 | |
@OneToMany | 一個 一對多的關聯,該屬性應該為集體類型,在數據庫中並沒有實際字段 | |
targetEntity | 目標類的實體 | |
cascade | 關聯到目標的操作 | |
fetch | 是否使用延遲加載 | |
mappedBy | 反向關聯 | |
orphanRemoval | 講移除操作級聯到關聯的實體中 |
測試
一對多的時候雖然多方維持瞭兩者關系,但是我們把權限賦予瞭一方所以,刪除多方並不會級聯操作,刪除一方可以移除多方數據。所以下的測試可以通過
@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); }
多對多
配置方法
和一對多、一對一不同,多對多沒法隻使用兩個數據實體完成相互之間的關系維護,這個時候需要一個關聯的中間表來維護他們之間的關系。
對於中間表的配置,你大可不去進行額外的配置讓JPA自動生成,當然你也可以使用之前介紹的@JoinTable
註解進行配置。
權限
和一對多、一對一不同,多對多一般是沒有辦法設置級聯操作的。因為雙方對應的都是集合對象,
而雙方某一條數據都可能被對方多條數據關聯。
代碼
用戶表
@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", // 定義中間表中關聯User表的外鍵名 joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") }, // 定義中間表中關聯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
提供的註解參數
註解 | 參數 | 描述 |
---|---|---|
@ManyToMany | 描述一個多對多的關聯.多對多關聯上是兩個一對多關聯,但是在ManyToMany描述中,中間表是由ORM框架自動處理 | |
targetEntity | 目標類的實體 | |
cascade | 關聯到目標的操作 | |
fetch | 是否使用延遲加載 | |
mappedBy | 反向關聯 | |
@JoinTable | JoinTable在many-to-many關系的所有者一邊定義。如果沒有定義JoinTable,使用JoinTable的默認值。 | |
catalog | 表的目錄 | |
foreignKey | 外鍵約束,創建表的時候使用 | |
indexes | 表的索引,在生成表的時候使用 | |
@JoinColumns | 關系存在多個JoinColumn,用JoinColumns定義多個JoinColumn的屬性。 | |
foreignKey | 此列的外鍵約束 | |
value | JoinColumn的集合 | |
inverseJoinColumns | 聯接表的外鍵列,該列引用不擁有關聯的實體的主表 | |
joinColumns | 聯接表的外鍵列,該列引用擁有關聯的實體的主表 | |
name | 進行連接的表名稱 | |
schema | 表的命名空間 |
測試
多對多的情況下,我們使用雖然User和UserTag的關系由中間表維護,但是我們在User中配置瞭中間表的關系維護,所以此時刪除用戶的時候可以成功刪除,且可以成功移除中間表數據,但是這個時候移除UserTag數據的時候,卻會拋出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 數據模型關聯操作(一對一、一對多、多對多)的文章就介紹到這瞭,更多相關Spring Boot 整合JPA 數據模型關聯內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 使用JPA單項一對多外鍵關聯
- cascade級聯關系操作案例詳解
- springboot+spring data jpa實現新增及批量新增方式
- jpa onetomany 使用級連表刪除被維護表數據時的坑
- 解決使用@ManyToMany查詢數據時的死循環問題