]
Christian Bauer commented on ANN-596:
-------------------------------------
Attach a runable standalone testcase if you want anyone to look at this, no Spring.
Ideally modify an existing test case in Hibernate to reproduce the problem.
ManyToMany with CascadeType ALL => StaleStateException
------------------------------------------------------
Key: ANN-596
URL:
http://opensource.atlassian.com/projects/hibernate/browse/ANN-596
Project: Hibernate Annotations
Issue Type: Bug
Affects Versions: 3.2.0.cr2, 3.3.0.ga
Environment: Hibernate 3.2.0cr2 with Annotations 3.2.0.cr2; and Hibernate 3.2
with Annotations 3.3.0ga.
Spring 2.0.4
HSQL 1.8.0 and MySQL 5.0
Reporter: Steven Morrison
Priority: Minor
Attachments: HibernateManyToManyMappingError.zip
Originally posted as bug to forums
(
http://forum.hibernate.org/posting.php?mode=editpost&p=2345494), but received no
response.
[b]Description:[/b] Hibernate appears to be end up in an inconsistent state when working
with a many-to-many mapping using cascade = ALL.
The test below shows Hibernate persisting a Root (with a Value mapped as many-to-many),
then later deleting the Root (which also deletes the Value). When attempting to delete the
Value, hibernate's "listing entities" logging suggests that the Value
instance is still persisted, but the Root deletion has removed this. StaleStateException
is then thrown.
[b]Hibernate version:[/b] 3.2
[b]Annotated classes:[/b]
[code]
@Entity
public class RootOfManyToMany
{
@Id
@GeneratedValue( generator = "hibseq" )
@GenericGenerator( name = "hibseq", strategy = "hilo" )
public Long id;
@ManyToMany( fetch = FetchType.EAGER, cascade = javax.persistence.CascadeType.ALL )
@JoinTable(
name = "MAPPING_TABLE",
joinColumns = {@JoinColumn(name = "ROOT_ID")},
inverseJoinColumns = {@JoinColumn(name = "VALUE_ID")}
)
public List< ValueOfManyToMany > values;
}
[/code]
[code]
@Entity
public class ValueOfManyToMany
{
@Id
@GeneratedValue( generator="hibseq" )
@GenericGenerator( name="hibseq", strategy="hilo" )
public Long id;
}
[/code]
Spring config:
[code]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
<!-- HSQL for local tests -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>org.hsqldb.jdbcDriver</value> </property>
<property name="username">
<value>sa</value> </property>
<property name="password">
<value></value> </property>
<property name="maxActive">
<value>2</value> </property>
<property name="poolPreparedStatements">
<value>false</value> </property>
<property name="url" value="jdbc:hsqldb:mem:test"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="hibernateProperties">
<props>
<prop
key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.show.sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
<property name="annotatedClasses">
<list>
<value>RootOfManyToMany</value>
<value>ValueOfManyToMany</value>
</list>
</property>
</bean>
<bean id="hibernateTemplate"
class="org.springframework.orm.hibernate3.HibernateTemplate">
<constructor-arg ref="sessionFactory"/>
</bean>
</beans>
[/code]
[b]Code between sessionFactory.openSession() and session.close():[/b]
[code]
public void test_manyToManyRelationship() throws Exception
{
RootOfManyToMany rootOfManyToMany = new RootOfManyToMany();
rootOfManyToMany.values = new ArrayList<ValueOfManyToMany>();
rootOfManyToMany.values.add( new ValueOfManyToMany() );
assertEquals( 0, hibernateTemplate.find( "from RootOfManyToMany"
).size() );
Long id = (Long)hibernateTemplate.save( rootOfManyToMany );
hibernateTemplate.flush();
assertEquals( 1, hibernateTemplate.find( "from RootOfManyToMany"
).size() );
RootOfManyToMany root = (RootOfManyToMany)hibernateTemplate.get(
RootOfManyToMany.class, id );
ValueOfManyToMany value = root.values.get( 0 );
hibernateTemplate.delete( root );
hibernateTemplate.flush();
hibernateTemplate.delete( value ); //Exception thrown here
}
[/code]
[b]Full stack trace of any exception that occurs:[/b]
org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException: Batch
update returned unexpected row count from update: 0 actual row count: 0 expected: 1;
nested exception is org.hibernate.StaleStateException: Batch update returned unexpected
row count from update: 0 actual row count: 0 expected: 1
Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count
from update: 0 actual row count: 0 expected: 1
at org.hibernate.jdbc.BatchingBatcher.checkRowCount(BatchingBatcher.java:93)
at org.hibernate.jdbc.BatchingBatcher.checkRowCounts(BatchingBatcher.java:79)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:58)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:242)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:235)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:144)
at
org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:297)
at
org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:993)
at
org.springframework.orm.hibernate3.HibernateAccessor.flushIfNecessary(HibernateAccessor.java:388)
at
org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:363)
at
org.springframework.orm.hibernate3.HibernateTemplate.delete(HibernateTemplate.java:774)
at
org.springframework.orm.hibernate3.HibernateTemplate.delete(HibernateTemplate.java:770)
at RootOfManyToManyTest.test_manyToManyRelationship(RootOfManyToManyTest.java:40)
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 junit.framework.TestCase.runTest(TestCase.java:154)
at junit.framework.TestCase.runBare(TestCase.java:127)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:118)
at junit.framework.TestSuite.runTest(TestSuite.java:208)
at junit.framework.TestSuite.run(TestSuite.java:203)
at
org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:128)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
[b]Name and version of the database you are using:[/b]
HSQL 1.8.0.2
[b]The generated SQL (notable log entries with <returned values>:[/b]
[code]
select rootofmany0_.id as id4_ from ROOT_OF_MANY_TO_MANY rootofmany0_
<returns empty list>
insert into ROOT_OF_MANY_TO_MANY (id) values (?)
binding '1' to parameter: 1
insert into VALUE_OF_MANY_TO_MANY (id) values (?)
binding '32768' to parameter: 1
insert into MAPPING_TABLE (ROOT_ID, VALUE_ID) values (?, ?)
binding '1' to parameter: 1
binding '32768' to parameter: 2
listing entities:
RootOfManyToMany{values=[ValueOfManyToMany#32768], id=1}
ValueOfManyToMany{id=32768}
<returns id=1>
select rootofmany0_.id as id4_ from ROOT_OF_MANY_TO_MANY rootofmany0_
result set row: 0
returning '1' as column: id4_
select values0_.ROOT_ID as ROOT1_1_, values0_.VALUE_ID as VALUE2_1_, valueofman1_.id as
id5_0_ from MAPPING_TABLE values0_ left outer join VALUE_OF_MANY_TO_MANY valueofman1_ on
values0_.VALUE_ID=valueofman1_.id where values0_.ROOT_ID=?
returning '1' as column: ROOT1_1_
returning '32768' as column: VALUE2_1_
done processing result set (1 rows)
<returns singleton list>
select rootofmany0_.id as id4_1_, values1_.ROOT_ID as ROOT1_3_, valueofman2_.id as
VALUE2_3_, valueofman2_.id as id5_0_ from ROOT_OF_MANY_TO_MANY rootofmany0_ left outer
join MAPPING_TABLE values1_ on rootofmany0_.id=values1_.ROOT_ID left outer join
VALUE_OF_MANY_TO_MANY valueofman2_ on values1_.VALUE_ID=valueofman2_.id where
rootofmany0_.id=?
binding '1' to parameter: 1
returning '1' as column: ROOT1_3_
returning '32768' as column: VALUE2_3_
done processing result set (1 rows)
<returns previously persisted root>
delete from MAPPING_TABLE where ROOT_ID=?
binding '1' to parameter: 1
delete from VALUE_OF_MANY_TO_MANY where id=?
binding '32768' to parameter: 1
delete from ROOT_OF_MANY_TO_MANY where id=?
binding '1' to parameter: 1
listing entities:
ValueOfManyToMany{id=32768}
<deletes all DB content including ValueOfManyToMany instance>
delete from VALUE_OF_MANY_TO_MANY where id=?
binding '32768' to parameter: 1
<throws exception>
[/code]
[b]Debug level Hibernate log excerpt:[/b]
Cropped to the tail of first delete:
[code]
DEBUG: Flushed: 0 insertions, 0 updates, 1 deletions to 1 objects
DEBUG: Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
DEBUG: listing entities:
DEBUG: ValueOfManyToMany{id=32768}
DEBUG: executing flush
DEBUG: registering flush begin
DEBUG: Deleting entity: [ValueOfManyToMany#32768]
DEBUG: about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
DEBUG: opening JDBC connection
DEBUG: delete from ValueOfManyToMany where id=?
DEBUG: preparing statement
DEBUG: binding '32768' to parameter: 1
DEBUG: Adding to batch
DEBUG: Executing batch size: 1
ERROR: Exception executing batch:
org.hibernate.StaleStateException: Batch update returned unexpected row count from
update: 0 actual row count: 0 expected: 1
at org.hibernate.jdbc.BatchingBatcher.checkRowCount(BatchingBatcher.java:93)
at org.hibernate.jdbc.BatchingBatcher.checkRowCounts(BatchingBatcher.java:79)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:58)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:242)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:235)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:144)
at
org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:297)
at
org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:993)
at
org.springframework.orm.hibernate3.HibernateAccessor.flushIfNecessary(HibernateAccessor.java:388)
at
org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:363)
at
org.springframework.orm.hibernate3.HibernateTemplate.delete(HibernateTemplate.java:774)
at
org.springframework.orm.hibernate3.HibernateTemplate.delete(HibernateTemplate.java:770)
at RootOfManyToManyTest.test_manyToManyRelationship(RootOfManyToManyTest.java:40)
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 junit.framework.TestCase.runTest(TestCase.java:154)
at junit.framework.TestCase.runBare(TestCase.java:127)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:118)
at junit.framework.TestSuite.runTest(TestSuite.java:208)
at junit.framework.TestSuite.run(TestSuite.java:203)
at
org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:128)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
DEBUG: about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
DEBUG: closing statement
ERROR: Could not synchronize database state with session
org.hibernate.StaleStateException: Batch update returned unexpected row count from
update: 0 actual row count: 0 expected: 1
at org.hibernate.jdbc.BatchingBatcher.checkRowCount(BatchingBatcher.java:93)
at org.hibernate.jdbc.BatchingBatcher.checkRowCounts(BatchingBatcher.java:79)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:58)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:242)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:235)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:144)
at
org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:297)
at
org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:993)
at
org.springframework.orm.hibernate3.HibernateAccessor.flushIfNecessary(HibernateAccessor.java:388)
at
org.springframework.orm.hibernate3.HibernateTemplate.execute(HibernateTemplate.java:363)
at
org.springframework.orm.hibernate3.HibernateTemplate.delete(HibernateTemplate.java:774)
at
org.springframework.orm.hibernate3.HibernateTemplate.delete(HibernateTemplate.java:770)
at RootOfManyToManyTest.test_manyToManyRelationship(RootOfManyToManyTest.java:40)
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 junit.framework.TestCase.runTest(TestCase.java:154)
at junit.framework.TestCase.runBare(TestCase.java:127)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:118)
at junit.framework.TestSuite.runTest(TestSuite.java:208)
at junit.framework.TestSuite.run(TestSuite.java:203)
at
org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:128)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
at
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
DEBUG: registering flush end
DEBUG: closing session
DEBUG: performing cleanup
DEBUG: releasing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open
ResultSets: 0, globally: 0)]
DEBUG: after transaction completion
DEBUG: transaction completed on session with on_close connection release mode; be sure to
close the session to release JDBC resources!
DEBUG: after transaction completion
[/code]
[b]Question:[/b] Is this a bug in Hibernate? My understanding is that Hibernate's
state becomes different to that of the DB, though all deletions are explicitly mentioned
in DEBUG logging.
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: