views:

1117

answers:

6

We are using Hibernate Spring MVC with OpenSessionInView filter. Here is a problem we are running into (pseudo code)

transaction 1 
load object foo
transaction 1 end

update foo's properties (not calling session.save or session.update but only foo's setters)

validate foo (using hibernate validator)
if validation fails ?
 go back to edit screen
 transaction 2 (read only)
 load form backing objects from db
 transaction 2 end
 go to view
else 
transaction 3 
session.update(foo)
transaction 3 end

the problem we have is if the validation fails foo is marked "dirty" in the hibernate session (since we use OpenSessionInView we only have one session throughout the http request), when we load the form backing objects (like a list of some entities using an HQL query), hibernate before performing the query checks if there are dirty objects in the session, it sees that foo is and flushes it, when transaction 2 is committed the updates are written to the database. The problem is that even though it is a read only transaction and even though foo wasn't updated in transaction 2 hibernate doesn't have knowledge of which object was updated in which transaction and doesn't flush only objects from that transaction. Any suggestions? did somebody ran into similar problem before

Update: this post sheds some more light on the problem: http://brian.pontarelli.com/2007/04/03/hibernate-pitfalls-part-2/

A: 

You can run a get on foo to put it into the hibernate session, and then replace it with the object you created elsewhere. But for this to work, you have to know all the ids for your objects so that the ids will look correct to Hibernate.

Elie
the foo is in the session, what do you mean by replace it?
talg
call get(foo.id) and then merge(foo)
Elie
+1  A: 

There are a couple of options here. First is that you don't actually need transaction 2 since the session is open you could just load the backing objects from the db, thus avoiding the dirty check on the session. The other option is to evict foo from the session after it is retrieved and later use session.merge() to reattach it when you what your changes to be stored.

With hibernate it is important to understand what exactly is going on under the covers. At every commit boundary it will attempt to flush all changes to objects in the current session regardless of whether or not the changes where made in the current transaction or any transaction at all for that matter. This is way you don't actually need to call session.update() for any object that is already in the session.

Hope this helps

Gareth Davis
A: 

What about using Session.clear() and/or Session.evict()?

Maxim
A: 

What about setting singleSession=false on the filter? That might put your operations into separate sessions so you don't have to deal with the 1st level cache issues. Otherwise you will probably want to detach/attach your objects manually as the user above suggests. You could also change the FlushMode on your Session if you don't want things being flushed automatically (FlushMode.MANUAL).

cliff.meyers
+1  A: 

There is a design issue here. Do you think an ORM is a transparent abstraction of your datastore, or do you think it's a set of data manipulation libraries? I would say that Hibernate is the former. Its whole reason for existing is to remove the distinction between your in-memory object state and your database state. It does provide low-level mechanisms to allow you to pry the two apart and deal with them separately, but by doing so you're removing a lot of Hibernate's value.

So very simply - Hibernate = your database. If you don't want something persisted, don't change your persistent objects.

Validate your data before you update your domain objects. By all means validate domain objects as well, but that's a last line of defense. If you do get a validation error on a persistent object, don't swallow the exception. Unless you prevent it, Hibernate will do the right thing, which is to close the session there and then.

A: 

Implement a service layer, take a look at spring's @Transactional annotation, and mark your methods as @Transactional(readOnly=true) where applicable.

Your flush mode is probably set to auto, which means you don't really have control of when a DB commit happens.

You could also set your flush mode to manual, and your services/repos will only try to synchronize the db with your app when you tell them to.

zmf