Using Hibernate 3.3.2.GA.
Hello:
When attempting to perform a cascading merge using the mapping below I encountered a NonUniqueObjectException on one of my child collections (DeterminantTypeMember) that has a key-many-to-one mapping to the parent. The relevant mappings and code are below, but the distinguishing features of this entity are that it consists only of key fields, and that both the local and foreign key mappings use a custom UserType.
As best I can tell the cause of the error is that DefaultMergeEventListener is not populating the id fields for the copy it is storing in the copyCache map (line 291) and passing on to AbstractSaveEventListener. This in turn seems to be caused by the method EntityMetamodel.getIdentifierProperty().isVirtual() returning true when called from AbstractEntityPersister (line 3260).
This behavior seems intentional and I don't claim to understand it, but the problem is when the empty id copy gets attached to the session, then a second instance with a different key comes along it also gets an empty id copy and now the session thinks there are 2 copies of the same (empty) identity when in fact both of my passed instances had distinct, populated id values.
What is it that causes EntityMetamodel.getIdentifierProperty().isVirtual() to return true, and why is an object copy with an empty ID being attached to the session? I'm assuming that the population of the ID is simply being deferred for some reason, but shouldn't Hibernate have to finish populating the ID before it can start trying to process other instances of the same class?
Can somebody please help me figure out what I am missing?
Here is what I believe is the relevant portion of my mapping (let me know if you need to see more):
Code:
<class name="com.etse.model.configuration.Determinant" table="DETERMINANT">
<id name="name">
<column name="DETERMINANTNAME" length="100"/>
</id>
<property name="primary" type="yes_no">
<column name="PRIMARY"/>
</property>
<set name="determinantDefinitions">
<key column="DETERMINANTNAME"/>
<one-to-many class="com.etse.model.configuration.DeterminantDefinition"/>
</set>
</class>
<class name="com.etse.model.configuration.DeterminantDefinition" table="DETERMINANTDEFINITION">
<composite-id>
<key-many-to-one name="determinant">
<column name="DETERMINANTNAME"/>
</key-many-to-one>
<key-property name="effectiveDate" type="com.etse.persistence.hibernate.UTCJodaDateTimeType">
<column name="EFFECTIVEDATE" sql-type="timestamp"/>
</key-property>
<key-property name="executionEffectiveDate" type="com.etse.persistence.hibernate.UTCJodaDateTimeType">
<column name="EXECUTIONEFFECTIVEDATE" sql-type="timestamp"/>
</key-property>
</composite-id>
<set name="determinantTypeMembers">
<key>
<column name="DETERMINANTNAME"/>
<column name="EFFECTIVEDATE"/>
<column name="EXECUTIONEFFECTIVEDATE"/>
</key>
<one-to-many class="com.etse.model.configuration.DeterminantTypeMember"/>
</set>
</class>
<class name="com.etse.model.configuration.DeterminantTypeMember" table="DETERMINANTTYPEMEMBER">
<composite-id>
<key-many-to-one name="determinantDefinition">
<column name="DETERMINANTNAME"/>
<column name="EFFECTIVEDATE"/>
<column name="EXECUTIONEFFECTIVEDATE"/>
</key-many-to-one>
<key-property name="determinantType" type="com.etse.persistence.hibernate.StringEnumType">
<column name="DETERMINANTTYPE" length="50"/>
</key-property>
</composite-id>
</class>
And the code:
Code:
public class DeterminantTypeMember implements Serializable {
private DeterminantDefinition determinantDefinition;
private DeterminantType determinantType;
public DeterminantTypeMember(){}
public DeterminantTypeMember(DeterminantDefinition determinantDefinition,
DeterminantType determinantType) {
this.determinantDefinition = determinantDefinition;
this.determinantType = determinantType;
}
public String toString(){
if (this.determinantType != null){
return this.determinantType.toString();
}else{
return "null";
}
}
public DeterminantDefinition getDeterminantDefinition() {
return determinantDefinition;
}
public void setDeterminantDefinition(DeterminantDefinition determinantDefinition) {
this.determinantDefinition = determinantDefinition;
}
public DeterminantType getDeterminantType() {
return determinantType;
}
public void setDeterminantType(DeterminantType determinantType) {
this.determinantType = determinantType;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime
* result
+ ((determinantDefinition == null) ? 0 : determinantDefinition
.hashCode());
result = prime * result
+ ((determinantType == null) ? 0 : determinantType.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DeterminantTypeMember other = (DeterminantTypeMember) obj;
if (determinantDefinition == null) {
if (other.determinantDefinition != null)
return false;
} else if (!determinantDefinition.equals(other.determinantDefinition))
return false;
if (determinantType == null) {
if (other.determinantType != null)
return false;
} else if (!determinantType.equals(other.determinantType))
return false;
return true;
}
}