Hi,

I'm getting some exceptions in my unit tests due to optimistic locking
errors and I don't quite understand why. There are 2 scenarios but for
brevity I'll just focus on one here and maybe that'll shed light on the
other case. BTW, I'm using OpenJPA 2.0.1.

Basically, I try to delete an entity which has 2 levels of entity
collections. Since the entities have CascadeType.ALL I expected that a
simple em.remove() would cascade to the others without error. Instead I
get the optimistic lock errors. Any insights appreciated. And my
apologies for including so much source; I tried to pare it down to the
essentials.

Below is the test which fails. (Some background: The methods
makeStubRecipe(), addGroup/Ingredient/Step() are just helpers that
populate fields with arbitrary data. The basic structure of the data is:
a Recipe has a list of Groupings; a Grouping has a list of Ingredients
and a list of Steps.):

@Test
public void testDelete_Cascades() {
        Recipe recipe = makeStubRecipe();
        Grouping g = addGroup(recipe);
        addIngredient(g);
        addStep(g);

        // Add to db
        et.begin();
        em.persist(recipe);
        et.commit();

        assertFalse(em.contains(recipe));

        int gid = recipe.getGroupings().get(0).getGroupId();
        int iid = 
recipe.getGroupings().get(0).getIngredients().get(0).getIngredientId();
        int sid = recipe.getGroupings().get(0).getSteps().get(0).getStepId();

        // Now delete
        recipe = em.find(Recipe.class, recipe.getRecipeId());
        et.begin();
        em.remove(recipe);
        et.commit();    // <- FAILS HERE
        
        assertNull(em.find(Recipe.class, recipe.getRecipeId()));
        assertNull(em.find(Grouping.class, gid));
        assertNull(em.find(Ingredient.class, iid));
        assertNull(em.find(Step.class, sid));
}

If I add the following right after et.begin() it succeeds:

        for (Grouping g2 : recipe.getGroupings()) {
                for (Ingredient i: g2.getIngredients()) {
                        em.remove(i);
                }
                for (Step s : g2.getSteps()) {
                        em.remove(s);
                }
                em.remove(g2);
        }

But it seems like I shouldn't have to programmatically cascade the
removes. Am I simply mistaken? If so, what's the value of declaring a
cascading relationship? The DDL, BTW, does have 'on delete cascade' on
the foreign key constraints.

Here's the exception:

<openjpa-2.0.1-r422266:989424 fatal store error> 
org.apache.openjpa.persistence.RollbackException: Optimistic locking errors 
were detected when flushing to the data store.  The following objects may have 
been concurrently modified in another transaction: 
[org.cliftonfarm.feed.domain.Grouping-1069, 
org.cliftonfarm.feed.domain.Ingredient-1107, 
org.cliftonfarm.feed.domain.Step-1119]
        at 
org.apache.openjpa.persistence.EntityManagerImpl.commit(EntityManagerImpl.java:584)
        at 
org.cliftonfarm.feed.domain.RecipeTest.testDelete_Cascades(RecipeTest.java:1451)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at 
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at 
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at 
org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
        at 
org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
        at 
org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
        at 
org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
        at 
org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
        at 
org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
        at org.junit.rules.TestWatchman$1.evaluate(TestWatchman.java:48)
        at 
org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79)
        at 
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71)
        at 
org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
        at 
org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
        at 
org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
        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:467)
        at 
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
        at 
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
        at 
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: <openjpa-2.0.1-r422266:989424 nonfatal store error> 
org.apache.openjpa.persistence.OptimisticLockException: Optimistic locking 
errors were detected when flushing to the data store.  The following objects 
may have been concurrently modified in another transaction: 
[org.cliftonfarm.feed.domain.Grouping-1069, 
org.cliftonfarm.feed.domain.Ingredient-1107, 
org.cliftonfarm.feed.domain.Step-1119]
        at 
org.apache.openjpa.kernel.BrokerImpl.newFlushException(BrokerImpl.java:2291)
        at org.apache.openjpa.kernel.BrokerImpl.flush(BrokerImpl.java:2139)
        at org.apache.openjpa.kernel.BrokerImpl.flushSafe(BrokerImpl.java:2037)
        at 
org.apache.openjpa.kernel.BrokerImpl.beforeCompletion(BrokerImpl.java:1955)
        at 
org.apache.openjpa.kernel.LocalManagedRuntime.commit(LocalManagedRuntime.java:81)
        at org.apache.openjpa.kernel.BrokerImpl.commit(BrokerImpl.java:1479)
        at 
org.apache.openjpa.kernel.DelegatingBroker.commit(DelegatingBroker.java:925)
        at 
org.apache.openjpa.persistence.EntityManagerImpl.commit(EntityManagerImpl.java:560)
        ... 29 more
Caused by: <openjpa-2.0.1-r422266:989424 nonfatal store error> 
org.apache.openjpa.persistence.OptimisticLockException: An optimistic lock 
violation was detected when flushing object instance 
"org.cliftonfarm.feed.domain.Grouping-1069" to the data store.  This indicates 
that the object was concurrently modified in another transaction.
FailedObject: org.cliftonfarm.feed.domain.Grouping-1069
        at 
org.apache.openjpa.jdbc.kernel.PreparedStatementManagerImpl.flushAndUpdate(PreparedStatementManagerImpl.java:123)
        at 
org.apache.openjpa.jdbc.kernel.BatchingPreparedStatementManagerImpl.flushAndUpdate(BatchingPreparedStatementManagerImpl.java:81)
        at 
org.apache.openjpa.jdbc.kernel.PreparedStatementManagerImpl.flushInternal(PreparedStatementManagerImpl.java:99)
        at 
org.apache.openjpa.jdbc.kernel.PreparedStatementManagerImpl.flush(PreparedStatementManagerImpl.java:87)
        at 
org.apache.openjpa.jdbc.kernel.ConstraintUpdateManager.flush(ConstraintUpdateManager.java:550)
        at 
org.apache.openjpa.jdbc.kernel.ConstraintUpdateManager.flush(ConstraintUpdateManager.java:120)
        at 
org.apache.openjpa.jdbc.kernel.BatchingConstraintUpdateManager.flush(BatchingConstraintUpdateManager.java:59)
        at 
org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.flush(AbstractUpdateManager.java:103)
        at 
org.apache.openjpa.jdbc.kernel.AbstractUpdateManager.flush(AbstractUpdateManager.java:76)
        at 
org.apache.openjpa.jdbc.kernel.JDBCStoreManager.flush(JDBCStoreManager.java:731)
        at 
org.apache.openjpa.kernel.DelegatingStoreManager.flush(DelegatingStoreManager.java:131)
        ... 36 more



The JPA properties I'm setting on the EntityManagerFactory are:

#-----------------------------------------------------------------
javax.persistence.jdbc.driver=org.apache.derby.jdbc.ClientDriver
javax.persistence.jdbc.url=jdbc:derby://localhost/feed
javax.persistence.jdbc.user=<whatever>
javax.persistence.jdbc.password=<whatever>
javax.persistence.sharedCache.mode=UNSPECIFIED

openjpa.Specification="JPA 2.0"
openjpa.AutoDetach=close,commit
openjpa.DynamicDataStructs=true
openjpa.Log=log4j
openjpa.TransactionMode=local
openjpa.jdbc.SynchronizeMappings=refresh
openjpa.jdbc.ResultSetType=scroll-insensitive
openjpa.jdbc.UpdateManager=batching-constraint
#-----------------------------------------------------------------

Finally, here's the entity sources with most of the boring details cut
out:

//----------------------------------------------------------------
@MappedSuperclass
public abstract class Versionable {

        @Version
        private Integer version;

        public Integer getVersion() {
                return version;
        }
}

//----------------------------------------------------------------
@Entity
public class Recipe extends Versionable implements Serializable {

        private static final long serialVersionUID = -2136187716531617857L;

        /** Unique ID for this Recipe */
        @Id
        @Column (name="RECIPE_ID", insertable=false, nullable=false, 
updatable=false)
        @GeneratedValue (strategy=GenerationType.IDENTITY)
        private Integer recipeId;

        @OneToMany (mappedBy="recipe", 
                cascade={CascadeType.ALL}, 
                fetch=FetchType.LAZY, 
                orphanRemoval=true)
        @OrderBy ("position")
        private List<Grouping> groupings = new LinkedList<Grouping>();

        /* ... uninteresting data fields omitted... */

        //--------------------------------------------------------------- 
ACCESSORS

        public Integer getRecipeId() {
                return recipeId;
        }

        public List<Grouping> getGroupings() {
                return groupings;
        }

        public void setGroupings(List<Grouping> groupings) {
                this.groupings = new LinkedList<Grouping>();
                this.groupings.addAll(groupings);
                for (Grouping g : this.groupings) {
                        // Welcome to the family:
                        g.setRecipe(this);
                }
        }

        /* ... uninteresting accessors & other methods omitted... */
}

//----------------------------------------------------------------
@Entity
public class Grouping extends Versionable implements Serializable {

        private static final long serialVersionUID = 4129360725018241352L;

        /** Unique ID for this Grouping */
        @Id
        @Column (name="GROUP_ID", insertable=false, nullable=false, 
updatable=false)
        @GeneratedValue (strategy=GenerationType.IDENTITY)
        private Integer groupId;

        /** Recipe to which this Grouping belongs */
        @ManyToOne
        @JoinColumn (name="RECIPE_ID", insertable=true, nullable=false, 
updatable=true)
        private Recipe recipe;
        
        @OneToMany (mappedBy="grouping", 
                cascade={CascadeType.ALL}, 
                fetch=FetchType.LAZY, 
                orphanRemoval=true)
        @OrderBy ("position")
        private List<Ingredient> ingredients = new LinkedList<Ingredient>();

        @OneToMany (mappedBy="grouping", 
                cascade={CascadeType.ALL}, 
                fetch=FetchType.LAZY, 
                orphanRemoval=true)
        @OrderBy ("position")
        private List<Step> steps = new LinkedList<Step>();

        /* ... uninteresting data fields omitted... */

        //--------------------------------------------------------------- 
ACCESSORS

        public Integer getGroupId() {
                return groupId;
        }

        public Recipe getRecipe() {
                return recipe;
        }

        public void setRecipe(Recipe recipe) {
                this.recipe = recipe;
        }

        public List<Ingredient> getIngredients() {
                return ingredients;
        }

        public void setIngredients(List<Ingredient> ingredients) {
                this.ingredients = new LinkedList<Ingredient>();
                this.ingredients.addAll(ingredients);
                for (Ingredient i : this.ingredients) {
                        // Welcome to the family:
                        i.setGrouping(this);
                }
        }

        public List<Step> getSteps() {
                return steps;
        }

        public void setSteps(List<Step> steps) {
                this.steps = new LinkedList<Step>();
                this.steps.addAll(steps);
                for (Step s : this.steps) {
                        // Welcome to the family:
                        s.setGrouping(this);
                }
        }

        /* ... uninteresting accessors & other methods omitted... */
}

//----------------------------------------------------------------
@Entity
public class Ingredient extends Versionable implements Serializable {

        private static final long serialVersionUID = 8616212484953302289L;

        /** Unique ID for this Ingredient */
        @Id
        @Column (name="INGREDIENT_ID", insertable=false, nullable=false, 
updatable=false)
        @GeneratedValue (strategy=GenerationType.IDENTITY)
        private Integer ingredientId;

        /** The Grouping to which this Ingredient belongs */
        @ManyToOne
        @JoinColumn (name="GROUP_ID", insertable=true, nullable=false, 
updatable=true)
        private Grouping grouping;

        /* ... uninteresting data fields omitted... */

        //--------------------------------------------------------------- 
ACCESSORS

        public Integer getIngredientId() {
                return ingredientId;
        }

        public Grouping getGrouping() {
                return grouping;
        }

        public void setGrouping(Grouping grouping) {
                this.grouping = grouping;
        }

        /* ... uninteresting accessors & other methods omitted... */

}

//----------------------------------------------------------------
@Entity
public class Step extends Versionable implements Serializable {

        private static final long serialVersionUID = -3765886461428007870L;

        /** Unique ID for this Step */
        @Id
        @Column (name="STEP_ID", insertable=false, nullable=false, 
updatable=false)
        @GeneratedValue (strategy=GenerationType.IDENTITY)
        private Integer stepId;

        /** The Grouping to which this Step belongs */
        @ManyToOne
        @JoinColumn (name="GROUP_ID", insertable=true, nullable=false, 
updatable=true)
        private Grouping grouping;

        /* ... uninteresting data fields omitted... */

        //--------------------------------------------------------------- 
ACCESSORS

        public Integer getStepId() {
                return stepId;
        }

        public Grouping getGrouping() {
                return grouping;
        }

        public void setGrouping(Grouping grouping) {
                this.grouping = grouping;
        }

        /* ... uninteresting accessors & other methods omitted... */

}


Reply via email to