Hibernate foreign key with a part of composite primary key
Asked Answered
B

2

17


I have to work with Hibernate and I am not very sure how to solve this problem, I have 2 tables with a 1..n relationship like this:

-------
TABLE_A
-------
col_b (pk)
col_c (pk)
[other fields]

-------
TABLE_B
-------
col_a (pk)
col_b (pk) (fk TABLE_A.col_b)
col_c (fk TABLE_A.col_c)
[other fields]

How can I manage this with Hibernate?

I do not have any idea how to declare a foreign key that would contain a part of primary key.

My database schema is generated from the Hibernate model.

Byronbyrum answered 17/9, 2015 at 8:8 Comment(7)
If you want to fight hibernate... go ahead and use composite keys. If you want a much easier life, ensure each table has a single primary key column. Trust me, it's not worth the fight... it's a battle which will cost you many hours in the futureObey
What do you mean by "part of primary key"? Composite primary keys in Hibernate are quite simple, but you shouldn't try to complicate that by trying to use only a part of it. Basicly you need a composite key class. There is an answer here with all basic details: #3585534Klement
There's a fairly thorough answer here: https://mcmap.net/q/116555/-how-to-map-a-composite-key-with-jpa-and-hibernateRhiamon
For one-to-many mappings you don't require a composite key. It is so so easier to manage this situation without using composite key.Sheaff
@LanceJava In this case I need to use composite keys because of readability.Byronbyrum
@Rhiamon This answer does not help at all in my case. It is only about how to define a composite key.Byronbyrum
https://mcmap.net/q/746116/-hibernate-model Could you please suggest on this query.Femmine
B
15

I have found two solutions to this problem.

The first one is rather a workaround and is not so neat as the second one.

Define the primary key of the B entity as composite key containing col_a, col_b, and col_c and what was supposed to be the primary key in the first place, define as unique constraint. The disadvantage is that the column col_c is not really conceptually a part of primary key.

@Entity
class A {
  @Id
  private int b;
  @Id
  private int c;
}

@Entity
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = { "a", "b" }) })
class B {
  @Id
  private int a;

  @Id
  @ManyToOne(optional = false)
  @JoinColumns(value = {
          @JoinColumn(name = "b", referencedColumnName = "b"),
          @JoinColumn(name = "c", referencedColumnName = "c") })
  private A entityA;
}

The second uses @EmbeddedId and @MapsId annotations and does exactly what I wanted to be done at the very beginning.

@Entity
class A {
  @Id
  private int b;
  @Id
  private int c;
}

@Embeddable
class BKey {
  private int a;
  private int b;
}

@Entity
class B {
  @EmbeddedId
  private BKey primaryKey;

  @MapsId("b")
  @ManyToOne(optional = false)
  @JoinColumns(value = {
          @JoinColumn(name = "b", referencedColumnName = "b"),
          @JoinColumn(name = "c", referencedColumnName = "c") })
  private A entityA;
}
Byronbyrum answered 18/9, 2015 at 14:13 Comment(6)
To get above code work you also need to implement Serializable for all above classesPolyzoan
I tried this way and I'm getting Unable to find column reference in the @MapsId mapping: c. Any idea?. TIAFarmergeneral
Is it a runtime or compile error? Could you post your code somewhere so that I can have a look at it?Byronbyrum
Is it possible to have MapsId for two attrributes? For example, if there was an attribute 'd' which is also part of both keys (same as b)?Hernia
@Hernia Maybe try with another @MapsId("d") annotation, so that you will have two annotations in the end: @MapsId("b") and @MapsId("d").Byronbyrum
Unfortunately it is not possible to have multiple ´@MapsId´ annotations on the same attribute. But I managed to solve it for my use case by using ´@JoinFormula´ instead of ´@JoinColumn´ for the columns that are used in Primary Key as well as Foreign Key.Hernia
A
-1

Jagger's second solution that is my first reaction, with @EmbededId and @MapsId. The following is another way based on his second solution but without using @MapsId. `

@Entity
class A {
  @Id
  private int b;
  @Id
  private int c;
}

@Embeddable
class BKey {
  private int a;
  private int b;
}

@Entity
class B {
  @EmbeddedId
  private BKey primaryKey;

  @ManyToOne(optional = false)
  @JoinColumns(value = {
          @JoinColumn(name = "b", referencedColumnName = "b", insertable= false, updatable= false),
          @JoinColumn(name = "c", referencedColumnName = "c") }, )
  private A entityA;
}
Aloha answered 12/2, 2016 at 2:20 Comment(1)
This won't work and ends with an exception org.hibernate.AnnotationException: Mixing insertable and non insertable columns in a property is not allowed There is a Hibernate bug for it .Byronbyrum

© 2022 - 2024 — McMap. All rights reserved.