views:

199

answers:

3

I'm having a problem where the entity manger is persisting an entity that I don't think has changed during the flush.

I know the following code is the problem because if I comment it out the entity doesn't persist. In this code all I'm doing is loading the entity and calling some getters.

Query qry = em.createNamedQuery("Clients.findByClientID");
qry.setParameter("clientID", clientID);

Clients client = (Clients) qry.getSingleResult();

results.setFname(client.getFirstName());
results.setLname(client.getLastName());
...
return results;

Later in a different method I do another namedQuery which causes the entity manger to flush. For some reason the client loaded above is persisted.

The reason this is a problem is because in the middle of all this, there is some old code that is making some straight JDBC changes to the client. When the entity manger persists the changes made by the straight JDBC are lost.

The theory we have at moment is that the entity manger is comparing the entity to the underlying record, sees that it's different, then persists it.

Can someone explain or confirm the behavior we're seeing?

A: 

I don't know the innards of hibernate, but my guess is that you are correct. But I don't think hibernate compares with the db, but compares with the local session cache.

You can avoid the problem by setting the object to readOnly - hibernate then does not check for changes, e.g.

   session.setReadOnly(client, true);

Alternatively, you can also evict the object, using Session.evict(). Next time the object is required it will be read in from the db, including any changes you made using your custom JDBC.

mdma
If it's comparing with the local session cache then how is the jdbc call interacting with it. The jdbc should be going straight to the db and skipping the session (or so I thought).I am injecting the data source, would that make a difference?
Preston
A: 

You're right that the entity manager compares the objects (as long as they're not read-only in the session, evicted, session.clear() has been called, etc) and if they don't match then it will flush them out.

Tthis specific issue has bitten us before, and caused exactly the same needless-flush problems -- ours was also a performance problem, in a tight loop we needed to flush() the session and every time we did hundreds of unchanged objects got dumped back to the DB.

In our case, it was that the entities in question did not implement equals() and hashCode() correctly -- Hibernate obviously relies on those to check the object's changed, and if they're incorrect in some way then it has no choice but to flush them back.

From memory (it was a while ago) it was actually a subsidiary entity which didn't implement hashCode() correctly -- imagine a Student-Teacher relationship, where Teacher doesn't correctly implement those methods. When dirty-checking a Student, Hibernate checks in its entity cache for the Teacher, thinks it's not there (due to incorrect implementations) and so thinks Student has changed, reflushing it back to write out the new Teacher ID.

So check hashCode() and equals() for the entitiy in question, all its related entities, and any other components/user types which get written to the DB.

Cowan
How were you able to determine that it was an improper implementation of hashcode or equals?
Preston
A LOT of very painful debugging, unfortunately. I can't recall if there's useful logging in Hibernate we were able to turn on; I think in the end, we worked it out only by a few strategic breakpoints in the Hibernate code. Start with DefaultFlushEntityEventListener.dirtyCheck() -- that finds all the dirty properties and so should make it obvious which properties are causing the flush back.We actually ended up writing a JUnit test case which crawled out classpath, found all the entities, and checked that they behaved correctly in terms of hashCode() and equals(), which found a few others too.
Cowan
We have a custom user type converts dates in the db from 0000-00-00 to a null. We suspect that maybe that's setting the dirty flag. Using the dirtyCheck as you suggest we should be able to verify if that's the source. If it is... is there anyway to say don't let this change set the dirty flag?
Preston
I don't think you can exclude it. I suspect what's happening is your bean's getter isn't returning the same value Hibernate is expecting it to -- that is, Hibernate is seeing 'null' from your bean but '0000-00-00' from the dehydrated database state. Not 100% sure how that comparison is done, but that code in DefaultFlushEntityEventListener should help you work it out if you step through it.
Cowan
A: 

If your getter is performing any logic so that it is returning something other than what hibernate sent to the setter, then it will be marked dirty. For instance, if your getter logic is returning 0 when hibernate gave you a null from the database then it'll be marked dirty because it looks like it changed. I'm not aware of a way to tell hibernate not to mark that change as making the entity dirty.

digitaljoel