SpringBoot

记一次JPA级联问题&CascadeType详解

Nick · 10月28日 · 2021年 · 本文3093字 · 阅读8分钟704

遇到的问题

首先我在用springboot-jpa写一个多对多demo,进行插入数据的时候遇到了如下的问题:
detached entity passed to persist
记一次JPA级联问题&CascadeType详解-左眼会陪右眼哭の博客
大概的意思是该数据插入的时候,使用了级联表中已经有的数据,该条数据的id已经存在,无法继续插入,因此:detached entity passed to persist。
这个是什么问题产生的呢?
这个问题搞了很久,网上的说法也是千奇百怪,后来突然恍然一悟,为什么会要插入数据插不进去,可能会发生的操作是什么,突然就想明白,是做了多对多操作,jpa的多对多操作的特点就是需要做级联,而级联的时候就可能系统认为是插入数据,所有的数据都需要进行持久化,就算数据库里面已经有的数据也进行了再次持久化。后来找到了@ManyToMany,果然注解属性的级联权限设置了:cascade = CascadeType.ALL,其中CascadeType.ALL的级联权限中包括了CascadeType.PERSIST,这个就是罪魁祸首。

JPA多对多级联的demo

级联代码如下:
User.java

package cn.kt.securitytest2.domin;

/**
 * Created by tao.
 * Date: 2021/10/27 10:39
 * 描述:
 */

import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import java.io.Serializable;
import java.util.*;

@Entity
@Getter
@Setter
@Table(name = "user")
public class User implements UserDetails, Serializable {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    @Column(name = "enabled")
    private Boolean enabled;

    @Column(name = "locked")
    private Boolean locked;

    @Column(name = "expired")
    private Boolean expired;

    @Column(name = "credentialsexpire")
    private Boolean credentialsExpire;

    @ManyToMany(targetEntity = Role.class, fetch = FetchType.EAGER, cascade = CascadeType.MERGE)
    @JoinTable(name = "user_role",
            //joinColumns配置当前对象在中间表中的外键(第一个参数是中间表的字段,第二个参数是本表对应的字段)
            joinColumns = {@JoinColumn(name = "uid", referencedColumnName = "id")},
            //inverseJoinColumns配置对方对象在中间表中的外键
            inverseJoinColumns = {@JoinColumn(name = "rid", referencedColumnName = "id")}
    )
    private Set<Role> roles = new HashSet<Role>();

}

Role.java

package cn.kt.securitytest2.domin;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

/**
 * Created by tao.
 * Date: 2021/10/27 10:39
 * 描述:
 */
@Entity
@Getter
@Setter
@Table(name = "role")
public class Role implements Serializable {

    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "nameen")
    private String nameEN;

    @Column(name = "namezh")
    private String nameZh;

    //多对多关系映射
    @ManyToMany(mappedBy = "roles", fetch = FetchType.EAGER)
    @JsonIgnore
    private Set<User> users = new HashSet<User>();

    @Override
    public String toString() {
        return "Role{" +
                "id=" + id +
                ", nameEN='" + nameEN + '\'' +
                ", nameZh='" + nameZh + '\'' +
                '}';
    }
}

其中user和role对应的是两张数据库表,还有一张关联的中间表user_role.

JPA级联操作的详解

通过以上的代码可以看到,User和Role的级联权限是CascadeType.ALL。
但经过实践得出:不要随便给all权限操作。应该根据业务需求选择所需的级联关系。否则可能酿成大祸。

级联的属性:
1. CascadeType.PERSIST
级联持久化(保存)操作:持久保存拥有方实体时,也会持久保存该实体的所有相关数据。这个属性就是造成上面问题的关键。当你保存一天条数据时,所有的关联数据都会进行保存,无论数据库里面有没有,但有时候我们是需要这样的级联操作的。
2. CascadeType.REMOVE
级联删除操作:删除当前实体时,与它有映射关系的实体也会跟着被删除。
3. CascadeType.DETACH
级联脱管/游离操作:如果你要删除一个实体,但是它有外键无法删除,你就需要这个级联权限了。它会撤销所有相关的外键关联。
4. CascadeType.REFRESH
级联刷新操作:假设场景 有一个订单,订单里面关联了许多商品,这个订单可以被很多人操作,那么这个时候A对此订单和关联的商品进行了修改,与此同时,B也进行了相同的操作,但是B先一步比A保存了数据,那么当A保存数据的时候,就需要先刷新订单信息及关联的商品信息后,再将订单及商品保存。
5. CascadeType.MERGE
级联更新(合并)操作:当Student中的数据改变,会相应地更新Course中的数据。
5. CascadeType.ALL
清晰明确,拥有以上所有级联操作权限。

0 条回应
在线人数:1人 来访统计
说谎
林宥嘉
隐藏