jpa onetomany 使用級連表刪除被維護表數據時的坑
jpa onetomany 使用級連表刪除被維護表數據時的坑
一、異常產生的場景
兩個實體類,為一對多的關系
主表 ,字段維護表,1個用戶可能有多個角色
實體類User,代碼如下:
package ywcai.ls.entity; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.OrderBy; import javax.persistence.Table; @Entity @Table(name="user") public class User implements Serializable{ /** * */ private static final long serialVersionUID = -7383132326629943397L; @Id @GeneratedValue(strategy= GenerationType.AUTO) @Column(name="id") private Long id; @Column(name="username") private String username; @Column(name="password") private String password; @OneToMany(cascade={CascadeType.ALL},fetch=FetchType.EAGER,mappedBy="user") //特別註意:onetomany標識這是級聯1對多的關系。cascade={CascadeType.ALL}表示主表的增查刪改都會直接通過關聯字段對從表進行相應操作。 //例如刪除主表的一個user實例,從表與user相關聯roles將被刪除。 //而fetch=FetchType.EAGER表示急加載,即指一旦主表進行瞭相應操作,則從表也將立即進行相應的級聯操作。 //例如,一旦讀取瞭user表的某一個實例,則user會立即加載Roles;而fetch=FetchType.LAZY為懶加載,當需要使用到getRolelist()方法時,才會讀取相關聯的級聯表數據 @OrderBy(value= "id ASC")//獲取的角色信息根據角色表的id進行升序排序 private Set<Roles> rolelist; public Set<Roles> getRolelist() { return rolelist; } public void setRolelist(Set<Roles> rolelist) { this.rolelist = rolelist; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } }
被維護的級聯表,多個Roles角色可能對應1個用戶
實體類Roles,代碼如下:
package ywcai.ls.entity; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity @Table(name="roles") public class Roles { @Id @GeneratedValue(strategy= GenerationType.AUTO) @Column(name="id") private Long id; @Column(name="username") private String username; @Column(name="rolename") private String rolename; @ManyToOne(cascade={CascadeType.REFRESH},fetch=FetchType.LAZY) @JoinColumn(name="userid")//加入一列作為外鍵 private User user; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getRolename() { return rolename; } public void setRolename(String rolename) { this.rolename = rolename; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } }
二、存在異常的問題
當Roles類級聯操作屬性使用(cascade={CascadeType.REFRESH},這樣主要為達到對從表的某一個用戶的權限進行操作,而不影響主表User。
這時候Roles的註解
@ManyToOne(cascade={CascadeType.REFRESH},fetch=FetchType.EAGER)
通過user對Roles進行增加操作時,無任何問題。但單獨對Roles表中的一項進行刪除,例如單獨刪除A用戶的管理員橘色,則無法刪除。
@Repository @Table(name="roles") @Qualifier("rolesRepository") public interface RolesRepository extends JpaRepository<Roles, Long > { @Transactional int deleteByUsernameAndRolename(String username,String rolename); }
首先通過JpaRepository約束接口刪除數據,必須開啟事務,否則報錯。
而開啟事務後,該刪除代碼可以執行,執行返回的結果也正常返回1,但實際的數據中卻沒有反應,數據無法被刪除。
三、異常原因分析
出現該問題的原因是Roles使用瞭CascadeType.REFRESH註解,Roles刪除瞭該表中的相應數據後,會自動試圖去刪除主表中該USER實體數據。但由於註解中僅賦予瞭CascadeType.REFRESH,讀取刷新數據的權限,因此刪除主表中該USER實體數據的語句是無法執行的,最終造成瞭整個deleteByUsernameAndRolename()事務的失敗回滾,結果就是要刪的數據沒刪掉。
若在Roles實體類增加CascadeType.REMOVE權限,雖deleteByUsernameAndRolename()的事務可以正常執行,但會造成主表USER用戶也被整體刪除,進而反過來造成Roles表中所有該USER的角色也都被刪除。顯然,這和業務需求也不符合。
四、解決方案
上訴的問題看起來已經是一個邏輯相悖的矛盾,無法解決。實際上隻需要將Roles中加載模式改為懶加載即可,User實體類的加載模式不需改變。
當刪除roles中記錄時不會立即觸發到對User實體類數據的刪除,也就不會報錯和造成失誤的回滾。
jpa onetomany的用法
one部分
時機項目中使用到的。
@OneToMany(cascade = CascadeType.ALL,fetch = FetchType.LAZY,mappedBy="dcpDataServiceManage") private List<DcpDataServiceTableFieldEntity> dataServiceTableFieldList;
註意這個mappBy 要和many部分字段對應上
many部分
/** * 關聯的 */ @ManyToOne @JoinColumn(referencedColumnName = "gid") private DcpDataServiceManageEntity dcpDataServiceManage;
這塊註意的是數據庫保存的是實體類的gid.。one部分mappBy就是這個字段名。
註意
這塊就可以省去中間的一張關聯表。由於項目的原因。不能采用jpa 自動建表的功能。這裡使用的是sql。 特別註意的是這個字段名在數據庫創建的時候要加GID的
像上面代碼塊的字段 根據表映射規則 數據庫存的字段應該是DCP_DATA_SERVICE_MANAGE_GID 這個要註意一下
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。
推薦閱讀:
- 使用JPA單項一對多外鍵關聯
- 解決使用@ManyToMany查詢數據時的死循環問題
- 使用JPA雙向多對多關聯關系@ManyToMany
- SpringData JPA中@OneToMany和@ManyToOne的用法詳解
- Spring Boot 整合JPA 數據模型關聯使用操作(一對一、一對多、多對多)