I have 2 main entities: Client and Bundle. They have a bidirectional ManyToMany relationship which works as expected. Examples:
- A client has several bundles. I delete the client. All the bundle entities are updated with that client removed, but the bundles entities remain.
- I update a client and remove a bundle from its
Set<Bundle>. That same bundle will have itsSet<Client>updated with the client removed. The bundle entity will remain.
All of this works both ways.
Both Client and Bundle have a OneToMany relationship, as composite key, with an entity ClientBundleApproval. which also has an an enum (APPROVED, REJECTED, PENDING).
My problem is: Assuming I have several entities configured. A client has several bundles, and several approvals pending. When I'm trying to delete a client, it will not be deleted if there exists a row with composite key that includes it in ClientBundleApproval.
My expected outcome was, that all rows in ClientBundleApproval containing that client's ID would be deleted, and all bundles would have their collection of related approvals removed, and clients updated (basically a cascade delete, but only to the composite key table and not to the other entity). Adding a cascade delete causes deleting a client to delete the composite key, but also the bundle (which is not the wanted behavior).
I need help figuring out if my configuration is the problem, or is it just not possible. I assume that maybe I should remove the composite key rows first, but I am required to keep the logic of the services handling those entities separate. (for example, in a ClientService to not have to inject other entities' JpaRepository<T, ID>)
Implementation notes: I am using Spring JPA, so for every entity I have an interface extending Spring's JpaRepository<T, ID>, which I inject to service classes and do CRUD operations with instead of an EntityManager.
I have tried removing the mappedBy from the OneToMany, different types of cascade (both in the relationship declaration and using @Cascade), with and without orphan removal. Nothing I could think of worked.
Entities and a diagram:
Client (I removed some boilerplate)
@Entity
@Table(name = "client_details")
public class Client implements Serializable {
@JoinTable(
name = "client_bundle",
joinColumns = { @JoinColumn(name = "client_id") },
inverseJoinColumns = { @JoinColumn(name = "bundleName") }
)
@Column(name = "bundles")
@ManyToMany(fetch = FetchType.EAGER /* all types of cascade except delete */)
private Set<Bundle> clientBundles;
@OneToMany(mappedBy = "client", fetch = FetchType.EAGER, orphanRemoval = true /* all types of cascade except delete */)
private List<ClientBundleApproval> approvals;
}
Bundle
@Entity
@Table(name = "bundle")
public class Bundle implements Serializable {
@ManyToMany(mappedBy="clientBundles", fetch = FetchType.EAGER /* all types of cascade except delete */)
private Set<Client> clients;
@OneToMany(mappedBy = "bundle", fetch = FetchType.EAGER, orphanRemoval = true /* all types of cascade except delete */)
private List<ClientBundleApproval> approvals;
}
ClientBundleApproval
@Entity
@Table(name = "client_bundle_approval")
public class ClientBundleApproval implements Serializable {
@EmbeddedId
private ClientBundleKey clientBundleKey;
@ManyToOne(fetch = FetchType.EAGER /* all types of cascade except delete */)
@JoinColumn(name = "client", referencedColumnName = "id")
@MapsId("clientId")
private Client client;
@ManyToOne(fetch = FetchType.EAGER /* all types of cascade except delete */)
@JoinColumn(name = "bundle", referencedColumnName = "id")
@MapsId("bundleId")
private Bundle bundle;
@Enumerated(EnumType.STRING)
private ClientBundleApprovalStatus approvalStatus;
}
ClientBundleKey
@Embeddable
public class ClientBundleKey implements Serializable{
private String clientId;
private String bundleId;
}
