A JPA cascade Problem & detailed explanation of CascadeType

Posted by R_P on Thu, 28 Oct 2021 06:32:16 +0200

Problems encountered

First, I encountered the following problems when I wrote a many to many demo with springboot JPA to insert data:
detached entity passed to persist

The general meaning is that when inserting the data, the existing data in the cascade table is used. The id of the data already exists and cannot be inserted. Therefore: detached entity passed to persist.
What caused this problem?
This problem has been solved for a long time, and there are many strange statements on the Internet. Later, it suddenly dawned on why the data should be inserted, and what the possible operations are. Suddenly, I want to understand that it is a many to many operation. The feature of jpa's many to many operation is that it needs to be cascaded, and when cascading, the system may think it is inserting data, All data needs to be persistent, even if the existing data in the database is persistent again. Later, I found @ ManyToMany. Sure enough, the cascade permission of the annotation attribute is set: cascade = CascadeType.ALL. The cascade permission of CascadeType.ALL includes CascadeType.PERSIST, which is the culprit.

JPA multi pair multi-level demo

The cascade code is as follows:
User.java

package cn.kt.securitytest2.domin;

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

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 configures the foreign keys of the current object in the intermediate table (the first parameter is the field of the intermediate table, and the second parameter is the field corresponding to this table)
            joinColumns = {@JoinColumn(name = "uid", referencedColumnName = "id")},
            //Reversejoincolumns configures the foreign keys of the opposite object in the intermediate table
            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
 * Description:
 */
@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;

    //Many to many relational mapping
    @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 and role correspond to two database tables and an associated intermediate table user_role.

Detailed explanation of JPA cascade operation

As can be seen from the above code, the cascade permission of User and Role is CascadeType.ALL.
But through practice, it is concluded that do not give all permission to operate. The required cascading relationship should be selected according to the business needs. Otherwise, it may lead to great disaster.

Cascading properties:

  1. CascadeType.PERSIST
    Cascade persistence (save): when the owner entity is persisted, all relevant data of the entity will also be persisted. This attribute is the key to the above problem. When you save one piece of data a day, all associated data will be saved, whether there is one in the database or not, but sometimes we need such a cascade operation.
  2. CascadeType.REMOVE
    Cascade deletion: when deleting the current entity, the entities with mapping relationship with it will also be deleted.
  3. CascadeType.DETACH
    Cascade de pipe / free operation: if you want to delete an entity, but it has foreign keys that cannot be deleted, you need this cascade permission. It undoes all related foreign key associations.
  4. CascadeType.REFRESH
    Cascade refresh operation: suppose there is an order in the scenario, which is associated with many goods. This order can be operated by many people. At this time, a modifies the order and associated goods. At the same time, B also performs the same operation, but B saves the data first than A. when a saves the data, You need to refresh the order information and associated commodity information before saving the order and commodity.
  5. CascadeType.MERGE
    Cascade update (merge): when the data in Student changes, the data in Course will be updated accordingly.
  6. CascadeType.ALL
    Clear and clear, with all cascade operation permissions above.

Topics: Java Spring Back-end