DataIntegrityViolationException & ConstraintViolationException- Managing JPA bidirectional relationships properly

Troubleshooting DataIntegrityViolationException,  PropertyValueException & ConstraintViolationException – Managing JPA bidirectional relationships properly

 

In this troubleshooting guide, we will explore one of the most common causes of a DataIntegrityViolationException, a ConstrainViolationException and a PropertyValueException. We will set up our database tables and our entity models in order to reproduce the exception, 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 thats 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.