tags:

views:

590

answers:

2

Hi All,

I implemented getEntityName method in my interceptor. I was expecting that the method will be called by Hibernate to resolve entity name of a (transient) object when I save the object. However, the method getEntityName from the interceptor was not used in the following scenarios:

1) session.saveOrUpdate was called with some entity name value. It was expected that the entity name will be overwritten by the interceptor. The interesting thing it that when I removed the entity name from save method call, the interceptor's getEntityName was used. Looks like it is only used when save is called without entity name.

2) objects in a collection were saved on cascading and the one-to-many association of the collection mapping used entity-name as reference to another class mapping. I beleive that this association's entity-name was used in cascading save similarly as in scenario 1.

Can someone tell me whether it is a bug or feature in Hibernate?

Below are my mappings. Version of Hibernate 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 would be very much appriciated.

--------------------UPDATED-------------------------

I guess I know what the problem is.

Hibernate does not use an interceptor to overwrite existing entity name on save/saveOrUpdate.

Hibernate allows to use discriminators and entity-names in a class and its subclasses mappings.

Discriminator is used to identify a subclass or super class mapping when a database record is fetched from a database. Then as far as I understand, based on the found subclass mapping a persistent entry (object) is created in persistent context. If the subclass has got its own entity-name this entity-name gets assigned to the persistent entry. Later whenever you update the persistent object this entity-name is used as a key to find the subclass mapping.

Based on the above, as far as I understand, Discriminator works in one direction, when mapping database row to a java object (persistent object). However when it comes to mapping of a transient java object to a database row, only class name of the object or provided to the save (or saveOrUpdate) method entity-name is used to find subclass/super class mapping. So Discriminator here is not used (I wonder why? May be because the it is not a part of the persistent object?).

In my scenario HBAttribute class mapping has got the "USERPROPERTY" entity-name and all its subclasses also have got their own entity-names ("USERPROPERTY_USERNAME", "USERPROPERTY_TITL" and "USERPROPERTY_EMAIL"). The reason why I use entity-names for the subclasses is that I need to reuse the java classes of the subclass mappings.

The HBAttribute class has got bidirectional association with the User class (User -> one-to-many -> HBAttribute) . In User class mapping the only way to specify referenced class mapping is to provide HBAttribute's "USERPROPERTY" entity-name in one-to-many association of the User's collection. The collection has got cascade="all" and hence all operations are expected to be cascaded to the collection's objects.

And here the problem comes. I create transient object of User class and then put just created transient objects of HBAttribute class to the collection. So all objects are transient. Then when I save the User object via session.save(user) method, the objects in the attributes collection get saved on cascade. However, because the one-to-many association refers HBAttribute class mapping using "USERPROPERTY" entity-name, the entity-name gets passed to the cascading save method. The "USERPROPERTY" entity is a super class mapping, but there are subclasses with their own entity-names and Hibernate does not resolve subclasses identified by entity-names (this is actually noticed in the Hibernate's code. I guess that Hibernate developers could use Discriminator to do that in this case). Here Interceptor's getEntityName would come in handy to tell Hibernate what subclass entity-name should be used, however as the "USERPROPERTY" entity has already been set/provided by the collection mapping there is no way to overwrite it with the interceptor.

My idea was to store subclass entity-names in the HBAttribute objects and use the interceptor.getEntityName to provide my the entity-names taken from the objects.

Below is the code of java.org.hibernate.impl.SessionImpl.getEntityPersister which does NOT allow an interceptor to overwrite 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;
                }
            }
        }  
    }
}

Could someone more experienced in Hibernate tell me whether my change is OK and should be proposed to be included in Hibernate?

Thanks, Anton

A: 

In Hibernate Core 3.3.2 the resolution of a subclass entity-names has been implemented in a much better way than I had in my fix.

Now in a Tuplizer there is a method determineConcreteSubclassEntityName(Object entityInstance, SessionFactoryImplementor factory) which I can overwrite in my case. The Tuplizer class can be registered whithin a class mapping.

There is also EntityNameResolver api which you can implement and register in your tuplizer. So now there is no need to use Entity Interceptors to resolve entity names.

Anton
A: 

Hi,

Nice catch. Thanks for the info. Be careful using this EntiyName resolver as it's a bit bugy...: http://opensource.atlassian.com/projects/hibernate/browse/HHH-4036

edbras