In this troubleshooting guide, we will explore how to properly map bidirectional relationships and common mapping pitfalls that cause exceptions such as DataIntegrityViolationException, a ConstrainViolationException and a PropertyValueException.

We will take an unusual approach in this guide by first building the faulty software, and then we will discuss how and why this should be fixed.

 

Mapping Bi-Directional Relationships (the wrong way)

As discussed, we will set up our database tables and our entity models in order to reproduce the exceptions, and then we will discuss how the issue can be resolved. (Spoiler alert: you need to make sure your data is consistent in memory)

 

Database tables

Let us start with our database tables. The first is a Customer table and the second is a Bank Branch. A customer can belong to a single bank branch, but a bank branch can have many customers (ie. one-to-many / many-to-one). Let us start with the bank branch table:

 

 

And the customer database table definition:

 

 

 

The customer table has the branch_id column, which is a foreign key to the id column of the bank branch table.

 

Entity Models

Now, let us define the problematic database models. Let us start with the bank branch entity:

 

 

Notice the @OneToMany relationship. The relationship defines three parameters. First is the “mappedBy” field which indicates the field in the Customer class where the BankBranch is mapped. The second setting is the FetchType. Here we indicate that we would like to lazy load the list of customers (recommended for performance reasons). The third parameter is the “cascade” parameter. A cascade of type CascadeType.ALL means that we would like to cascade all event that occur on the parent entity to the children.

 

Below is the Customer entity class:

 

 

 

Reproducing the error

Now let us move on to our tests. Our first test will attempt to save a new bank branch and a customer.

 

When we save everything, we assume that the customer will be added through cascading the save event from the branch and everything will be nice. But that is not true. We get the following nice exception (scroll down to see the full stack trace):

 

 

Hibernate complains that the branch field is null or transient. This is because we did not set the branch field in the customer entity. Since we defined the CascadeType.ALL property in the BankBranch entity, the cascade save event was propagated to the customer entity. But the customer instance had a null branch field. Therefore we get the error. Note that hibernate will not set both sides of the relationships automatically. You have to do it your self.

 

Let us move on to another test, which reproduces another data integrity violation exception. The test attempts to delete a bank branch, but first, it tries to move the customer of the branch to another branch before we make a deletion.

 

 

And here is the output that we get:

 

 

 

The error occurred because the deleted bank branch is still referenced by the customers, even though we did move them to another bank branch. So what is the solution to the above issues?

 

 

Bidirectional relationships require bidirectional setters

The problem with bidirectional JPA relationships is that the data on both sides of the relationships need to be consistent. In our example, this means that if you are moving the customers of the branch to another branch, then you need to also  change the bank branch of each customer to the new bank branch. This can be done via bidirectional setters. Let us revisit the BankBranch entity class and add some convenience methods:

 

 

 

Notice that the new add and remove customer methods. These methods will set the branch field in the incoming customer entity and therefore maintain data consistency. Let us revisit our tests:

 

 

 

Notice our tests now use the add customer method to both add the customer to the customers list of the bank branch and to assign the bank branch entity of the customers. Our tests now run smoothly as both sides of the relationship have consistent data.

 

Summary

In this example we explored the importance of maintaining data integrity in a JPA bidirectional relationship, and which errors we could encounter when our data is not consistent in memory. The next time you encounter an ConstraintViolationException, a DataIntegrityViolationException or a PropertyValueException, then make sure that your data is consistent and that you are setting both sides of your bidirectional relationship.

 

If you would like to have a solid knowledge foundation about JPA and Hibernate, then I strongly recommend reading the book “High-Performance Java Persistence” by Vlad Mihalcea. The book focuses on JPA and working with relational databases in order to cleanly develop applications that utilize a persistence backend. The author is one of the developers of the Hibernate framework which gives it a high authority on the topic.

You can check more details about the book, how other users rate the book and the price from our Amazon affiliate link (redirects to your local Amazon store): “High-Performance Java Persistence