Thursday 3 March 2011

Pessimistic and Optimistic Locking in JPA 2.0

A most welcome addition to JPA 2.0 was the introduction of pessimistic locking. This allows the entity manager (or query) to lock a database record thereby preventing other transactions from changing the same record. This will ensure data consistency but at a performance cost so for each entity you need to ask yourself what is the chance of contention?

Let's look at the two approaches in more detail:

Optimistic Locking

Optimistic locking is the preferred approach when modifying entities that are infrequently updated. Optimistic locking can be explicit as will be shown later or implicit by using a version attribute. A version attribute is an attribute which has been annotated with @Version. This attribute will then get incremented when the transaction commits. The below class shows an example of its usage:

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.PersistenceContext;
import javax.persistence.Version;
 

@MappedSuperclass
public abstract class BaseEntity implements Serializable {

    private static final long serialVersionUID = 1L;
    private static final int ID_LENGTH=36;

    @Id
    @Column(length = ID_LENGTH)
    private String id;
    @Version
    private int versionNumber;

    // etc ...

}
 
There are two optimistic lock modes:

  • OPTIMISTIC or READ - locks the entity when the transaction reads it for entities wit a version
  • OPTIMISTIC_FORCE_INCREMENT or WRITE - locks the entity when the transaction reads it for entities with a version and increments the version attribute

Pessimistic Locking

Pessimistic locking can be applied to all entities regardless of whether they have a version attribute or not.
 
There are three pessimistic lock modes:

  • PESSIMISTIC_READ - locks the entity when the transaction reads it and allows reads from other transactions
  • PESSIMISTIC_WRITE - locks the entity when the transaction updates it but does not allow reads, updates or deletes from other transactions
  • PESSIMISTIC_FORCE_INCREMENT - locks the entity when the transaction reads it and increments the version attribute (if present)

There are many different ways to implement a pessimistic or optimistic lock using the Entity Manager and Query interfaces as shown in the example classes below:

import java.io.Serializable;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;


@Named("customerManager")
@ApplicationScoped
public class CustomerManager implements Serializable {


    private static final long serialVersionUID = 1L;

    @PersistenceContext
    private EntityManager entityManager;


    public void setName(String id, String name) {

        Customer customer = 

            entityManager.find(Customer.class, id);
        entityManager.lock(customer,

            LockModeType.OPTIMISTIC_FORCE_INCREMENT);
        customer.setName(name);
    }


    public Customer findCustomer(String id) {

        return entityManager.find(Customer.class, id, 

            LockModeType.OPTIMISTIC);
    }


    public Customer 

        applyBankCharges(String id, Double charges) {
 
        Customer customer = 

            entityManager.find(Customer.class, id);
        customer.setBalance(customer.getBalance() - charges);
        if (customer.getBalance() < 0.0) {
            // overwrite changes and return the refreshed 

            // and locked object
            entityManager.refresh(customer, 

                LockModeType.PESSIMISTIC_WRITE);
        }
        return customer;
    }


    public List<Customer> 

        findCustomerByPartialName(String partialName) {
 
        Query query = entityManager.createQuery(
                "SELECT c FROM Customer c WHERE c.name" + 

                " LIKE :partialName").
                setParameter("partialName", partialName);
        query.setLockMode( 

            LockModeType.PESSIMISTIC_FORCE_INCREMENT);
        return query.getResultList();
    }
}



import javax.persistence.Entity;
import javax.persistence.LockModeType;
import javax.persistence.NamedQuery;


@Entity

@NamedQuery(name = "findByNameQuery",
    query = "SELECT c FROM Customer c WHERE c.name LIKE :name",
    lockMode = LockModeType.PESSIMISTIC_FORCE_INCREMENT)
public class Customer extends BaseEntity {


    private String name;

    private Double balance;

    public void setName(String name) {

        this.name = name;
    }
    public Double getBalance() {

        return this.balance;
    }
    public void setBalance(Double balance) {

        this.balance = balance;
    }
}

 

Finally, a word on exceptions. If a PessimisticLockException or an OptimisticLockException are thrown then the transaction is rolled back as they are both RuntimeExceptions.

2 comments:

  1. In the "setName(String id, String name)" method, what happens if another user updates the name of the customer just between the "find" and the lock ? I assume his change will be overwritten, right ?

    I would use the find version that also locks the entity (added in JPA 2.0)..

    ReplyDelete
  2. If the customer's name gets updated in between the "find" and the lock then there'll be a version mismatch on the customer object so the entityManager.lock call will throw an OptimisticLockException.

    ReplyDelete

Note: only a member of this blog may post a comment.