Circular dependencies between entities

November 29, 2010

One question I am often asked is how to handle circular dependencies between Hibernate entities. Interestingly enough, the answer that people are usually looking for ends up being to a slightly different question; namely, how to handle circular dependencies between entities. Let’s have a look at an example.

Consider two entities, Customer and Account, with a one-to-many relationship between Customer and Account. Given a Customer, it seems reasonable to be able to obtain his Accounts by invoking a getAccounts() method. Furthermore, given an Account, it seems reasonable to be able to obtain its owner by invoking a getCustomer() method.

The mapping of these two entities to the database is quite trivial: each Account has a foreign key pointing to a Customer. And as long as the two sides of this relationship are properly described to Hibernate, one ends up being able to use the two aforementioned methods very easily.

Customer customer = session.get(Customer.class, id);
List<Account> accounts = customer.getAccounts();
Account account = session.get(Account.class, id);
Customer customer = account.getCustomer();

So far, so good. The confusion usually rises when creating these entities. Let’s say that you have a Customer and you want to create a new Account.

Account account = new Account();
account.setCustomer(customer);

Will this newly created Account be in the list returned by getAccounts()? Well, it depends. If you reload the customer as in the first example above, then the Account will definitely be in the list. No surprise there. But what if you don’t? What if you invoke getAccounts() right after invoking setCustomer(Customer)?

The right way to answer this question is to ignore Hibernate for a moment. Assuming these two entities are not mapped, would the Account be in the list? Of course, it depends on the implementation of setCustomer(Customer). Unless the method explicitly adds the Account to the list, the Account won’t end up in the list automagically.

Implementing this correctly is very simple. Pick one of the two entities as the owner of the relationship. Here, Customer seems to be an appropriate choice. Then, make sure the relationship is always defined by the owner. As long as you can guarantee that, you know that both sides are set up properly.

void addAccount(Account account) {
accounts.add(account);
account.setCustomer(this);
}

Make sure no one will be invoking setCustomer(Customer) directly by adding some documentation or some annotation tests can analyze.

/**
* Do not invoke this method. Use {@link Customer#addAccount(Account)} instead.
*/
void setCustomer(Customer customer) {
this.customer = customer;
}

Even though it might not always be necessary to configure both sides on the relationship correctly when dealing with mapped entities, it is good practice to do so. One day, some developer will expect this to be true and might be really surprised if it isn’t.