I'm experiencing an unique constraint violation when updating a many-to-many association if the
hashCode() method of the entities is based on fields which have a different value than in the database.
What I try to do in two subsequent web requests (that means the session is closed in-between):
Store two objects with the following data:
Code:
Parent:
idField1 = 1
dataField1 = dataParent
systemId = 3
Set<Child> = {child} (many-to-many association)
Code:
Child:
idField2 = 1
dataField2 = dataChild
systemId = 3
The tables PARENT, CHILD and PARENTS2CHILD are populated correctly.
Now I try to merge and flush the previously saved objects with the following data:
Code:
Parent:
idField1 = 1
dataField1 = dataParent
systemId = 4 //this value has changed
Set<Child> = {child} (many-to-many association)
Code:
Child:
idField2 = 1
dataField2 = dataChild
systemId = 4 //this value has changed
The tables PARENT, CHILD are updated correctly. But Hibernate tries to insert a new row in the relation table PARENTS2CHILD and an Unique Constraint Violation occurs.
The reason is that the systemId is part of the hashCode generation for the Parent and Child classes (see below). This behavior is generated by AndroMDA out of our UML model automatically. So the entities are first added to a Set internally by Hibernate after the initial load from the database (with systemId=3) and during the check for necessary updates the entities with systemId=4 are considered to be missing (since they can not be found in the internal Set because their hashCode has changed).
I know that this behavior violates the contract of the Set specification, but from the view of a Hibernate user one can not know that the implementation of a
by value equality/hashCode (as mentioned in the book
Java Persistence with Hibernate) leads to the described problem. In this book, the
by value equality/hashCode is not recommended, but also not completely forbidden, but when using many-to-many associations it seems to be problematic.
In
http://www.hibernate.org/109.html and in other discussions I found very few comments on
by value equality/hashCode so I think it is not used widely, am I right?
In our case, I think we will change the generation process for our Entity classes in a way that the equals and hashCode methods only depend on the database identifier, but I would be interested in your opinion about using
by value equality/hashCode generated by default for all Entities in a class hierarchy which have no specified identifier(like in this case GeneralEntity, see below).
Cheers,
Jürgen
Hibernate version: Experienced with 3.2.4.sp1 and 3.2.6.GA
Mapping documents:GeneralEntity:
Code:
@javax.persistence.Column(name = "SYSTEM_ID", insertable = true, updatable = true)
public java.lang.String getSystemId()
...
public int hashCode()
{
int hashCode = 0;
hashCode = 29 * hashCode + (getSystemId() == null ? 0 : getSystemId().hashCode());
return hashCode;
}
Parent extends GeneralEntity:
Code:
@javax.persistence.Id
@javax.persistence.Column(name = "ID_FELD1", nullable = false, insertable = true, updatable = true)
public java.lang.String getIdFeld1()
...
@javax.persistence.ManyToMany(cascade = {javax.persistence.CascadeType.ALL})
@javax.persistence.JoinTable
(
name = "PARENTS2CHILD",
joinColumns = {@javax.persistence.JoinColumn(name = "PARENTS_ID_FELD1_FK", referencedColumnName = "ID_FELD1")},
inverseJoinColumns = {@javax.persistence.JoinColumn(name = "CHILD_ID_FELD2_FK", referencedColumnName = "ID_FELD2")}
)
public java.util.Set<com.trinitec.samples.ejb3xfire.Child> getChild()
....
public int hashCode()
{
int hashCode = super.hashCode();
hashCode = 29 * hashCode + (getIdFeld1() == null ? 0 : getIdFeld1().hashCode());
return hashCode;
}
Child extends GeneralEntity:
Code:
@javax.persistence.Id
@javax.persistence.Column(name = "ID_FELD2", nullable = false, insertable = true, updatable = true)
public java.lang.String getIdFeld2()
...
public int hashCode()
{
int hashCode = super.hashCode();
hashCode = 29 * hashCode + (getIdFeld2() == null ? 0 : getIdFeld2().hashCode());
return hashCode;
}
Code between sessionFactory.openSession() and session.close():Code:
emanager is injected by container:
...
emanager.merge(parent);
emanager.flush();
...
Full stack trace of any exception that occurs:Code:
2008-06-25 17:26:25,703 DEBUG [org.hibernate.util.JDBCExceptionReporter] Could not execute JDBC batch update [insert into PARENTS2CHILD (PARENTS_ID_FELD1_FK, CHILD_ID_FELD2_FK) values (?, ?)]
java.sql.BatchUpdateException: ORA-00001: unique constraint (TSTJA.SYS_C00342166) violated
at oracle.jdbc.driver.DatabaseError.throwBatchUpdateException(DatabaseError.java:343)
at oracle.jdbc.driver.OraclePreparedStatement.executeBatch(OraclePreparedStatement.java:10656)
at org.jboss.resource.adapter.jdbc.WrappedStatement.executeBatch(WrappedStatement.java:519)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:48)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:246)
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)
...
Name and version of the database you are using:Oracle 10.1.02
The generated SQL (show_sql=true):Code:
...
2008-06-25 17:26:25,656 INFO [STDOUT] Hibernate: select parent0_.ID_FELD1 as ID1_3_1_, parent0_.SYSTEM_ID as SYSTEM2_3_1_, parent0_.DATA_FELD1 as DATA3_3_1_, child1_.PARENTS_ID_FELD1_FK as PARENTS1_3_, child2_.ID_FELD2 as CHILD2_3_, child2_.ID_FELD2 as ID1_0_0_, child2_.SYSTEM_ID as SYSTEM2_0_0_, child2_.DATA_FELD2 as DATA3_0_0_ from PARENT parent0_ left outer join PARENTS2CHILD child1_ on parent0_.ID_FELD1=child1_.PARENTS_ID_FELD1_FK left outer join CHILD child2_ on child1_.CHILD_ID_FELD2_FK=child2_.ID_FELD2 where parent0_.ID_FELD1=?
...
2008-06-25 17:26:25,671 INFO [STDOUT] Hibernate: update CHILD set SYSTEM_ID=?, DATA_FELD2=? where ID_FELD2=?
...
2008-06-25 17:26:25,687 INFO [STDOUT] Hibernate: update PARENT set SYSTEM_ID=?, DATA_FELD1=? where ID_FELD1=?
...
2008-06-25 17:26:25,687 INFO [STDOUT] Hibernate: insert into PARENTS2CHILD (PARENTS_ID_FELD1_FK, CHILD_ID_FELD2_FK) values (?, ?)
...
Debug level Hibernate log excerpt:Code:
2008-06-25 17:26:25,687 DEBUG [org.hibernate.jdbc.ConnectionManager] skipping aggressive-release due to flush cycle
2008-06-25 17:26:25,687 DEBUG [org.hibernate.persister.collection.AbstractCollectionPersister] Deleting rows of collection: [com.trinitec.samples.ejb3xfire.Parent.child#1]
2008-06-25 17:26:25,687 DEBUG [org.hibernate.persister.collection.AbstractCollectionPersister] no rows to delete
2008-06-25 17:26:25,687 DEBUG [org.hibernate.persister.collection.AbstractCollectionPersister] Inserting rows of collection: [com.trinitec.samples.ejb3xfire.Parent.child#1]
2008-06-25 17:26:25,687 DEBUG [org.hibernate.jdbc.AbstractBatcher] about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
2008-06-25 17:26:25,687 DEBUG [org.hibernate.SQL] insert into PARENTS2CHILD (PARENTS_ID_FELD1_FK, CHILD_ID_FELD2_FK) values (?, ?)