Hibernate Tip: How to customize the association mappings using a composite 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:

“One of my entities uses an @IdentityClass to map a composite primary key, and I need to map a one-to-many association to it. How can I change the names of the foreign key columns?”

Solution:

If you don’t provide any additional information about your association mapping, Hibernate uses default names for the foreign key columns. They follow the pattern <association attribute name>_<primary key attribute name>. That means that books_id is the default name for an association that’s mapped by the books attribute, and that references an entity with a primary key column called id.

This approach is independent of the number of columns that form the primary key of the referenced entity. If it’s a composite primary key, Hibernate generates a default name for each of the identifying entity attributes.

Customize the default column names

You can adapt this mapping using one or more @JoinColumn annotations. Since Hibernate 5 and JPA 2.2, the @JoinColumn annotation is repeatable, and you can apply multiple of them to your association mapping attribute. If you’re using an older Hibernate version, you need a @JoinColumn annotation for each column and wrap them in a @JoinColumns annotation.

Let’s take a look at a simple example.

A Product gets identified by the combination of the manufacturerId and a manufacturer-specific productId. As requested in the question, I model the primary key using an @IdClass. If you want to learn more about this primary key mapping or the mapping of composite primary keys in general, please take a look at my Advanced Hibernate Online Training.

@Entity
@IdClass(value = Product.ProductId.class)
public class Product {

	@Id
	private Long productId;

	@Id
	private String manufacturerId;

	@Version
	private int version;

	private String title;

	// getter and setter methods omitted

	public static class ProductId implements Serializable {
		
		private Long productId;
	
		private String manufacturerId;

		public ProductId() {
			super();
		}

		public ProductId(Long productId, String manufacturerId) {
			this.productId = productId;
			this.manufacturerId = manufacturerId;
		}

		// getter and setter methods omitted

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((manufacturerId == null) ? 0 : manufacturerId.hashCode());
			result = prime * result + ((productId == null) ? 0 : productId.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;
			ProductId other = (ProductId) obj;
			if (manufacturerId == null) {
				if (other.manufacturerId != null)
					return false;
			} else if (!manufacturerId.equals(other.manufacturerId))
				return false;
			if (productId == null) {
				if (other.productId != null)
					return false;
			} else if (!productId.equals(other.productId))
				return false;
			return true;
		}
	}
}

The vital part of this Hibernate Tip is the mapping of the Review entity.

@Entity
public class Review {

	@Id
	@GeneratedValue
	private Long id;

	@ManyToOne
	@JoinColumn(name = "p_id", referencedColumnName = "productid")
	@JoinColumn(name = "m_id", referencedColumnName = "manufacturerid")
	private Product product;

	private String comment;

	// getter and setter methods omitted
}

The product attribute maps the association to the Product entity. Hibernate’s default mapping would expect that the columns product_productid and product_manufacturerid on the review table store the foreign key to a record in the product table.

As described earlier, you can change this mapping by annotating the product attribute with one or more @JoinColumn annotations. I did that in the code snippet to tell Hibernate to use the columns p_id and m_id as the foreign key references to the productid and manufacturerid columns on the product table.

When I use this entity to persist a new record, you can see that Hibernate uses the column names p_id and m_id instead of the default names.

17:56:46,584 DEBUG [org.hibernate.SQL] - 
    insert 
    into
        Review
        (comment, m_id, p_id, id) 
    values
        (?, ?, ?, ?)

Learn more:

If you want to learn more about primary key and association mappings, please read the following articles:

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!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.