How to handle org.eclipse.persistence.exceptions.OptimisticLockExceptio

ssss picture ssss · Dec 12, 2014 · Viewed 9.4k times · Source

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)

Answer

akhilless picture akhilless · Dec 15, 2014

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.