Saturday, July 27, 2013

JPA @Version annotation: when is the version updated?


In JPA you can annotate a field of an entity with @Version to specify that property as its optimistic lock value.
But the above documentation does not say when the version number is validated and updated (increased).
The short answer is: at commit time!

I came across this because I was having trouble passing on the updated version number back to the caller, after a merge() operation.

The setup

Example and the solution

A client class (not in a transaction) invokes the method 

   /** 
    * Updates the order and returns the new version number for the order
    *
    * @param order the entity to update. Must be provided with the current version of the order.
    *             If not provided, StaleObjectStateException will be thrown
    */
   Date updateMe(Order orderWithNewValues);

on a service class, which is annotated with Spring @Service and Spring @Transactional

That method invokes 

   /** 
    * Similar java doc
    */
   Date updateMe(SomeEntity orderWithNewValues)

in a DAO which is annotated with Spring @Transactional(propagation = Propagation.MANDATORY) and @Repository.  The DAO searches for the entity to update using its name, sets the version number to the currently known version number, updates it and should return the new version number.

The class Order is annotated (amongst other things) with @Version as follows:

   @Version
   @Column(nullable = false)
   @Temporal(TemporalType.TIMESTAMP)
   private Date version;
   ...
   DateTime getVersion() {
       return version;
   }

The implementation of the DAO method implementing the update originally was:

   Order order = orderDao.findByName(name);
   if (order == null) {
       throw new IllegalArgumentException("No order with name '" + name + "' exists");
   }

   Order updatedOrderNotManaged = new Order(order);
   updatedOrderNotManaged.setVersion(orderWithNewValues.getVersion());
   updatedOrderNotManaged.setTotal(orderWithNewValues.getTotal());

   Order updatedOrderManaged = orderDao.merge(updatedOrderNotManaged);

   return updatedOrderManaged.getVersion();


Expecting that the last line (return updatedOrderManaged.getVersion()) would return the new version number, since the non-managed entity was now managed.

But it isn't! What seems to be the reason is that:
  • The JPA implementation (in this case Hibernate) can do some caching, deferring the actual database access to a later moment, causing the changes to not hit the database yet.
  • Not hitting the database also doesn't cause the @Version to be "triggered" (it seems).
  • So the version is still at its old value!
But after the service method had returned the (old) version to the client calling it (so the transaction was committed), in the database you can see the new increased timestamp in the version column.

The solution is to force the JPA implementation to flush() to the database, such that the @Version annotation gets triggered and thus the version gets updated.
Therefor the following was added, just before the "return"  statement: 


   orderDao.flush();

That did it, now return updatedOrderManaged.getVersion()) is returning the updated version number. Should the transaction commit fail later, then no harm is done since the whole transaction is rolled back and the client should get an exception or similar so it knows it should ignore the returned version number.


No comments: