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。

推薦閱讀: