ObjectDB ObjectDB

Remove an entity which attributes were changed - OptimisticLockException

#1

We use an OSGi environment and we have three OSGi plugins.

 

In plugin A an entity is loaded and the entity is passed to plugin B.

Plugin B changes an attribute on the entity.

Plugin C removes the entity from the database.

After commit the transaction an exception is caused with optimistic lock exception of the removed entity.

If this operations on the entity are executed in only one plugin, it works well.

Why the ObjectDB want to commit the changes of the entity although the entity will be removed, if we use three OSGi plugins.

edit
delete
#2

The difference between using one plugin or several plugins seems to be using one or more EntityManagerFactory / EntityManager instances, i.e. this could also be demonstrated by a non OSGi application:

  • User 1 retrieves an object and changes it.
  • User 2 retrieves an object, removes it and commit.

This should work if the changes of user 1 are local in memory and will fail with OptimisticLockException if user 1 is committing or flushing the changes before user 2 is trying to remove the object, since then there is a conflict between two different operations on the same object by two different users.

Note that user 1 may flush the changes implicitly by running a query. You can run specific queries with COMMIT flush mode to avoid flushing changes to the database.

You may also disable optimistic locking checks if you do not need it.

ObjectDB Support
edit
delete
#3

We use only one entity manager, one transaction and one thread.

The problem is not the wrong practice of operations. The ObjectDB does not throw the changes of the entity, if entity will remove. And this is only the case, if invoking the operations in different plugins. This leads to the optimistic lock exception.

But the ObjectDB throws the changes of the entity, if invoking the operations in one plugin.

In both cases the operations are the same. What is the reason why the ObjectDB can not avoid the invoking of the changes although the entity will remove?

edit
delete
#4

What do you mean by "ObjectDB throws the changes of the entity"?

Are you sure that the same EntityManager (not just the same EntityManagerFactory) is used by the different plugins?

Could you please check if the same Java instance is used by the two operations (as should be when only one EntityManager is used) or different Java instances?

ObjectDB Support
edit
delete
#5

I meant the ObjectDB throws away the changes of entity, because it is not necessary to commit the changes, if the entity will be removed. This happens, if used in one plugin, but is not happen, if used in several plugins.

We use really one EntityManager in all plugins.
And also the entity instances in both plugins are identical.
I checked all of this with a debugger.

 

We had a similar issue last year 2015-11-09 under the forum task 'Remove a modified entity cause an optimistic lock exception'. Maybe it helps to pin down the source of the issue.

edit
delete
#6

Forum thread "Remove a modified entity cause an optimistic lock exception" indeed discusses a similar problem. However, the test case on that forum thread still passes with the last ObjectDB version.

ObjectDB maintains a version number for every entity object, which is increased on every update. An OptimisticLockException is thrown when that version number indicates a concurrent update by another user (using another EntityManager). According to your description this is not the case.

In order to understand the cause, please track the object version. You can do that easily by adding a version field to the entity class, so the version number will be injected to that field.

Please check this version number during the operations with one plugin and with multiple plugins and find the exact point of difference between these two modes.

ObjectDB Support
edit
delete
#7

I have found the problem.

Our entity had the version = 3 on entityA (Type A) and after a find operation on another entityB (Type B) the version changed to 8.

entityA.setMap(); // change the entity field

EntityManager em = getEntityManager();
entityB = em.find(TypeB, uid of TypeB);

I don't understand why this operation leads to increase the version number of entity, although both entities haven't any relation each other.

This fails also in both cases, with several plugins and with one plugin.

edit
delete
#8

This is definitely an unexpected behaviour.

If it fails also with one plugin, could you try isolating it further to a simple test console application?

ObjectDB Support
edit
delete
#9

In a simple console application the issue is not occurred.

I created an OSGi example for eclipse mars, with plugins com.btc.ep.base.dal.tests.it and main.You can execute the unit test OptimisticLockTest as a JUnit Plugin Test. The test causes the optimistic lock exception.


 

 

edit
delete
#10

The test fails with another exception:

java.lang.NullPointerException
at org.osgi.util.tracker.ServiceTracker.<init>(ServiceTracker.java:184)
at com.btc.ep.core.Services.getServiceTracker(Services.java:23)
at com.btc.ep.core.Services.get(Services.java:34)
at com.btc.ep.base.dal.tests.it.OptimisticLockTest.getPersistenceService2(OptimisticLockTest.java:97)
at com.btc.ep.base.dal.tests.it.OptimisticLockTest.deleteDatabase(OptimisticLockTest.java:92)
at com.btc.ep.base.dal.tests.it.OptimisticLockTest.setUp(OptimisticLockTest.java:37)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
ObjectDB Support
edit
delete
#11

Please execute the unit test OptimisticLockTest as a JUnit Plugin Test.

edit
delete
#12

The issue can be demonstrated by the following test case:

import javax.persistence.*;

public class T1855 {

    public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory(
                "objectdb:$objectdb/db/test.tmp;drop");

        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        em.persist(new MyEntity());
        em.getTransaction().commit();
        em.close();

        em = emf.createEntityManager();
        em.getTransaction().begin();
        MyEntity e = em.find(MyEntity.class, 1);
        e.value++;
        em.flush();
        em.remove(e);
        em.flush();
        em.getTransaction().commit();
        em.close();
       
        emf.close();
    }

    @Entity
    public static class MyEntity {
        @Id @GeneratedValue long id;
        int value;
    }
}

Version 2.6.8 fixes the bug.

ObjectDB Support
edit
delete
#13

The fix solves the issue.

But now we have another issue in our OSGi Test (use eclipse mars) with plugins com.btc.ep.base.dal.tests.it and main.

You can execute the unit test RemovedFormalRequirementIsAlwaysInTheDatabaseTest as a JUnit Plugin Test.

The test shows an FormalRequirementImpl entity which the test has removed. But in another transaction the FormalRequirementImpl exists in the database yet.

We had expected an exception that the entity could not be removed.

See the attachment.

 

edit
delete
#14

Could you please demonstrate the issue with a simple example, as in #12 above? Maybe by applying a small change to that console test case? It is very difficult to follow an issue in a large test case (2 projects, 46 java files, which most of it is unrelated to the reported issue).

ObjectDB Support
edit
delete
#15

Yes - you can directly use your Test-Example.

Check the Database after removing Object 'e'

 

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Persistence;

public class T1855 {
public static void main(String[] args) {

        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory(
                "objectdb:$objectdb/db/test.tmp;drop");

        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        MyEntity entity = new MyEntity();
        em.persist(entity);
        em.getTransaction().commit();
        em.close();

        em = emf.createEntityManager();
        em.getTransaction().begin();
        MyEntity e = em.find(MyEntity.class, 1);
        e.value++;
        em.flush();

        long removedID = e.id;
        em.remove(e); // remove e
        em.flush();
        em.getTransaction().commit();
        em.close();

        em = emf.createEntityManager();
        em.getTransaction().begin();
        MyEntity removed = em.find(MyEntity.class, removedID);
        if (removed != null) {
            System.out.println("Find removed Entity... " + removed);
            throw new RuntimeException();
        }
        em.getTransaction().commit();
        em.close();

        emf.close();
    }

    @Entity
    public static class MyEntity {
        @Id
        @GeneratedValue
        long id;
        int value;
    }
}

 

edit
delete
#16

Thanks. Build 2.8.0_01 should fix it.

ObjectDB Support
edit
delete
#17

Thanks. The issue is solved with Build 2.8.0_01.

edit
delete

Reply

To post on this website please sign in.