Search code examples
javaspringhibernateone-to-many

Spring JPA, OneToMany referencing part of primary key


Given A class

@Entity
@Table(name = "ATABLE")
public class A implements Serializable {
    public static final String DB_ID = "AID";
    public static final String DB_MARKET = "AMARKET";

    @EmbeddedId
    @AttributeOverrides({
            @AttributeOverride(name = "id", column = @Column(name = DB_ID)),
            @AttributeOverride(name = "market", column = @Column(name = DB_MARKET))
    })
    public AIdClass id;

    @OneToMany
    @JoinColumn(name = B.DB_MARKET, referencedColumnName = DB_MARKET, insertable = false, updatable = false)
    public List<B> bs;
}

and B class

@Entity
@Table(name = "BTABLE")
public class B implements Serializable {
    public static final String DB_ID = "BID";
    public static final String DB_MARKET = "BMARKET";

    @EmbeddedId
    @AttributeOverrides({
            @AttributeOverride(name = "id", column = @Column(name = DB_ID)),
            @AttributeOverride(name = "market", column = @Column(name = DB_MARKET))
    })
    public BIdClass id;
}

Each entities might be listed, but using that @OneToMany relation does throw the following error

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory'
Caused by: org.hibernate.AnnotationException: Unable to map collection fr.zzz.domain.A.bs
Caused by: org.hibernate.AnnotationException: referencedColumnNames(AMARKET) of fr.zzz.domain.A.bs referencing fr.zzz.domain.B not mapped to a single property

An A entity relates to multiple B on A.AMARKET = B.BMARKET


Solution

  • You are having this issue because there is be a possibility that composite keys (AID,AMARKET) and (BID,BMARKET) will not be unique when doing a join on keys AMARKET = BMARKET. Therefore you are getting the error not mapped to a single property. Please bear with me, use the following sample data to analyze the issue;

    For table A

    AID  AMARKET 
     1      1       
     2      1    
     3      2 
    

    For table B

    BID  BMARKET
     1      1   
     2      2
     3      2
    

    The above scenario is absolutely possible (at least on a database level) and just using AMARKET and BMARKET to make the join @OneToMany is not possible. What is possible though is to use @ManyToMany, this will immediately solve the issue if the table structures are correct.

    But what if it is required to use @OneToMany due to some business constraint. Then you must update the table B to include A.AID and add a foreign key constraint to ensure data integrity. Then only the result set will be valid for the relationship @OneToMany. And the join will be as follows;

    @OneToMany
    @JoinColumn(name = B.DB_AID, referencedColumnName = DB_ID)
    @JoinColumn(name = B.DB_MARKET, referencedColumnName = DB_MARKET)
    public List<B> bs;
    

    In B:

    @Entity
    @Table(name = "BTABLE")
    public class B implements Serializable {
        public static final String DB_ID = "BID";
        public static final String DB_MARKET = "BMARKET";
        public static final String DB_AID = "AID";
    
        @EmbeddedId
        @AttributeOverrides({
                @AttributeOverride(name = "id", column = @Column(name = DB_ID)),
                @AttributeOverride(name = "market", column = @Column(name = DB_MARKET))
        })
        public BIdClass id;
    
        @Column(name = DB_AID)
        private Long aid; // assuming aid is a Long
    }
    

    Now the join is being done on the composite primary key of A.