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;
}