When working with JPA I prefer generating the primary key on the application and not in the database (check out this post from James Brundege). Additionally, I also prefer optimistic locking and in order to use it you need to specify a
@Version field in your Entity. But you need to be careful how to initialize these two fields. In this post I’ll talk about an error when you assign a default value for both fields in JPA which leads the
EntityManager to never call
persist but the
merge operation instead when creating a new entity.
You can find the example code on GitHub
Let’s start with a simple definition of two entities with a one-to-many relationship:
In a real project I’d prefer using an abstract mapped superclass to implement the basic functionality of creating the id and specifying the version field.
When trying to save a
repository.save(ExampleEntity(composite = listOf(Composite(), Composite()))) we get a
EntityNotFoundException. This is because in a single transaction we want to persist a
ExampleEntity and all the
Composite objects. But in our definition we didn’t instruct JPA to cascade the persist operation. So - we can achieve that by adding it to the one-to-many relationship:
@OneToMany(cascade = [CascadeType.PERSIST]). Save it. Run it. And … It still crashes!?
And here comes the behavior I wasn’t aware of. After looking into the SQL that got executed I noticed that Hibernate executed a
select on the composite table. So for some reasons Hibernate expected that the composite entity already exists instead of creating it.
The reason why Hibernate is doing that is because as soon as an Entity has a non-null primary key, and a non-null version JPA defines the Entity as existent and fetches it from the database.
So the lesson is easy. Don’t - ever - initialize, touch or modify the version field! A look into the JPA specification (Chapter 3.4.2) also clearly states:
An entity may access the state of its version field or property or export a method for use by the application to access the version, but must not modify the version value
So, this is the correct implementation:
You learn something new every day ;-)