【问题标题】:Interceptor's getEntityName is not used. Bug in Hibernate?不使用拦截器的 getEntityName。休眠中的错误?
【发布时间】:2009-05-21 04:03:12
【问题描述】:

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

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

2) 集合中的对象以级联方式保存,集合映射的一对多关联使用实体名称作为对另一个类映射的引用。我相信这个关联的实体名称被用于级联保存,与场景 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>

您对这个问题的回答/cmets 将非常受欢迎。

--------更新-------------

我想我知道问题出在哪里了。

Hibernate 不使用拦截器在保存/保存或更新时覆盖现有实体名称。

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

鉴别器用于在从数据库中获取数据库记录时识别子类或超类映射。 然后据我了解,基于找到的子类映射,在持久上下文中创建持久条目(对象)。 如果子类有自己的实体名称,则该实体名称将分配给持久条目。以后无论何时 您更新持久对象,此实体名称用作查找子类映射的键。

基于上述,据我了解,当将数据库行映射到 java 对象(持久对象)时,鉴别器在一个方向上工作。 然而,当涉及到临时 java 对象到数据库行的映射时,只有对象的类名或提供给保存(或 saveOrUpdate)方法的实体名用于查找子类/超类映射。所以这里没有使用Discriminator(我想知道为什么?可能是因为它不是持久对象的一部分?)。

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

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

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

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

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

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 中?

谢谢, 安东

【问题讨论】:

    标签: hibernate


    【解决方案1】:

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

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

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

    【讨论】:

      【解决方案2】:

      不错的收获。谢谢(你的)信息。 小心使用这个 EntiyName 解析器,因为它有点问题......: http://opensource.atlassian.com/projects/hibernate/browse/HHH-4036

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-25
        • 1970-01-01
        • 1970-01-01
        • 2014-02-02
        • 2019-06-18
        相关资源
        最近更新 更多