Tuesday, December 10, 2013

Versioning & Optimistic Locking In Hibernate

By Jim White (Directory of Training and Instructor)
A few weeks ago while teaching Hibernate, a student (thanks Dan) asked me about whether version numbers can be generated by the database.  The answer is – Yes, in some cases.  I thought the question and an expanded discussion of Hibernate’s versioning property would be a good topic for this week’s blog entry.

The Property (Or @Version Annotation)

For those of you that use Hibernate today, you are probably aware that Hibernate can provide optimistic locking through a version property on your persistent objects.  Furthermore, the version property is automatically managed by Hibernate.
To specify a version for your persistent object, simply add a version (or timestamp) property in your mapping file and add a version attribute to your persistent class.  Here is an example on a Product class.
public class Product {
    private long id;
    private String name;
    private String model;
    private double cost;
    private long version;
}

 
         
           
       

        
       
       
       
 
 
For those using annotations, just mark the version property’s getter in the class with the @Version annotation.
@Version public long getVersion() {
    return version;
}
The version property on the class can be numeric (short, int, or long) or timestamp or calendar.  However, another configuration element is provided for timestamps or calendars (covered below).
With automatic versioning (and version set to a numeric), Hibernate will use the version number to check that the row has not been updated since the last time it was retrieved when updating the persistent object (Product in this example).  Notice the extra where “and version = X” clause below, when a product from above is updated.
Hibernate:
    /* update
        com.intertech.domain.Product */ update
            Product
        set
            version=?,
            name=?,
            model=?,
            cost=?
        where
            id=?
            and version=?
If the version has been updated, Hibernate throws a StaleObjectStateException and the transaction (and any persistent object changes) rollback.  If the update commits without issue, Hibernate also increments the version number automatically (note the “set version = ?” as part of the update clause above).

Hibernate Versus The Database Managing The Version

Under this configuration and in this example, Hibernate (from within your Java application) is managing the version number.  However, some DBA’s may prefer to have the version controlled by database rather than Hibernate, especially when the database may be a timestamp and the application is distributed to multiple systems (maybe even timezones).  If the Hibernate application is on different systems that are not time synchronized, the timestamp generated by each system might be different thereby generating improper optimistic lock success or failures.  To inform Hibernate to have the database manage the version, include a generated=”always” attribute in the Hibernate mapping of the persistent object.

 
         
           
       

        generated=”always”
/>
       
       
       
 

Hibernate will now defer to the database to generate the necessary version and update the persistent object’s row accordingly.  A warning is in order however.  This will cause Hibernate to issue an extra SQL to get the version number back from the database after a successful update or insert.  Note the extra SQL issued below after a successful update of a product (Hibernate even comments it to indicate why it is needed).  You might also note that the version number is not set as above when updating the product.  Again, Hibernate is now totally deferring to the database to manage this property.
Of important note, not all dialects (i.e. databases) support the managing of the version number.  HSQLDB, for example, does not allow the version number to be controlled from the database.
Hibernate:
    /* update
        com.intertech.domain.Product */ update
            Product
        set
            name=?,
            model=?,
            cost=?
        where
            id=?
            and version=?
Hibernate:
    /* get generated state com.intertech.domain.Product */ select
        product_.version as version8_
    from
        Product product_
    where
        product_.id=?

Timestamps For Version

In many situations, your DBA or organization may prefer a timestamp to be used as the version indicator.  In theory, the timestamp is a little less safe than a version number.  This is because a timestamp can, in theory, be updated by two transactions at the exact same time (depends on the precision of the database) and allow both to update causing a last in wins scenario (violating the optimistic lock).  It can also be argued that timestamps also take up more space in the database.  Depending on the dialect, the use of a timestamp may incur an additional hit of the database to get the timestamp.  However, on the plus side, timestamps provide useful information about the “when” a row was last altered.  In cases of auditing or record tracking, this might be important information.  Version numbers don’t provide any context for when an update occurred.
If a timestamp is useful, change your version property to be a java.util.Date or Calendar and use a (rather than using a ) element in the mapping file.
public class Product {
    private Date version;
}

 
         
           
       

        <!––>
                
       
       
  
 
The and are equivalent, but it might help the reader to better see/understand that the version is an effectivity timestamp versus a numerical indicator.
You might also notice the source=”db” in the mapping configuration above.  The source attribute can be set to “vm” (for virtual machine) or “db” (for database).  When set to “db”, the database creates and manages the version timestamp (equivalent to generated=”always”) above.  As mentioned above, in the case of the database managing the version timestamp, your application may incur an additional hit of the database as Hibernate must first retrieve the timestamp before updating the row.  Further, as the Hibernate documentation says:  “Not all Dialects are known to support the retrieval of the database’s current timestamp.”

Optimistic Locking Without A Version

Your application may need to deal with a legacy database and the database cannot be altered to contain a version (numeric or timestamp) column.  In this case, Hibernate provides two additional options for optimistic lock management.  You can use all the fields of the persistent object to check that nothing in a row was modified before updating a row.  Set the optimistic-lock=”all” attribute (and dynamic-update=”true”) in the element to use this approach.  The SQL issued by Hibernate will then use each property as a check in the where clause as shown below.
Hibernate:
    /* update
        com.intertech.domain.Product */ update
            Product
        set
            name=?
        where
            id=?
            and name=?
            and model=?
            and cost=?
comparison of the state of all fields in a row but without a version or timestamp property mapping, turn on optimistic-lock="all" in the  mapping
As a slight alternative to this approach, you can set optimistic-lock=”dirty” and then Hibernate will only use modified columns as part of the optimistic check (see below).  This last approach actually allows to transactions to update the same object concurrently legally, but only if they are modifying different columns – something that might be advantageous in a larger application setting where many people are accessing the same objects (thus rows) but rarely updating the same properties (columns).
Hibernate:
    /* update
        com.intertech.domain.Product */ update
            Product
        set
            name=?
        where
            id=?
            and name=?
See the Hibernate documentation for more details on these alternatives to versioning.
Come join us to learn more about Hibernate!  Sign up for Complete Hibernate at Intertech.com here.

No comments: