I want to handle concurrent execution by using Optimistic Locking. I have included @Version
annotation in my entity class.
In my code I am running two threads concurrently. Sometimes it is executing correctly. Sometimes it is throwing org.eclipse.persistence.exceptions.OptimisticLockException
and javax.persistence.OptimisticLockException
.
public class StudentCreation implements Runnable {
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("jpaApp");
Student student = null;
@Override
public void run() {
for (int i = 1; i <= 2; i++) {
try {
if (i % 2 == 0) {
update("XYZ");
} else {
update("ABC");
}
} catch (Exception e) {
System.out.println("Update exception");
}
}
}
// main method
public static void main(String ar[]) {
StudentCreation std1 = new StudentCreation();
// creating two threads
Thread thread1 = new Thread(std1, "Thread1");
Thread thread2 = new Thread(std1, "Thread2");
thread1.start();
thread2.start();
}
public Object update(String name) throws Exception {
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// reading data from database
student = em.find(Student.class, 1L);
em.lock(student, LockModeType.OPTIMISTIC);
//updating
student.setStudentName(name);
em.getTransaction().commit();
return student;
}
}
[EL Warning]: 2014-12-12 17:54:35.75--UnitOfWork(744551)--Thread(Thread[Thread2,5,main])--Local Exception Stack:
Exception [EclipseLink-5006] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.OptimisticLockException
Exception Description: The object [locks.Student@131a989] cannot be updated because it has changed or been deleted since it was last read.
Class> locks.Student Primary Key> 1
at org.eclipse.persistence.exceptions.OptimisticLockException.objectChangedSinceLastReadWhenUpdating(OptimisticLockException.java:144)
at org.eclipse.persistence.descriptors.VersionLockingPolicy.validateUpdate(VersionLockingPolicy.java:790)
at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.updateObjectForWriteWithChangeSet(DatabaseQueryMechanism.java:1087)
at org.eclipse.persistence.queries.UpdateObjectQuery.executeCommitWithChangeSet(UpdateObjectQuery.java:84)
at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:301)
at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58)
at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:899)
at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:798)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:108)
at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:85)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1804)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1786)
at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1737)
at org.eclipse.persistence.internal.sessions.CommitManager.commitChangedObjectsForClassWithChangeSet(CommitManager.java:267)
at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:130)
at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:4207)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1441)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1531)
at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitRootUnitOfWork(RepeatableWriteUnitOfWork.java:277)
at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitAndResume(UnitOfWorkImpl.java:1169)
at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:132)
at locks.StudentCreation.update(StudentCreation.java:43)
at locks.StudentCreation.run(StudentCreation.java:19)
at java.lang.Thread.run(Unknown Source)
To know how to handle OptimisticLockException
you have first to consider how optimistic locking works. When using this policy your entity gets a version field or property that is annotated with @Version and which you can map to a column in the database if you want. By default when optimistic locking is used the JPA provider compares the value the @Version field had when the entity was read from the database with the one that it has now. This happens when the changes in the entity are being commited to the database which typically happens at the end of a transaction. If the old value of the @Version field equals to the current one the persistence provider increments the version field. But if they do not match the OptimisticLockException is thrown to indicate that during your transaction some other transaction has changed the entity and your trasaction is rolledback. This behavior prevents you from overriding another transaction's results. This means that the most appropriate way to handle the exception is rereading the entity from the database and reapplying the changes you want to make to it. For example you can do it in a loop
boolean success = false;
while (!success) {
try {
//start transaction, read entity, update it and commit
success = true;
}
catch (OptimisticLockException ex) {
//log your error
}
}
This ensures that your code tries rereading the entity and reapplying changes to it untill no version conflict occurs and transaction is successfully committed.