spring-boothibernatejpajpa-2.1

Handling multiple ManyToOne associations in single entity with JPA


I have the following entity structure.

@Entity
class ParentA {
  @OneToMany(mappedBy = "parentA", cascade = CascadeType.ALL, orphanRemoval = true)
  private Set<Child> children = new HashSet<>();

  public void addChild(Child c) { children.add(c); c.setParentA(this); }
  public void removeChild(Child c) { children.remove(c); c.setParentA(null); }
}

@Entity
class ParentB {
  @OneToMany(mappedBy = "parentB")
  private Set<Child> children = new HashSet<>();

  public void addChild(Child c) { children.add(c); c.setParentB(this); }
  public void removeChild(Child c) { children.remove(c); c.setParentB(null); }
}

@Entity
class Child {
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "parent_a_id", nullable = true)
  private ParentA parentA;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "parent_b_id", nullable = true)
  private ParentB parentB;

  // Some other properties
}

It feels cumbersome to keep all the associations in sync in many cases. For example when using ParentAService to delete a child, the service needs to sync the ParentB entity. But it should not be the responsibility of ParentAService to modify ParentB i.e.


class ParentAService {
  @Transactional
  public void deleteChild(ParentA parentA, Child child) {
    // transaction details

    child.getParentB().removeChild(child)
    parentA.removeChild(child);
  }
}

Is there a correct way of dealing with these kinds of associations or is this a code smell?


Solution

  • Add a method on the Child class to cleanup the dependencies it has, call that from your service. You could even annotate that method with @PreRemove to have it being called before the actual delete to do the cleanup.

    @Entity
    class Child {
      @ManyToOne(fetch = FetchType.LAZY)
      @JoinColumn(name = "parent_a_id", nullable = true)
      private ParentA parentA;
    
      @ManyToOne(fetch = FetchType.LAZY)
      @JoinColumn(name = "parent_b_id", nullable = true)
      private ParentB parentB;
    
      // Some other properties
      @PreRemove
      public void cleanup() {
        parentA.removeChild(this)
        parentB.removeChild(this)
      }
    }