JPA transaction re-retry retry: merging object with auto-increment @Version

I want to recover from a failed transaction.

Now, of course, after any rollback, all objects become disconnected, and the entity manager is closed. However, the user interface still contains separate objects. Obviously, we cannot just throw away user changes, so we would like to let them try again (correct the highlighted validation error, and then click the button again).

Following the Java Persistence WikiBook ,

One method of error handling is to call a merge for each managed entity after the commit fails in the new EntityManager, and then try to commit the new EntityManager. One problem may be that any identifiers that have been assigned, or optimistic versions of locks that have been assigned or increased, may need to be reset. In addition, if the original EntityManager was EXTENDED, all objects that were in use would still be detached and should be reset.

This option seems straightforward before we inevitably encounter these expected problems. Some services can run flash for various reasons, which increases the increment of @Version fields both in the database (which is rollback) and in Java objects (which is not the case). The following save calls are merge that raise an unexpected OptimisticLockException .

Is there a reliable way to “rollback” the version fields in the Java beans entity?

Well, that’s hard. We have cascading objects with their own @Versions, so doing it manually seems fragile. (How can we reliably know the original (saved) versions in any case? It is impossible to execute the request, because another user can successfully update the entity in average time, and the request of the current version can break oplocking!)

Another more attractive error handling method is to always work with a non-transactional EntityManager. When this time is for commit, a new EntityManager is created, non-transaction objects are merged into it, and the new EntityManager is committed. If the commit fails, only the state of the new EntityManager can be inconsistent, the original EntityManager will not be affected. This can fix the problem, and the EntityManager is re-combined with 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 usual. This solution is costly and therefore should only be used if error handling is really required and the JPA does not provide alternatives.

It seems logical. Does anyone have experience implementing this kind of recovery with two EntityManager (especially with Spring)? Any errors I should know about before trying to do this? It seems that every service and DAO will now need to know about these two entity managers (with Spring, today they are almost agnostically saved). DAO "find" operations use a single EM; "update" uses a different one. Or have separate "reads" and "write" DAO. Uch.

Other options that I have considered include:

  • Use the DTO in the user interface, so automatic zooming does not affect anything. Ugly.
  • Move the call to merge until the end of any linked operation. Entity joins only after all checks and status updates succeed. It seems strange that the "save" service will no longer be merge (just a check). In fact, the user interface will be responsible for calling the DAO! Is it as unusual as it seems?

Advice? Thanks: -)

My architecture update includes:

  • Individual Objects Updated Using the User Interface (JSF)
  • Object identifiers are NOT auto-generated (pre-assigned UUIDs and / or business keys)
  • Objects have @Version auto-increment fields for oplocking
  • Save service checks em.merge (JPA over Hibernate)
  • Process services check, apply business logic, update status status.
  • Services can be compiled. One button user interface can
    • (Spring @Transactional Tips: @Transactional )
    • Save
    • Process 1
    • Process 2
    • ( @Transactional : commit)
  • Any service can raise a validation exception (JSR 303) that rolls back (messages are expected to be displayed in the user interface)
+6
source share
4 answers

You ask two different questions, and then a general call for advice. I will go with advice (I would like to put this in a comment, but SO will not let me comment ...)

Using a DTO (or any UI-only data structure) in the user interface is not ugly - it sounds architecturally. I agree with JB Nizet that EM’s non-transactional approach significantly transforms your organization into a DTO. If you have to use objects in your user interface (and I understand that Spring encourages this), this is the way to go.

In my experience, the main reason people want to use entities as a UI data store is to access the complex validation logic in it. You may also consider reorganizing to make this logic available without referring to your complete domain model.

+1
source

It can only support JB Nizet comment: merging only copies the state of individual objects.

+1
source

Obviously we can't just throw away user changes

if the user tries to update, and someone has already changed it, is the user better at updating the copy and making the changes again?

0
source

Since EntityManager.merge() creates a copy of the passed object, the solution seems pretty simple: maintain the link to the passed object in the user interface (JSF bean) until the transaction completes successfully, and only then update the link using the copied object:

 @ManagedBean @SomeScoped public class EntityBean { @EJB private PersistenceService service; private Object entity; public void update() { try { // service.update() is a CMT method Object updatedEntity = service.update(entity); // if we are here transaction has completed successfully, so update the reference entity = updatedEntity; } catch(Exception e) { // if catched, entity has not been modified (just like transaction never happened) // now handle exception (log, add a FacesMessage, rethrow, ...) } } ... } 

You can redisplay the user interface, and UIComponent will use the same object that the user filled in before sending the previous request.

This way, you don’t have to worry about reloading @Id s, @Version s or any other changed field or property, including cascading associations, or using multiple EntityManager s or not using the extended persistence context.

However, this simple example has some limitations:

  • never use EntityManager.persist in a "past" object, because persist does not execute a copy. You should always use EntityManager.merge , but there are times when this is simply not possible.

  • transferred objects must be in the "TRANSIENT" or "DETACHED" state for EntityManager.merge to create a copy (otherwise, the transferred object is returned without copying). This implies that using the extended context persistence or OpenSessionInViewFilter and its derivatives is strictly prohibited.

  • Particular attention should be paid when introducing the composition of the service with sub-transactions: you can end with wrapped objects when the sub-transaction completes successfully, but the parent rollbacks.

Please note that I do not know how Spring works, so all this may not be applicable.

0
source

Source: https://habr.com/ru/post/891377/


All Articles