【发布时间】: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