Search code examples
javahibernatejpamany-to-manyhibernate-mapping

@ManyToMany JPA relation using a 2nd degree relationship in join table and UK


I have this entity model (simplified):

@Entity
class A {
  @Id
  String id;

  Collection<B> bs;
}

@Entity
class B {
  @Id
  String id;

  @ManyToOne
  @JoinColumn(name = "c_id", nullable = false)
  C c;
}

@Entity
class C {
  @Id
  String id;
}

What would be the best approach to add a join table between A and B (many-to-many relation) and enforce a composite UK using A.id and B.c.id?

I tried something like this:

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable
        (name = "a_b",
                joinColumns = @JoinColumn(name = "a_id"),
                inverseJoinColumns = {
                        @JoinColumn(name = "b_id", referencedColumnName = "id"),
                        @JoinColumn(name = "c_id", referencedColumnName = "c_id")},
                uniqueConstraints = @UniqueConstraint(name = "uk_a_c", columnNames = {"a_id", "c_id"}))

but I get a MultipleBagFetchException, which is very strange. It must be related to the fact that c_id is not part of the primary key. If I remove c_id from inverseJoinColumns it works as expected, but it's not what I need.


Solution

  • My final mapping looks like this:

    @Entity
    @EntityListeners(ABListener.class)
    @Table(name = "a_b", uniqueConstraints =
    @UniqueConstraint(name = "uk_rel_a_b", columnNames = {"a_id", "c_id"}))
    public class AB extends BaseEntity<Long> {
    
        private Long id;
    
        @ManyToOne(optional = false)
        @JoinColumn(name = "a_id")
        private A a;
    
        @ManyToOne(optional = false)
        @JoinColumn(name = "b_id")
        private B b;
    
        @ManyToOne(optional = false)
        @JoinColumn(name = "c_id")
        private C c;
    
        public AB(A a, B b) {
            this.a = a;
            this.b = b;
        }
        /* rest of the class */
    }
    

    And a listener to handle setting of C:

    public class ABListener {
    
        @PrePersist
        @PreUpdate
        void handleCUpdate(AB ab) {
            if (ab.getB() != null) {
                ab.setC(ab.getB().getC());
            }
        }
    
    }