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.
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 ?
ReplyDeleteI would use the find version that also locks the entity (added in JPA 2.0)..
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