You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@openjpa.apache.org by Tim Watts <ti...@cliftonfarm.org> on 2011/10/18 17:43:24 UTC
Confused about Optimistic lock exceptions
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... */
}
Re: Confused about Optimistic lock exceptions
Posted by Tim Watts <ti...@cliftonfarm.org>.
Thanks Milosz. That worked: SchemaFactory is my new friend. That also
fixed the other scenario I alluded to in my original post.
Thanks for the kinder, gentler RTFM too. There's a lot of knobs and
switches to digest in this area. Haven't used OpenJPA since the 1.0
days and that was on a fairly trivial schema (not that this one is
terribly complex).
On Wed, 2011-10-19 at 09:33 +0200, Miłosz Tylenda wrote:
> Tim,
>
> How about using @ForeignKey or changing mapping defaults so that
> OpenJPA knows the constraints defined in the database [1]?
>
> <property name="openjpa.jdbc.SchemaFactory"
> value="native(ForeignKeys=true)"/>
>
>
> Cheers,
> Milosz
>
> [1] http://openjpa.apache.org/builds/2.0.1/apache-openjpa-2.0.1/docs/manual/manual.html#ref_guide_mapping_jpa_fk
>
>
>
> > A little light shed when I set logging level to DEBUG. It appears that
> > OpenJPA is trying to delete from "top to bottom": i.e. RECIPE then
> > GROUPING then INGREDIENT then STEP. Since I have cascade deletes
> > declared in the DDL I can understand how it would get errors when trying
> > to delete from GROUPING etc.
> >
> > I am reluctant to relax the cascade deletes in DDL. So how can I
> > persuade OpenJPA to delete from the bottom up or just delete from RECIPE
> > and be done with it? I tried the following with no difference in
> > outcome:
> > * switching the UpdateManager to 'batching-operation-order'
> > * paring down @OneToMany.cascade to just PERSIST.
> >
> > Still welcoming any insights...
> >
> >
> > On Tue, 2011-10-18 at 11:43 -0400, Tim Watts wrote:
> > > 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... */
> > >
> > > }
> > >
> > >
> >
> >
> >
Re: Confused about Optimistic lock exceptions
Posted by Miłosz Tylenda <mt...@o2.pl>.
Tim,
How about using @ForeignKey or changing mapping defaults so that OpenJPA knows the constraints defined in the database [1]?
<property name="openjpa.jdbc.SchemaFactory" value="native(ForeignKeys=true)"/>
Cheers,
Milosz
[1] http://openjpa.apache.org/builds/2.0.1/apache-openjpa-2.0.1/docs/manual/manual.html#ref_guide_mapping_jpa_fk
> A little light shed when I set logging level to DEBUG. It appears that
> OpenJPA is trying to delete from "top to bottom": i.e. RECIPE then
> GROUPING then INGREDIENT then STEP. Since I have cascade deletes
> declared in the DDL I can understand how it would get errors when trying
> to delete from GROUPING etc.
>
> I am reluctant to relax the cascade deletes in DDL. So how can I
> persuade OpenJPA to delete from the bottom up or just delete from RECIPE
> and be done with it? I tried the following with no difference in
> outcome:
> * switching the UpdateManager to 'batching-operation-order'
> * paring down @OneToMany.cascade to just PERSIST.
>
> Still welcoming any insights...
>
>
> On Tue, 2011-10-18 at 11:43 -0400, Tim Watts wrote:
> > 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... */
> >
> > }
> >
> >
>
>
>
Re: Confused about Optimistic lock exceptions
Posted by Tim Watts <ti...@cliftonfarm.org>.
A little light shed when I set logging level to DEBUG. It appears that
OpenJPA is trying to delete from "top to bottom": i.e. RECIPE then
GROUPING then INGREDIENT then STEP. Since I have cascade deletes
declared in the DDL I can understand how it would get errors when trying
to delete from GROUPING etc.
I am reluctant to relax the cascade deletes in DDL. So how can I
persuade OpenJPA to delete from the bottom up or just delete from RECIPE
and be done with it? I tried the following with no difference in
outcome:
* switching the UpdateManager to 'batching-operation-order'
* paring down @OneToMany.cascade to just PERSIST.
Still welcoming any insights...
On Tue, 2011-10-18 at 11:43 -0400, Tim Watts wrote:
> 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... */
>
> }
>
>