
Transactions and Concurrency Control
Are you struggling with performance issues in your Spring, Jakarta EE, or Java EE application?
What if there were a tool that could automatically detect what caused performance issues in your JPA and Hibernate data access layer?
Wouldn’t it be awesome to have such a tool to watch your application and prevent performance issues during development, long before they affect production systems?
Well, Hypersistence Optimizer is that tool! And it works with Spring Boot, Spring Framework, Jakarta EE, Java EE, Quarkus, Micronaut, or Play Framework.
So, rather than fixing performance issues in your production system on a Saturday night, you are better off using Hypersistence Optimizer to help you prevent those issues so that you can spend your time on the things that you love!
While answering questions on the Hibernate forum, I stumbled on the following question about using the @ManyToOne annotation when the Foreign Key column on the client side references a non-Primary Key column on the parent side.
In this article, you are going to see how to use the @JoinColumn annotation in order to accommodate non-Primary Key many-to-one associations.
Assuming we have the following tables in our database:

The isbn column on the publication and book tables are linked via a Foreign Key constraint which is the base of our @ManyToOne assocation:

The Book represents the parent side of the association, and it’s mapped as follows:
@Entity(name = "Book")
@Table(name = "book")
public class Book implements Serializable {
@Id
@GeneratedValue
private Long id;
private String title;
private String author;
@NaturalId
private String isbn;
}
The isbn column is mapped as a @NaturalId since it can be used as a business key as well. For more details about the @NaturalId annotation, check out this article.
Notice that the
Bookentity implementsSerializable. This is on purpose as otherwise you won’t be able to map this entity as an association via a non-Primary Key column.
The Publication represents the child of the association, so it’s going to be mapped like this:
@Entity(name = "Publication")
@Table(name = "publication")
public class Publication {
@Id
@GeneratedValue
private Long id;
private String publisher;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
@Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
}
By default, the @ManyToOne association assumes that the parent-side entity identifier is to be used to join with the client-side entity Foreign Key column.
However, when using a non-Primary Key association, the referencedColumnName should be used to instruct Hibernate which column should be used on the parent side to establish the many-to-one database relationship.
Assuming we have the following entities in our database:
Book book = new Book()
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea")
.setIsbn("978-9730228236");
entityManager.persist(book);
Publication amazonUs = new Publication()
.setPublisher("amazon.com")
.setBook(book)
.setPriceCents(4599)
.setCurrency("$");
entityManager.persist(amazonUs);
Publication amazonUk = new Publication()
.setPublisher("amazon.co.uk")
.setBook(book)
.setPriceCents(3545)
.setCurrency("&");
entityManager.persist(amazonUk);
Upon fetching the Publication along with its associated Book, we can see that the @ManyToOne association works as expected:
Publication publication = entityManager.createQuery("""
select p
from Publication p
join fetch p.book b
where
b.isbn = :isbn and
p.currency = :currency
""", Publication.class)
.setParameter("isbn", "978-9730228236")
.setParameter("currency", "&")
.getSingleResult();
assertEquals(
"amazon.co.uk",
publication.getPublisher()
);
assertEquals(
"High-Performance Java Persistence",
publication.getBook().getTitle()
);
When executing the JPQL query above, Hibernate generates the following SQL statement:
SELECT
p.id AS id1_1_0_, b.id AS id1_0_1_,
p.isbn AS isbn5_1_0_, p.currency AS currency2_1_0_,
p.price_in_cents AS price_in3_1_0_,
p.publisher AS publishe4_1_0_,
b.author AS author2_0_1_, b.isbn AS isbn3_0_1_,
b.title AS title4_0_1_
FROM
publication p
INNER JOIN
book b ON p.isbn = b.isbn
WHERE
b.isbn = '978-9730228236' AND
p.currency = '&'
As you can see, the referencedColumnName allows you to customize the JOIN ON clause so that the isbn column is used instead of the default entity identifier.
If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.
If you want to represent a non-Primary Key @ManyToOne association, you should use the referencedColumnName attribute of the @JoinColumn annotation.
For more complex situations, like when you need to use a custom SQL function in the JOIN ON clause, you can use the Hibernate specific @JoinFormula annotation.

Hi, Vlad.
I’m working with a legacy system that, for some reason, uses composite IDs for almost all its entities. I’m working with three entities, Payment, Account and User. It’s a bit of a mess.
Payment has a composite ID (int p_x, int p_y, int p_z), with the first two components of its ID (p_x, p_y) also representing a foreign key to the Account (obviously then with a composite ID (a_x, a_y).
Similarly, a User has a (u_x, u_y) composite primary key. Account models this as two fields:
@Column(“cust_x”)
int customerX;
@Column(“cust_y”)
int customerY;
and the underlying DB table has an equivalent foreign key constraint. Payment has no “direct” JPA associations to either User or Account, but when a Payment is created it always takes an Account ID, copies it into its p_x and p_y, and tacks on the p_z value. The underlying table has a foreign key on (p_x, p_y) -> Account(a_x, a_y).
I find myself in a situation where I want to query for a collection of Payments and their associated Users, but without also join fetching the Account.
I thought to do this by adding the User directly to Payment, using the various fields on Account as a @JoinTable:
@ManyToOne(fetch = FetchType.LAZY) @JoinTable(name = "accounts", joinColumns = { @JoinColumn(name = "a_x", referencedColumnName = "p_x", insertable = false, updatable = false), @JoinColumn(name = "a_y", referencedColumnName = "p_y", insertable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = "cust_x",table = "users", referencedColumnName = "u_x", insertable = false, updatable = false), @JoinColumn(name = "cust_y", table = "users", referencedColumnName = "u_y", insertable = false, updatable = false), } ) private User customer;But this is causing JPA to throw “secondaryTable JoinColumn cannot reference a non primary key”.
p_x and p_y are both part of the primary key of Payment, similarly u_x and u_y User. That leaves only (a_x, a_y, cust_x, cust_y) on Account as suspects. (a_x, a_y) form the primary key for Account: (cust_x, cust_y) obviously do not, but as I said are part of a foreign key. But these must be the reason for the failure to model the relationship.
I was hoping to find an answer in this article, but I don’t follow why my attempted solution doesn’t ‘match’ what’s happening here.
Is what I’m attempting to do simply not possible? If I want the Payment and the User that paid it, am I simply forced to return it all as (select p from Payment join fetch p.account a join fetch a.user u)?
Thanks in advance, big admirer.
The
@ManyToOnecannot be used with@JoinTablebecause@JoinTableis for@ManyToMany. But, in your case, you don’t really need a different mapping, you just need a different query.You said that you want the Payment and the User that paid it, and not the account.
And the problem is that you used JOIN FETCH instead of JOIN. So, instead of:
You need:
Here’s an example that uses this approach.
Thanks for getting back to me, Vlad.
If I wrap the result of (p, u) in a record as shown in that example, are p and u still managed?
And in this instance, I’m not fetching a single (p, u), I need to batch-process a set of (p, u)s. So if I do
and return a
List<PaymentWithUser>, they’ll all be managed by Hibernate, right?That’s a very good question. Normally, we fetch DTO projections using the constructor expression, but in this case the entities are fetched and bound to the Record. So, both entities are managed even if fetched via the Record projection. Here’s a test case that validates this property.
You should be able to select a
List<PaymentWithUser>and the entities should be managed.Inspired by this thread, I wrote this article about the proposed solution.