Hibernate H2更新/删除时的参照完整性约束冲突及Queue持久化问题
Hey there! Let's break down your three main problems and walk through how to fix each one step by step.
1. Referential Integrity Error When Deleting a User
The error you're seeing happens because your ExperimentierStation and User have a many-to-many relationship—JPA automatically creates an intermediate join table (EXPERIMENTIERSTATION_USER) that links the two entities. When you try to delete a User, rows in this join table still reference the user's ID, violating the foreign key constraint.
Why CascadeType.MERGE Isn't Working
CascadeType.MERGE only propagates merge operations from the parent to child entities—it has no effect on delete operations, so that's why you still get the error.
The Fix:
First, you need to properly set up the bidirectional many-to-many association (more on this next), then follow this safe workflow to delete a user:
- Fetch all
ExperimentierStationentities associated with the user you want to delete - Remove the user from the
benutzerlist of each of these stations - Save the updated stations to the database
- Now you can delete the user without violating constraints
If your business logic allows deleting all associated stations when a user is removed, you could add CascadeType.REMOVE to the @ManyToMany annotation—but this is rarely the desired behavior for many-to-many relationships, so proceed with caution.
2. Using mappedBy for Bidirectional Associations (No Table Name Needed!)
mappedBy doesn't require a table name—it refers to the field name in the owning side of the association (the entity that manages the join table). Here's how to set it up:
Update the User Class
Add the bidirectional association pointing back to ExperimentierStation:
@Data @Entity // ... keep existing named queries and annotations public class User { // ... keep all existing fields // Add this bidirectional many-to-many association @ManyToMany(mappedBy = "benutzer") private List<ExperimentierStation> experimentierStationen; // ... keep existing toString method }
mappedBy = "benutzer"tells JPA that the owning side of the relationship is thebenutzerfield in theExperimentierStationclass.- JPA will automatically use the existing join table for this association—no need to specify a table name here.
3. Persisting a Queue in ExperimentierStation
JPA doesn't support persisting Queue directly because it's an interface—JPA needs to know the concrete implementation class to use. Here's how to fix this:
Option 1: Persist a Queue of Entities (e.g., ProzessSchritt)
If your queue holds entity objects, use @OneToMany with a concrete implementation like LinkedList:
@Data @Entity // ... keep existing annotations and fields public class ExperimentierStation { // ... keep all existing fields // Persisted Queue for entities @OneToMany(targetEntity = ProzessSchritt.class) @JoinColumn(name = "es_id") // Foreign key linking to ExperimentierStation's esID private Queue<ProzessSchritt> queue = new LinkedList<>(); // ... keep existing constructors }
Option 2: Persist a Queue of Value Types (e.g., Strings, Enums)
If your queue holds simple value types, use @ElementCollection:
@Data @Entity // ... keep existing annotations and fields public class ExperimentierStation { // ... keep all existing fields @ElementCollection(targetClass = String.class) @CollectionTable(name = "ES_QUEUE_ITEMS", joinColumns = @JoinColumn(name = "es_id")) @Column(name = "queue_item") private Queue<String> queue = new LinkedList<>(); // ... keep existing constructors }
- We initialize the queue with
LinkedList(a concrete Queue implementation) so JPA knows how to instantiate it when loading from the database.
Full Updated Code Snippets
Modified ExperimentierStation Class
import lombok.*; import javax.persistence.*; import java.util.List; import java.util.Queue; import java.util.LinkedList; /** Experimenting stations data class */ @Data @Entity @NamedQueries({ @NamedQuery(name = "ExperimentierStation.findAllInLocation", query = "SELECT es FROM ExperimentierStation es WHERE es.standort = :standort"), @NamedQuery(name = "ExperimentierStation.getByStatus", query = "SELECT es FROM ExperimentierStation es WHERE es.status = :status"), @NamedQuery(name = "ExperimentierStation.getAll", query = "SELECT es FROM ExperimentierStation es") }) @RequiredArgsConstructor @NoArgsConstructor public class ExperimentierStation { /** The station's id */ @NonNull @Id private int esID; /** The station's location */ @NonNull @ManyToOne private Standort standort; @NonNull private String name; /** The station's status */ @NonNull private Enum<ExperimentierStationZustand> status; /** Conditions for using an experimenting station */ @OneToMany private List<Bedingung> bedingungen; @NonNull @ManyToMany(cascade=CascadeType.MERGE, fetch = FetchType.LAZY) private List<User> benutzer; @OneToOne private ProzessSchritt currentPS; // Persisted Queue example (adjust based on your actual data type) @OneToMany(targetEntity = ProzessSchritt.class) @JoinColumn(name = "es_id") private Queue<ProzessSchritt> queue = new LinkedList<>(); }
Modified User Class
import lombok.*; import javax.persistence.*; import java.time.LocalDateTime; import java.util.List; /** * This class is used to create user objects */ @Data @Entity @NamedQueries({ @NamedQuery(name = "User.findById", query = "SELECT u from User u WHERE u.id = :id"), @NamedQuery(name = "User.findByUsername", query = "SELECT u from User u WHERE u.username = :username"), @NamedQuery(name = "User.findByEmail", query = "SELECT u from User u WHERE u.email = :email"), @NamedQuery(name = "User.getAll", query = "SELECT u FROM User u") }) @RequiredArgsConstructor(access = AccessLevel.PUBLIC) @NoArgsConstructor(access = AccessLevel.PUBLIC) public class User { /** * User's id */ @Id @NonNull private int id; /** * The user's name */ @NonNull private String vorname; /** * The user's surname */ @NonNull private String nachname; /** * User's email address */ @NonNull private String email; /** * The user's phone number */ @NonNull private String telefonnummer; /** * User's username */ @NonNull private String username; /** * User's hashed password */ @NonNull private String password; /** * Is the user verified? */ @NonNull private boolean wurdeVerifiziert; /** * Creation date of the user object */ @NonNull private LocalDateTime erstellungsDatum; /** * The role's of the user */ @NonNull @ElementCollection private List<Role> rollen; /** * The User's language preference */ @NonNull private String language; // Bidirectional many-to-many association @ManyToMany(mappedBy = "benutzer") private List<ExperimentierStation> experimentierStationen; public String toString() { return vorname + nachname; } }
Final Notes
- Always remember to synchronize both sides of a bidirectional association when adding/removing entities (e.g., when adding a user to a station, also add the station to the user's
experimentierStationenlist to keep the model consistent). - For the queue, choose the annotation based on whether you're storing entities or simple values.
内容的提问来源于stack exchange,提问作者leolion3

