This is a similar problem to Forcing a transaction to rollback on validation error The scenario is this: A user edits a page, the transaction is set to MANUAL so only if we call flush it would be committed to the database. Now the user wants to cancel the changes. Easy as you haven't flushed it yet.
Now consider this scenario: User edits page with lots of ajax on there. Some of these ajax callbacks require database queries (e.g. using a richFaces suggestion box etc). Some validation is done as well that requires database lookups. The problem is Hibernate will automatically issue a flush when you do a query. So the user doesn't press the save button (which would flush the transaction) he presses the cancel button. What do you do now?
If you don't do anything the changes will be written to the database - not what the user expects.
You can throw an exception that is annotated with
@ApplicationException(rollback=true)
That would rollback the transaction. You can then redirect to another page. However here I've come across another problem, on some pages you redirect to you get a lazy initialisation exception. I've specified
<exception class="com.mycomp.BookingCancelException">
<end-conversation before-redirect="true"/>
<redirect view-id="/secure/Bookings.xhtml">
<message severity="INFO">#{messages['cancel.rollback']}</message>
</redirect>
</exception>
in pages.xml, so the conversation should end before we are doing the redirect. A new conversation should start (with a new transaction) but that doesn't seem to happen in all cases? Why?
I've read somewhere else that you can simply use
Transaction.instance().rollback();
This would be preferable as you don't have to go via exceptions (the redirect always takes long when Seam handles exceptions) but the problem is that the Transaction isn't actually rolled back. I couldn't figure out why. If I check the status of the transaction it says that it is not in rollback state.
How would you best handle Cancel requests. The pure MANUAL flush doesn't work in this case. You could work with detached entities but the page contains several linked entities so this is getting messy.
Update: I've now discovered that throwing the ApplicationException doesn't rollback the transaction in all cases. So rather confused now.
Update 2: Of course rolling back transactions will not work when you have an page where you use ajax to update values. Each transaction only covers one request. So if you do e.g. 5 edits with ajax request, rolling back a transaction will only roll back the changes from the last ajax request and not from the earlier 4 ones.
So solution is really to use the flush mode MANUAL.
There are a few things that will cause a flush even if you specify MANUAL.
- a query in an ajax request can trigger a flush - Use setFlushMode(FlushMode.COMMIT) on the query to avoid this.
- Persisting an entity can trigger a flush depending on the ID generation used (e.g. if you use the strategy IDENTITY). You can work around this by using Cascades. If you need to create entities during the edit which don't have any real relationship with the main entity your are editing just add them to a list and persist all entities in that list when you do a save.
- When you start a nested conversation or another bean joins the conversation the Flush Mode on that session is set back to AUTO when you don't specify @Begin(join=true,flushMode=FlushModeType.MANUAL)
You might want to specify MANUAL as the default mode in components.xml
<core:manager concurrent-request-timeout="10000"
conversation-id-parameter="cid" conversation-timeout="600000" default-flush-mode="MANUAL"/>