I'd like to recover after a failed transaction.
Now, of course after any rollback, all entities become detached and the entity manager is closed. However, the UI still holds the detached entities. Obviously we can't just throw away the user's changes, so we'd like to let them retry (fix the highlighted validation error, then click the button again).
Following the Java Persistence WikiBook,
One method of error handling is to call merge for each managed object after the commit fails into a new EntityManager, then try to commit the new EntityManager. One issue may be that any ids that were assigned, or optimistic lock versions that were assigned or incremented may need to be reset. Also, if the original EntityManager was EXTENDED, any objects that were in use would still become detached, and need to be reset.
This option seems straightforward at first, until we inevitably encounter exactly those anticipated issues. Some services might trigger a flush for various reasons, which increments @Version
fields in both the DB (which is rolled back) and Java entities (which are not). The next "save" calls merge
, which throws an unexpected OptimisticLockException
.
Is there a reliable way to "rollback" version fields in the Java entity beans?
OK, that seams hard. We have cascaded entities with their own @Versions, so doing it manually seems fragile. (How can we reliably know the original (persisted) versions anyway? Can't query, because some other user might successfully update the entity in the mean time; querying the current version could break oplocking!)
Another more involved method to error handling is to always work with a non-transactional EntityManager. When it's time to commit, a new EntityManager is created, the non-transactional objects are merged into it, and the new EntityManager is committed. If the commit fails, only the state of the new EntityManager may be inconsistent, the original EntityManager will be unaffected. This can allow the problem to be corrected, and the EntityManager re-merged into another new EntityManager. If the commit is successful any commit changes can be merged back into the original EntityManager, which can then continue to be used as normal. This solution requires a fair bit of overhead, so should only be used if error handling is really required, and the JPA provider provides no alternatives.
This seems logical. Does anyone have any experience implementing this kind of recovery with two EntityManagers (especially with Spring)? Any pitfalls I should be aware of before attempting it? It seems like every service and DAO would now have to become aware of the two entity managers (with Spring, today they are almost persistence-layer agnostic). DAO 'find' operations use one EM; 'update' uses another. Or have separate 'read' and 'write' DAOs. Ouch.
Other options I've considered include:
merge
to the end of any composed operation. Entity is only attached after all validation and state updates have succeeded. Seems strange that the "save" service would no longer merge
(only validate). In effect, the UI would take responsibility for calling the DAO! Is this as unusual as it sounds?Advice? Thanks :-)
Update My architecture includes:
@Version
fields for oplockingem.merge
(JPA over Hibernate)@Transactional
advice around UI controller: begin)@Transactional
: commit)You ask two different questions and then a general call for advice. I'm going with the advice (would have put this in a comment but SO won't let me comment yet...)
Using DTO's (or any UI-only data structure) in the UI is not ugly - it's architecturally sound. I agree with JB Nizet that the non-transactional EM approach essentially turns your entities into DTO's. If you must use entities in your UI (and I realize Spring encourages this), this is the way to go.
In my experience the main reason people want to use entities as UI scratch storage is to get access to the complex validation logic therein. You might also want to consider refactoring towards making that logic accessible without invoking your full domain model.
Can only support JB Nizet's comment: merge only copies the state of the detached entities.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With