Should Hibernate Session#merge do an insert when receiving an entity with an ID?

concurrency, hibernate, java, jpa

Solution

I've been looking at JSR-220, from which `Session#merge` claims to get its semantics. The JSR is sadly ambiguous, I have found.

It does say:

Optimistic locking is a technique that is used to insure that updates to the database data corresponding to the state of an entity are made only when no intervening transaction has updated that data since the entity state was read.

If you take "updates" to include general mutation of the database data, including deletes, and not just a SQL `UPDATE`, which I do, I think you can make an argument that the observed behaviour is not compliant with optimistic locking.

Many people agree, given the comments on my question and the subsequent discovery of this bug.

From a purely practical point of view, the behaviour, compliant or not, could lead to quite a few bugs, because it is contrary to many developers' expectations. There does not seem to be an easy fix for it. In fact, Spring Data JPA seems to ignore this issue completely by blindly using `EM#merge`. Maybe other JPA providers handle this differently, but with Hibernate this could cause issues.

I'm actually working around this by using `Session#update` currently. It's really ugly, and requires code to handle the case when you try to `update` an entity that is detached, and there's a managed copy of it already. But, it won't lead to spurious inserts either.

Problem

This seems like it would come up often, but I've Googled to no avail. Suppose you have a Hibernate entity `User`. You have one `User` in your DB with id 1. You have two threads running, A and B. They do the following: - A gets user 1 and `close`s its `Session` - B gets user 1 and `delete`s it - A changes a field on user 1 - A gets a new `Session` and `merge`s user 1 All my testing indicates that the `merge` attempts to find user 1 in the DB (it can't, obviously), so it inserts a new user with id 2. My expectation, on the other hand, would be that Hibernate would see that the user being merged was not new (because it has an ID). It would try to find the user in the DB, which would fail, so it would not attempt an insert or an update. Ideally it would throw some kind of concurrency exception. Note that I am using optimistic locking through `@Version`, and that does not help matters. So, questions: - Is my observed Hibernate behaviour the intended behaviour? - If so, is it the same behaviour when calling `merge` on a JPA `EntityManager` instead of a Hibernate `Session`? - If the answer to 2. is yes, why is nobody complaining about it?

Original source