20100515

Watch out for manual flushing in JBoss Seam

I've done quite a lot of development in JBoss Seam for the last six months and overall I'm quite enthusiastic. Also I'm looking forward to using some of the features of Seam in their new Java EE 6 incarnations (ie. in short: @Inject in stead of @In, @Produces in stead of @Factory, @Unwrap and @Out, and @ConversationScoped in stead of @Scope(CONVERSATION) ;-)).

One key feature of Seam is it's persistence strategy and at first glance it's quite a cool thing. The idea is to use an extended persistence context which means that your entities are kept managed across transactions. The extended persistence context is very important as Seam wraps each request in transactions and all changes to entities caused by actions in the request will then be automatically propagated to the database. The extended persistence context saves you from having to call merge(...) in order to reattach your entities all the time. Calling merge(...) is a heavy operation so this is good.

This pattern makes a lot of sense just until the point where you want to make it possible to edit an entity throughout a few requests but then forget about your changes (because the user changes his/her mind). To make this use case possible the Seam guys are advocating to use "MANUAL flushing" which means that Hibernate won't flush updates to the database unless you programmatically tell it to. Seems smart - here's the idea: Hibernate will keep track of all changes made in transactions (requests) but won't flush them. At a certain time the user will typically hit a "Save changes" button and then everything will be flushed.

Apart from the fact that MANUAL flushing is a Hibernate-specific feature not available with other JPA persistence providers, this pattern has three very serious flaws:

  1. Any query fired will cause an implicid flush - even if the flush-mode is MANUAL. This means that if your conversation involves a query, your changes will be flushed even though you haven't invoked the flush-method yourself. Again - this almost certainly rules out the possibility to use MANUAL flushing in just about any conversation I can imagine (especially if you want to enable navigation by nested conversations). Queries are a good example of something that used to be a 'side-effect-free function' but is now something that can impose a lot of unintended changes in state.
    NOTE: I stand (a bit) corrected here - I was adviced that this behaviour can be avoided by setting the flushmode of the query to COMMIT and it seems to work.
  2. While we're at the topic - if you wan't to enable nested conversations then you will have to do a lot of plumbing code to make sure that the nested parts doesn't invoke the flush-method and then end up flushing on behalf of the parent-conversation also. It IS possible to code your way around this flaw but it's a very serious prohibitant to compose your application of reusable nested conversations.
  3. The Seam guys seem to have failed to realize that transactions are used for other purposes than saving entities. For example, if you're using JMS, you would send messages at commit-time which means that developers of the JMS-dispatch code will assume that if a commit takes place, data has been persisted. If the message contains for example id's of updated entities the messagehandler will access these entities before any updates has taken place because the updates haven't been flushed!
I think that these flaws make it utterly hard to develop applications using MANUAL flushing because of the intrinsics it imposes on the flow in your application. In this light, I'm quite pleased that they didn't include manual flushing in Java EE 6 (or rather, JPA 2).

5 comments:

Dean Hiller said...

odd, I have never had this problem. Doing queries never caused a flush for me AND typically we bootstrap into the object model at the begin of the wizard and then go from object to object(and looking at the log4jdbc logs, I can see that was resulting in more queries but no updates at all). we have found the manual flushing to be a really nice feature....maybe I am missing something but we have 5 to 10 page wizards and I had to manually add flush(because it was manual mode) in certain cases...it worked great for us.

Dean Hiller said...

hmmm, maybe you are on a different version of hibernate. We are on jboss 5.1.0 AS and it's version of hibernate. I hope hibernate hasn't introduced some bugs later.

Kasper Sørensen said...

I'm using JBoss 5.1 and the built-in Hibernate (3.3 afaik) as well.

I posted these issues at the Seam forum as well (http://www.seamframework.org/Community/15GotchasInSeam) and someone there said that the query-related issue could be avoided by setting flush mode to COMMIT on the Query. I haven't tried it though.

Be aware though that what I mean is not when you "go from object to object" - that doesn't cause a flush. What I'm talking about is executing a query using the EntityManager. For my use-case we have an ajax-enabled button which will update a field whose value is based on a database-query (SELECT MAX something)...

Kasper Sørensen said...

I followed the advice given on the seam forums and it seems that issue no. 1 can be avoided by setting the query's flushmode to COMMIT. I've updated my blog-post accordingly.

mpd said...

I agree about nested conversations.. I think when openning a nested conversation there should be an option to use a brand new entity manager.

This would avoid having to worry about that objects that are being managed in the current persistent session. Frequently in my use cases I'm needing to detach or clear objects from the session. Really uncool.