2
votes

I have a similar question as below, but the solution didn't solve my problem.

hibernate composite Primary key contains a composite foreign key, how to map this

I am trying to join 2 tables, each having a composite primary key with partial foreign key reference.

Table A
--------
f1 (pk)
f2 (pk)
f3 (pk)
f4 (pk)

Table B
--------
f1 (pk, fk)
f2 (pk, fk)
f5 (pk)
f6 (pk)

I created A, APK, B, BPK

In A:

private Set<B> bSet;
@OneToMany(targetEntity=B.class, cascade = CascadeType.ALL, mappedBy= "bpk.a")    
public Set<MovesEntity> getBSet() {
    return bSet;
}

In BPK:

@ManyToOne(fetch=FetchType.EAGER)
@JoinColumns({
    @JoinColumn(name="f1", referencedColumnName="f1", nullable=false, insertable=false, updatable = false),
    @JoinColumn(name="f2", referencedColumnName="f2", nullable=false, insertable=false, updatable = false)
})
public A getA() {
    return a;
}

The above approach gives me this Exception:

AnnotationException: referencedColumnNames(f1, f2) of entity.BPK.bpk.a 
referencing com.example.entity.A not mapped to a single property

Can you please help ?

1
JPA doesn't allow partial primary key. B needs a foreign key for each of A's primary key fields to uniquely identify the A. If only f1 and f2 uniquely identify an A, then they should be its primary key (not f2 and f3). - Chris
Thanks a lot Chris. This clears my question!.. In this case, i have to annotate f3 and f4 as @Basic in APK instead of id or embeddedId. I am using this service only to SELECT, so i guess its okay ? - Mjasmin
f3 and f4 shouldn't be in APK, just in A as basics. - Chris
Yes, as Basic in A - Thanks! - Mjasmin

1 Answers

1
votes

Assuming f1 and F2 uniquely identify A and exist within APK, you can use JPA 2.0's derived IDs for this in a few ways. Easiest to show would be:

@Entity
@IdClass(BPK.class)
public class B {
  @ID
  String f5;
  @ID
  String f6;
  @ID
  @ManyToOne(fetch=FetchType.EAGER)
  @JoinColumns({
    @JoinColumn(name="f1", referencedColumnName="f1", nullable=false),
    @JoinColumn(name="f2", referencedColumnName="f2", nullable=false)
  })
  A a;
}

public class BPK {
  String f5;
  String f6;
  APK a;
}

Key points here are that B has a reference to A that control the foriegn key fields f1 and f2, and A's primary key is used within B's primary key - with the same name as the relationship. Another way to map it would be to make B's PK an embeddid id, but embedded IDs still cannot have reference mappings, so it might look:

@Entity
@IdClass(BPK.class)
public class B {
  @EmbeddedId
  BPK pk;
  @MapsId("apk")
  @ManyToOne(fetch=FetchType.EAGER)
  @JoinColumns({
    @JoinColumn(name="f1", referencedColumnName="f1", nullable=false),
    @JoinColumn(name="f2", referencedColumnName="f2", nullable=false)
  })
  A a;
}

@Embeddable
public class BPK {
  String f5;
  String f6;
  APK apk;
}

Notice the mapsId - this tells JPA that the columns in the embedded 'apk' reference use the foreign key fields from the reference mapping as pulled from A. JPA will populate the foreign keys for you from the reference mapping, important if you are using sequencing.