未使用Interceptor的getEntityName。 Hibernate中的错误?

时间:2009-05-21 04:03:13

标签: hibernate

我在拦截器中实现了getEntityName方法。当我保存对象时,我期待Hibernate调用该方法来解析(瞬态)对象的实体名称。但是,拦截器中的方法getEntityName未在以下方案中使用:

1)使用某个实体名称值调用session.saveOrUpdate。预计实体名称将被拦截器覆盖。     有趣的是,当我从save方法调用中删除实体名称时,使用了拦截器的getEntityName。看起来它只在没有实体名称的情况下调用save时使用。

2)集合中的对象保存在级联中,集合映射的一对多关联使用entity-name作为对另一个类映射的引用。我相信这个关联的实体名称在场景1中用于级联保存。

有人可以告诉我它是否是Hibernate中的错误或功能?

以下是我的映射。 Hibernate的版本是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>

你对这个问题的回答/评论非常有用。

--------------------修订------------------------- < / p>

我想我知道问题所在。

Hibernate不使用拦截器覆盖save / saveOrUpdate上的现有实体名称。

Hibernate允许在类及其子类映射中使用鉴别器和实体名称。

Discriminator用于在从数据库中提取数据库记录时标识子类或超类映射。 然后据我所知,基于找到的子类映射,在持久化上下文中创建持久性条目(对象)。 如果子类有自己的实体名称,则将该实体名称分配给持久性条目。以后什么时候 您更新持久对象,此实体名称用作查找子类映射的键。

基于以上所述,据我所知,当将数据库行映射到java对象(持久对象)时,Discriminator在一个方向上工作。 但是,当涉及将瞬态java对象映射到数据库行时,仅使用对象的类名或提供给save(或saveOrUpdate)方法entity-name来查找子类/超类映射。所以这里没有使用判别器(我想知道为什么?可能是因为它不是持久对象的一部分?)。

在我的场景中,HBAttribute类映射具有“USERPROPERTY”实体名称,并且其所有子类也都有自己的实体名称(“USERPROPERTY_USERNAME”,“USERPROPERTY_TITL”和“USERPROPERTY_EMAIL”)。 我为子类使用实体名称的原因是我需要重用子类映射的java类。

HBAttribute类与User类(User - &gt; one-to-many - &gt; HBAttribute)具有双向关联。 在User类映射中,指定引用类映射的唯一方法是在User的集合的一对多关联中提供HBAttribute的“USERPROPERTY”实体名称。该集合已获得cascade =“all”,因此所有操作都应该级联到集合的对象。

问题来了。我创建User类的临时对象,然后将刚创建的HBAttribute类的临时对象放入集合中。所以所有对象都是瞬态的。然后,当我通过session.save(user)方法保存User对象时,属性集合中的对象将保存在级联中。但是,因为一对多关联使用“USERPROPERTY”实体名称引用HBAttribute类映射,所以实体名称将传递给级联保存方法。 “USERPROPERTY”实体是一个超类映射,但是有子类有自己的实体名称,而Hibernate不解析实体名称标识的子类(这实际上是在Hibernate的代码中注意到的。我猜Hibernate开发人员可以使用Discriminator在这种情况下这样做)。这里Interceptor的getEntityName会派上用场告诉Hibernate应该使用哪个子类entity-name,但是由于集合映射已经设置/提供了“USERPROPERTY”实体,所以无法用拦截器覆盖它。

我的想法是将子类实体名称存储在HBAttribute对象中,并使用interceptor.getEntityName来提供从对象中获取的实体名称。

以下是java.org.hibernate.impl.SessionImpl.getEntityPersister的代码,它不允许拦截器覆盖原始非null实体名称。

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;
            }
        }
    }
}

这是我的黑客代码。

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;
                }
            }
        }  
    }
}

在Hibernate中有更多经验的人能否告诉我我的更改是否正常并且应该被提议包含在Hibernate中?

谢谢, 安东

2 个答案:

答案 0 :(得分:0)

在Hibernate Core 3.3.2中,子类实体名称的解析以比我修复中更好的方式实现。

现在在Tuplizer中有一个方法determineConcreteSubclassEntityName(Object entityInstance,SessionFactoryImplementor factory),我可以在我的情况下覆盖它。 Tuplizer类可以在类映射中注册。

还有EntityNameResolver api,你可以在你的tuplizer中实现和注册。 所以现在不需要使用实体拦截器来解析实体名称。

答案 1 :(得分:0)

很好听。谢谢(你的)信息。 使用这个EntiyName解析器要小心,因为它有点笨拙...: http://opensource.atlassian.com/projects/hibernate/browse/HHH-4036