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.
Code:
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.
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
--
Regards,
Anton Anufriev