Interceptor getEntityName is not used. Hibernate bug?
I have applied the getEntityName method in my interceptor. I expected the method to be called by Hibernate to resolve the entity name of the (transient) object when the object is saved. However, the getEntityName method from the interceptor was not used in the following scenarios:
1) session.saveOrUpdate is called with some object name value. The object name was expected to be overwritten by the interceptor. Interestingly, when I removed the entity name from the save method call, the getEntityName interceptor was used. It looks like it is only used when save is called without an object name.
2) the objects in the collection were preserved when cascading, and the one-to-many association of the collection map used the entity-name as a reference to another class map. I believe this entity-association name was used in cascading persistence similar to scenario 1.
Can someone tell me if this is a bug or a feature in Hibernate?
Below are my mappings. Hibernate version is 3.3.1.GA.
<class name="User" table="HUBUSER">
<id name="id" type="integer">
<column name="USERID"/>
<generator class="sequence">
<param name="sequence">USERID</param>
</generator>
</id>
...
<map cascade="all" inverse="true" name="attributes">
<key on-delete="cascade" update="false">
<column name="USERID"/>
</key>
<map-key column="PROPERTYKEY" type="string"/>
<one-to-many class="HBAttribute" entity-name="USERPROPERTY"/>
</map>
...
</class>
<class discriminator-value="HBAttribute" entity-name="USERPROPERTY" name="HBAttribute" table="USERPROPERTY">
<id name="id" type="integer">
<column name="USERPROPERTYID"/>
<generator class="sequence">
<param name="sequence">USERPROPERTYID</param>
</generator>
</id>
<discriminator column="PROPERTYKEY" type="string"/>
<property insert="false" name="propertyKey" not-null="false" type="string" update="false">
<column name="PROPERTYKEY"/>
</property>
<many-to-one class="User" name="hubObject" not-null="true" update="false">
<column name="USERID"/>
</many-to-one>
<!-- Subclass for USERNAME -->
<subclass discriminator-value="USERNAME" entity-name="USERPROPERTY_USERNAME" name="HBAttributeString">
<property name="value" not-null="false" type="string">
<column name="PROPVALCHAR"/>
</property>
</subclass>
<!-- Subclass for TITL -->
<subclass discriminator-value="TITL" entity-name="USERPROPERTY_TITL" name="HBAttributeString">
<property name="value" not-null="false" type="string">
<column name="PROPVALCHAR"/>
</property>
</subclass>
<!-- Subclass for EMAIL -->
<subclass discriminator-value="EMAIL" entity-name="USERPROPERTY_EMAIL" name="HBAttributeString">
<property name="value" not-null="false" type="string">
<column length="80" name="PROPVALCHAR"/>
</property>
</subclass>
Your answers / comments on this issue will be very helpful.
-------------------- UPDATED -------------------------
I think I know what the problem is.
Hibernate does not use an interceptor to overwrite the existing object name in the save / saveOrUpdate file.
Hibernate allows you to use discriminators and entity names in a class and its subclass mappings.
The discriminator is used to determine the subclass or match the superclass when a database record is retrieved from the database. Then, as I understand it, based on the subclass mapping found, a persistent entry (object) is created in a persistent context. If the subclass has its own entity-name, that entity-name is assigned to a permanent entry. Later you update the persistent object, this entity-name is used as the key to find the subclass.
Based on the above, as I understand it, the Discriminator works in one direction when matching a database string to a Java object (persistent object). However, when it comes to mapping a java transient object to a database string, only the object class name is used to define the subclass / superclass, or a noun is provided. The name of the save method (or saveOrUpdate). So the discriminator is not used here (I wonder why? Maybe because it is not part of a persistent object?).
In my scenario, the HBAttribute class mapping got the entity name "USERPROPERTY" and all of its subclasses also got their own entity names ("USERPROPERTY_USERNAME", "USERPROPERTY_TITL" and "USERPROPERTY_EMAIL"). The reason I use entity names for subclasses is because I need to reuse java classes for subclass mappings.
The HBAttribute class has a bi-directional relationship with the User class (User → one-to-many → HBAttribute). In a user class mapping, the only way to specify a referential class mapping is to provide the entity name HBAttribute "USERPROPERTY" in the one-to-many association of the user collection. The collection has cascade = "all" and therefore all operations are expected to be cascaded into collection objects.
And this is where the problem arises. I create a transient object of the User class and then put only the created temporary HBAttribute class objects into the collection. Thus, all objects are transient. Then when I save the User object via the session.save (user) method, the objects in the attribute collection are cascaded. However, since the one-to-many association refers to the mapping of HBAttribute classes using the entity-name "USERPROPERTY", the entity name is passed to the cascading save method. The "USERPROPERTY" object is a superclass mapping, but there are subclasses with their own entity names, and Hibernate does not allow subclasses identified by entity names (this is actually seen in the Hibernate code. I thinkthat the Hibernate devs could use Disccriminator to do it in this case). This is where the Interceptor getEntityName comes in handy to tell Hibernate which entity name of the subclass should be used, however since the "USERPROPERTY" object has already been set / provided by the collection mapping, there is no way to overwrite it with the interceptor.
My idea was to store the entity names of the subclass in HBAttribute objects and use the interceptor.getEntityName to provide my entity names taken from the objects.
Below is the java.org.hibernate.impl.SessionImpl.getEntityPersister code that does NOT allow the interceptor to overwrite the original, not null entity-name.
public EntityPersister getEntityPersister(final String entityName, final Object object) {
errorIfClosed();
if (entityName==null) {
return factory.getEntityPersister( guessEntityName( object ) );
}
else {
// try block is a hack around fact that currently tuplizers are not
// given the opportunity to resolve a subclass entity name. this
// allows the (we assume custom) interceptor the ability to
// influence this decision if we were not able to based on the
// given entityName
try {
return factory.getEntityPersister( entityName )
.getSubclassEntityPersister( object, getFactory(), entityMode );
}
catch( HibernateException e ) {
try {
return getEntityPersister( null, object );
}
catch( HibernateException e2 ) {
throw e;
}
}
}
}
And this is my hacked code.
public EntityPersister getEntityPersister(final String entityName, final Object object) {
errorIfClosed();
if (entityName==null) {
return factory.getEntityPersister( guessEntityName( object ) );
}
else {
//even if the original entity-name is not null try to resolve
//the entity-name via interceptor, if the returned entity-name
// is null, then use original entity-name.
String overwrittenEntityName = interceptor.getEntityName( object );
if (overwrittenEntityName != null) {
return factory.getEntityPersister( overwrittenEntityName );
} else {
// try block is a hack around fact that currently tuplizers are not
// given the opportunity to resolve a subclass entity name. this
// allows the (we assume custom) interceptor the ability to
// influence this decision if we were not able to based on the
// given entityName
try {
return factory.getEntityPersister( entityName )
.getSubclassEntityPersister( object, getFactory(), entityMode );
}
catch( HibernateException e ) {
try {
return getEntityPersister( null, object );
}
catch( HibernateException e2 ) {
throw e;
}
}
}
}
}
Can anyone more experienced in Hibernate tell me if my change has changed and should it be suggested to hibernate?
Thanks, Anton
a source to share
Subclass name resolution was much better in Hibernate Core 3.3.2 than it was in my fix.
There is now a defineConcreteSubclassEntityName (Object entityInstance, SessionFactoryImplementor factory) method in Tuplizer that I can overwrite in my case. The Tuplizer class can be registered when displaying the class.
There is also an EntityNameResolver api that you can implement and register in your tuplizer. Therefore, there is now no need to use Entity Interceptors to resolve entity names.
a source to share
Nice catch. Thanks for the info. Be careful using this EntiyName regulator as it is a bit bugy ...: http://opensource.atlassian.com/projects/hibernate/browse/HHH-4036
a source to share