|

Hibernate Tip: Map a bidirectional one-to-one association with shared composite primary key


Take your skills to the next level!

The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.


Hibernate Tips is a series of posts in which I describe a quick and easy solution for common Hibernate questions. If you have a question for a future Hibernate Tip, please post a comment below.

Question:

Today’s question was inspired by a question I answered on StackOverflow:
How can I map a bidirectional one-to-one association that shares a composite primary key?

Solution:

This mapping consists of 2 main parts:

  1. You need to map a composite primary key that consists of multiple entity attributes.
  2. You need to model a bidirectional one-to-one association that shares the same primary key.

Map a composite primary key

There are multiple ways to map a composite primary key. My preferred option uses an embeddable as an embedded id. It requires a simple Java class that’s annotated with @Embeddable and has attributes and optional mapping annotations for all elements of the primary key. You can see an example of an embeddable called AddressKey with the attributes xId and yId in the following code snippet.

@Embeddable
public class AddressKey implements Serializable {

	private Long xId;
	private Long yId;
	
	public AddressKey() {}
	
	public AddressKey(Long xId, Long yId) {
		super();
		this.xId = xId;
		this.yId = yId;
	}

	public Long getxId() {
		return xId;
	}

	public void setxId(Long xId) {
		this.xId = xId;
	}

	public Long getyId() {
		return yId;
	}

	public void setyId(Long yId) {
		this.yId = yId;
	}

	@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;
	}
}

You can use this embeddable as an attribute in your entity class and annotate it with @EmbeddedId to map the primary key attributes. By doing that, the attributes and mapping information of your embeddable become part of the entity.

@Entity
public class Address {

	@EmbeddedId
	private AddressKey id;

	private String city;

	private String street;

	private String country;

	@OneToOne(mappedBy = "address")
	private Person person;

	...
}

Sharing a composite primary key

In the next step, you can model a one-to-one association that shares the composite primary key. I did that for the following Person entity.

@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;

	...
}

As you can see in the code snippet, the Person entity uses the AddressKey embeddable to map its primary key.

The address attribute models the one-to-one association to the Address entity. I annotated it with @MapsId to tell Hibernate to use the primary key of the associated Address entity as the primary key of the Person entity.

You might also want to use a set of @JoinColumn annotations to map the foreign key attributes to the columns xId and yId. By default, Hibernate maps them to address_xId and address_yId. Since Hibernate 5.2, the @JoinColumn annotation is repeatable, and you no longer need to wrap it in a @JoinColumns annotation.

That’s all you need to do. Your Person and Address entities now share the same composite primary key.

Learn more:

If you liked this article, you might also be interested in:

Hibernate Tips Book

Get more recipes like this one in my new book Hibernate Tips: More than 70 solutions to common Hibernate problems.

It gives you more than 70 ready-to-use recipes for topics like basic and advanced mappings, logging, Java 8 support, caching, and statically and dynamically defined queries.

Get it now!