Could someone please help me with doing the bi-directional one-to-one JPA mapping for the following relationship using the composite primary key using Hibernate/JPA?

- 12,193
- 32
- 113
- 153
1 Answers
You can use an @Embeddable
to map the composite primary key of both entities and a @MapsId
annotation on the address attribute of your User
entity to share the primary key.
But before I show you the mapping, please be aware that User
is a reserved word in most databases. I recommend you choose a different name for the entity. In my example, I changed it to Person
.
Let's start with the @Embeddable
that maps the primary keys. I named the class AddressKey
. It's a simple Java class that implements the Serializable
interface and has the attributes xId
and yId
. You also need to implement the equals
and hashCode
methods.
@Embeddable
public class AddressKey implements Serializable {
private Long xId;
private Long yId;
public AddressKey() {}
public AddressKey(Long xId, Long yId) {
this.xId = xId;
this.yId = yId;
}
// omit getter and setter for readability
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((xId == null) ? 0 : xId.hashCode());
result = prime * result + ((yId == null) ? 0 : yId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AddressKey other = (AddressKey) obj;
if (xId == null) {
if (other.xId != null)
return false;
} else if (!xId.equals(other.xId))
return false;
if (yId == null) {
if (other.yId != null)
return false;
} else if (!yId.equals(other.yId))
return false;
return true;
}
}
If you annotate the attribute with @EmbeddedId
, you can use the AddressKey
embeddable to map the primary key of the Address
class.
@Entity
public class Address {
@EmbeddedId
private AddressKey id;
private String city;
private String street;
private String country;
@OneToOne(mappedBy = "address")
private Person person;
// omit getter and setter methods
}
The mapping of the Person
entity looks similar. In addition to the primary key mapping, you need to annotate the address
attribute, which maps the association to the Person
entity, with @MapsId
. That tells Hibernate that it shall use the primary key of the associated Address
entity as the primary of the Person
entity.
You also need to annotate the address
attribute with 2 @JoinColumn
annotations to map the foreign keys to the xId
and yId
columns of your Person table. Without these annotations, Hibernate would map them to the columns address_xId
and address_yId
.
@Entity
public class Person {
@EmbeddedId
private AddressKey id;
private String name;
private String society;
@OneToOne
@JoinColumn(name="xId", referencedColumnName="xId")
@JoinColumn(name="yId", referencedColumnName="yId")
@MapsId
private Address address;
// omit getter and setter methods for readability
}

- 3,012
- 9
- 23
-
Thanks for the response Thorben. Just 2 questions I've got. **1)** Should it not be `@JoinColumns` at the `address` field in the `Person` entity, like `@OneToOne @JoinColumns({ @JoinColumn(name="xId", referencedColumnName="xId"), @JoinColumn(name="yId", referencedColumnName="yId")}) @MapsId private Address address;` **2)** Is `@MapsId` really needed? – skip Jan 26 '19 at 23:26
-
2Hi Skip, 1) Since Hibernate 5.2 and JPA 2.2, the [`@JoinColumn` annotation is repeatable](https://thoughts-on-java.org/benefits-repeatable-annotations-hibernate-5-2/) and you no longer need to wrap it in a @JoinColumns annotation. 2) Yes, `@MapsId` is required to tell Hibernate that it shall use the primary key value of the associated `Address` entity as the primary key value of the `Person` entity – Thorben Janssen Jan 29 '19 at 09:56
-
@ThorbenJanssen Can we somehow make xId to be autogenerated using IDENTITY strategy (MySQL 5.7)? – Eugene Maysyuk Apr 26 '20 at 15:55
-
Had a similar case with @OneToOne with composite and shared primary key by two entities, just that it was unidirectional. So it didn't work until I labelled @MapsId with the name of the shared key (named "key" in my case: ```public class EnterpriseGroupsDetailsEntity{ @EmbeddedId private GroupKey key; @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name =.., referencedColumnName =.., insertable = false, updatable = false) @JoinColumn(name =.., @MapsId("key") private GroupEntity groupEntity; ... ``` – Rnam Mar 15 '23 at 20:15