Java EE applications usually handle many concurrent users. This means that there must exist some strategies that keep the data consistent in case of concurrent modification of the same database row (entity). JPA2 defines two types of locking, namely Optimistic Locking (OL) and Pessimistic Locking (PL).
Firstly, let's consider OL. This locking strategy assumes that it is unlikely that the same entity will be modified concurrently. In case of such modification javax.persistence.OptimisticLockException is thrown. Take a look at this entity:
@Entity public class Customer { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "customer_seq") @SequenceGenerator(name = "customer_seq", sequenceName = "CUSTOMER_SEQ") private long id; private String name; @Version private long version; public long getId() { return id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
There is nothing special about that entity, except for the field annotated with @Version. It tells JPA provider that OL should be used. Every time the modification of the entity (database update) is to be performed, the value of the version field will be incremented. Let's analyze the following code example:
@Stateless public class OptimisticExceptionBean { @PersistenceContext(unitName = "my-unit") private EntityManager em; @Resource private SessionContext ctx; @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void addCustomer() { Customer c = new Customer(); c.setName("John Doe"); em.persist(c); } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void tx1() { Customer c = em.find(Customer.class, 1L); c.setName("John Doe 1"); startTx2(); } private void startTx2() { OptimisticExceptionBean thisBean = ctx.getBusinessObject(OptimisticExceptionBean.class); thisBean.tx2(); } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void tx2() { Customer c = em.find(Customer.class, 1L); c.setName("John Doe 2"); } }The invocation order is as follows:
1). addCustomer();
2). tx1();
The result of such invocations is... exception:
ARJUNA012125: TwoPhaseCoordinator.beforeCompletion - failed for SynchronizationImple... org.hibernate.engine.transaction.synchronization.internal.RegisteredSynchronization@1e7cf16f >: javax.persistence.OptimisticLockException: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [entity.Customer#1]
TX1 is rolled back and TX2 is committed. When TX1 is about committing, JPA provider checks if we are aware of the latest changes (the value of the version field within entity is the same as the value in the database) that were applied to the entity.
1). We have queried Customer entity within TX1. The value of the version was 0.
2). Then, we started TX2, queried the same Customer entity and committed the changes. The value of the version was incremented: 0+1=1 in the database.
3). Next, we went back to TX1 and tried to commit the changes as well. However, our version value was 0 and the value in the database was 1. That's why the exception appeared:
TX1 start
TX2 start
TX2 end
TX1 end - OptimisticLockException
You should be aware that in our example we have enforced the OptimisticLockException - tx1() method (within TX1) invokes tx2() method (within TX2) ant waits for TX2 to commit. After adding @Asynchronous annotation for tx2() method [tx1() does not wait for the return of tx2()] - both of the transactions were committed without any exceptions:
TX1 start
TX1 end
TX2 start
TX2 end
However, the order could be different - it is up to JVM and container to manage asynchronous invocations.
How can we make the transactions commit successfully? We should minimize the periods between querying an entity and committing the
changes of that entity. The transactions should be short. If the transaction is long running the state of the entity should be refreshed before committing:
public void tx1() { Customer c = em.find(Customer.class, 1L); startTx2(); em.refresh(c); c.setName("John Doe 1"); }The changes made by TX2 are overwritten.
Someone can say: "OK, let's use Pessimistic Locking instead of Optimistic Locking and it will do the trick". But that's the topic for another blog entry. Stay tuned!
No comments :
Post a Comment