2

I have 2 main entities: Client and Bundle. They have a bidirectional ManyToMany relationship which works as expected. Examples:

  1. A client has several bundles. I delete the client. All the bundle entities are updated with that client removed, but the bundles entities remain.
  2. I update a client and remove a bundle from its Set<Bundle>. That same bundle will have its Set<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;
}

diagram

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
maydawn
  • 498
  • 8
  • 20
  • https://stackoverflow.com/a/21277087/66686 – Jens Schauder Nov 13 '17 at 07:34
  • @JensSchauder I'm not sure that I can understand how that answer helps me. Both of my entities `Client` and `Bundle` are completely independent of each other (a bundle can have no clients and vice versa). Regarding `ClientBundleApproval`, what I understand from that answer is that I'm not supposed to have a `JpaRepository` for it, and I should manage this table by updating the client. Is that correct ? And even then, I am still facing the same problem. And the last paragraph states not to expose those `JpaRepository` directly to clients. I do not do that, I use them in dedicated services – maydawn Nov 13 '17 at 08:31
  • Sorry for the confusion. It's not intended to fix your current problem. But you mention: "I am using Spring JPA, so for every entity I have an interface extending Spring's JpaRepository" which is a common antipattern discussed in the answer I linked to. – Jens Schauder Nov 13 '17 at 08:55
  • @maydawn hi! I've met the same problem. Do you remember how you had fixed it? – nikiforovpizza Mar 30 '18 at 10:54

0 Answers0