This is an extension to the previous post about unidirectional many to one mapping. Please ensure to get familiar with it before you move to bi-directionality. This post will give you an understanding of what benefits can a bi-directional many to one JPA entity mapping give you and why it’s sometimes a bad idea. We’ll learn how to set it up with JPA annotations using a simple example and we’ll look into what SQL statements Hibernate fires when this type of mapping is applied.
As always you can find the source code for code examples on allAroundJava GitHub.

Why we use a bi-diretional many to one mapping.

For the purpose of this article we’ll reuse entity classes Account and CreditCard from previous article.

Many to One entity relation

A bi-directional many to one mapping realizes an ORM promise letting you avoid writing SQL queries and use Java code to fetch entity collection property. In our example, thanks to the bi-directional mapping, we’re able to get all account’s credit cards, just by calling .getCreditCards() on Account instance.
Using this approach:

  • Hibernate will automatically fire a SELECT * FROM CREDITCARD WHERE ACCOUNT_ID=? whenever you access property behind account.getCreditCards().
  • Account class can cascade its state changes to accompanying CreditCards. When proper CascadeType is set, you can create CreditCard rows by adding elements to creditCards collection in Account. This would not require calling persist() on each and every CreditCard. Hibernate will do it for you recognizing OneToMany relation’s PERSIST cascade type.

Why can one to many mapping be problematic

Please bear in mind that you can easily model a bi-directional many to one entity mapping with a unidirectional many to one and a named query for the “One” side. Yes it requires writing some SQL-like code, but you’ll surely fetch entity relations in a more conscious way with this approach
For our example, it’s worth asking how often will you need a full set of CreditCards when you retrieve an Account. If you’re more likely to order a subset of Credit Cards, get them in a particular order or use an aggregate, you’ll need to use a query anyway. You’ll probably be better off leaving the relation one-sided then.
There are a few reasons why I’m so strongly advising against making the relation bi-directional:

  • Iterating over Accounts and accessing lazily loaded CreditCards (in each iteration) may lead to n + 1 selects problem.
  • Fetching Account with eagerly loaded CreditCards, who also have eagerly loaded relations may lead to a cartesian product problem.
  • When Persistence Context is closed, accessing uninitialized, lazily loaded CreditCard collection will result in an exception.
  • Cascading state changes in heavily nested service methods may lead to unintended updates. When there’s a large hierarchy of service methods being called in several transactions, sometimes it’s hard to determine when exactly is the persistence context flushed. Updates to cascaded entity collection property may not obviously be persisted in the database.

Bi-directional many to one entity relation definitely adds complexity. It’s actually a convenience set up which does not change anything in the database. Foreign key has already been configured in CreditCard entity with a @ManyToOne annotation.

It’s worth considering if you really need a bi-directional many to one mapping. If it turns out you really do, here’s how to set it up.

Configuring bi-directional many to one mapping

public class CreditCard {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String cardNumber;
    private LocalDate expirationDate;
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private Account account;

    //static factory

public final class Account {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String accountNumber;
    private String accountOwnerName;

    @OneToMany(mappedBy = "account")
    private Set creditCards;

    //static factory

SQL queries generated by Hibernate with bi-directional many to one

From previous article, we already know how the “Many” side behaves. Let us look at the “One” side of the relation. Here’s a simple test which persists an Account with two CreditCards and then fetches the Account. In the logs, you can notice that CreditCards are fetched with an additional SELECT query. This happens only when the creditCards property is accessed in Account. More precisely when a function is called on the creditCards collection. Calling .getCreditCards() does not trigger the select query yet.
This is because one to many side is by default lazily loaded by Hibernate. If we defined the @OneToMany relation to be eagerly loaded, only a single SQL query would be send to the database. That query would fetch all the credit cards with a JOIN.

public void whenAccessingCreditCards_thenNonemptyCollection() {
    Account account = Account.newInstance("345-321-346", "Henry Ford");
    CreditCard cardOne = CreditCard.newInstance("12345",, account);
    CreditCard cardTwo = CreditCard.newInstance("6789",, account);


    Account fetchedAccount = accountDao.getById(account.getId()).get();
    Set creditCards = fetchedAccount.getCreditCards();
    LOGGER.debug("Credit cards will be fetched now");
    Assert.assertEquals(2, creditCards.size());

Here’s the log output of above test

19:07:14.851 - Persisting class com.allaroundjava.model.Account
19:07:15.022 - insert into Account (id, accountNumber, accountOwnerName) values (null, ?, ?)
19:07:15.046 - Persisting class com.allaroundjava.model.CreditCard
19:07:15.047 - insert into CreditCard (id, account_id, cardNumber, expirationDate) values (null, ?, ?, ?)
19:07:15.060 - Persisting class com.allaroundjava.model.CreditCard
19:07:15.061 - insert into CreditCard (id, account_id, cardNumber, expirationDate) values (null, ?, ?, ?)
19:07:15.063 - Fetching class com.allaroundjava.dao.AccountDao with id 1 from database
19:07:15.072 - select as id1_0_0_, account0_.accountNumber as accountN2_0_0_, account0_.accountOwnerName as accountO3_0_0_ from Account account0_ where
19:07:15.097 - Credit cards will be fetched now
19:07:15.100 - select creditcard0_.account_id as account_4_1_0_, as id1_1_0_, as id1_1_1_, creditcard0_.account_id as account_4_1_1_, creditcard0_.cardNumber as cardNumb2_1_1_, creditcard0_.expirationDate as expirati3_1_1_ from CreditCard creditcard0_ where creditcard0_.account_id=?

Take a look here for some more knowledge

Leave a Comment