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)
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.