Hello,
I'm unable to swap elements of unidirectional list supported with additional database table.
Model
Code:
@Entity
public class Unit {
private Long id;
private List<Action> actions = new LinkedList<Action>();
@Id
@GeneratedValue
public Long getId() {
return id;
}
public void setId( Long id ) {
this.id = id;
}
@OneToMany(cascade = CascadeType.ALL)
@IndexColumn(name = "idx")
@JoinTable(name = "UnitActions", joinColumns = { @JoinColumn(name = "unit_id") }, inverseJoinColumns = @JoinColumn(name = "action_id"))
@Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
public List<Action> getActions() {
return actions;
}
public void setActions( List<Action> actions ) {
this.actions = actions;
}
}
Code:
@javax.persistence.Entity
public class Action {
private Long id;
private String name;
public Action() {
}
public Action( String name ) {
this.name = name;
}
@NotNull
public String getName() {
return name;
}
public void setName( String name ) {
this.name = name;
}
@Id
@GeneratedValue
public Long getId() {
return id;
}
public void setId( Long id ) {
this.id = id;
}
}
Code between sessionFactory.openSession() and session.close():Code:
Unit unit = (Unit) session.load( Unit.class, id );
// HERE IS THE PROBLEM
Collections.swap( unit.getActions(), 0, 1 );
session.persist( unit );
SQL statements for MySQLCode:
DEBUG (2007-06-14) 22:32.39:906 [SQL] update UnitActions set action_id=? where unit_id=? and idx=?
DEBUG (2007-06-14) 22:32.39:906 [AbstractBatcher] preparing statement
DEBUG (2007-06-14) 22:32.39:906 [LongType] binding '36' to parameter: 1
DEBUG (2007-06-14) 22:32.39:906 [LongType] binding '18' to parameter: 2
DEBUG (2007-06-14) 22:32.39:906 [IntegerType] binding '0' to parameter: 3
DEBUG (2007-06-14) 22:32.39:906 [LongType] binding '35' to parameter: 1
DEBUG (2007-06-14) 22:32.39:906 [LongType] binding '18' to parameter: 2
DEBUG (2007-06-14) 22:32.39:906 [IntegerType] binding '1' to parameter: 3
As you can see, these two updates cannot work without violating database constrains.
StacktraceCode:
ERROR (2007-06-14) 22:32.39:937 [AbstractFlushingEventListener] Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:71)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:253)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:237)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:144)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1000)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:338)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
at com.mobilebox.hibtest1.FirstTestCase.testOne(FirstTestCase.java:73)
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 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:130)
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)
Caused by: java.sql.BatchUpdateException: Duplicate entry '35' for key 2
at com.mysql.jdbc.ServerPreparedStatement.executeBatch(ServerPreparedStatement.java:657)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:48)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:246)
... 26 more
What I can see is tha Hibernate tries to swap list elements by switching action_ids while keeping idx values of particular rows.
This leads to constraint violations as the first update causes a single action_id to occur in two rows.
Sample Maven project to illustrate the problem:http://mefiu.wlkp.org/test/hibtest1.zipFile size 10kB, run with:
Code:
mvn clean install
Wouldn't it be possible to switch idx values and leave action_ids as they are ?
Rhis would require the Hibernate list to be aware of the fact that the element has been removed and inserted at different list position.
Any current solution requires deep invasion into DAOs code as you are unable to switch elements without removing first, flushing, repositioning second, adding a FRESH new one into second's position. This may of course yield big problems if deleting a child has cascade effects.
Could you please confirm this error with my sample source project?
Hibernate version:
core: 3.2.4.sp1 + annotations 3.3.0.ga
Name and version of the database:
MySQL 5.0.3
Test case for HSQLDB 1.8.0.7