Hibernate 实体代理初始化

11

我遇到了一个Hibernate实体无法初始化的问题。
似乎它仍然返回一个未初始化的代理...

如果我查看我的调试信息,我期望我的实体已被初始化。
但它看起来像这样:

entity = {SomeEntity_$$_jvst47c_1e@9192}"SomeEntityImpl@1f3d4adb[id=1,version=0]"
    handler = {org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer@9196}
        interfaces = {java.lang.Class[2]@9197}
        constructed = true
        persistentClass = {java.lang.Class@3605}"class SomeEntityImpl"
        getIdentifierMethod = null
        setIdentifierMethod = null
        overridesEquals = true
        componentIdType = null
        replacement = null
        entityName = {java.lang.String@9198}"SomeEntityImpl"
        id = {java.lang.Long@9199}"1"
        target = {SomeEntityImpl@9200}"SomeEntityImpl@1f3d4adb[guid=<null>,id=1,version=0]"
        initialized = true
        readOnly = true
        unwrap = false
        session = {org.hibernate.internal.SessionImpl@6878}"SessionImpl(PersistenceContext[entityKeys=[EntityKey[EntityReferenceImpl#2], EntityKey[SomeEntityImpl#1], EntityKey[...
        readOnlyBeforeAttachedToSession = null
        sessionFactoryUuid = null
        allowLoadOutsideTransaction = false
请注意,即使进行了显式初始化,我的Hibernate POJO仍然只包含一个handler。在我的调试视图中,当我展开target节点时,可以看到“真实”的属性值(未在上面显示)。
我正在做什么:
EntityReferenceImpl entityReference = findEntityReference(session);
SomeEntity entity = null;
if (entityReference != null) {
    // initialize association using a left outer join
    HibernateUtil.initialize(entityReference.getSomeEntity());
    entity = entityReference.getSomeEntity();
}
return entity;

注意HibernateUtil.initialize的调用!

SomeEntity映射:

public class SomeEntityImpl extends AbstractEntity implements SomeEntity {
    @OneToMany(mappedBy = "someEntity", fetch = FetchType.EAGER, targetEntity = EntityReferenceImpl.class, orphanRemoval = true)
    @Cascade(CascadeType.ALL)
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Set<EntityReference> entityReferences = new HashSet<>();

    @Target(EntityName.class)
    @Embedded
    private Name name;

    @Target(EntityAddress.class)
    @Embedded
    private Address address;

    ...

}

EntityReferenceImpl 映射:

public class EntityReferenceImpl extends AbstractEntity implements EntityReference {

@ManyToOne(optional = true, fetch = FetchType.LAZY, targetEntity = SomeEntityImpl.class)
@JoinColumn(name = "entity_id")
private SomeEntity someEntity;

...

}

那么副作用是什么:当POJO稍后带有更新的属性时,我仍然拥有相同的结构(如上所述),并且可以在target节点下看到更新的属性。
但是当我尝试使用session.merge()session.update()session.saveOrUpdate()更新实体时,Hibernate无法检测到“脏”属性,并且不会调用更新查询到数据库。


是否有人知道这种奇怪行为的原因?我已经尝试了所有我能想到的办法,但没有任何结果。
非常感谢任何帮助!


HibernateUtil类来自哪个包?难道你不使用Hibernate类吗? - walkeros
你解决了这个问题吗?我也遇到了类似的问题。 - Muthu Raman
3个回答

11

您的调试窗口中的实体看起来是已经正确初始化的。

当您有一些可能由Hibernate代理的实体时,这些实体被存储在代理对象中,即使在正确初始化后仍然存在。在初始化后,代理对象本身不会消失...

public class EntityReferenceImpl extends AbstractEntity implements EntityReference {

@ManyToOne(fetch = FetchType.LAZY, ...)
private SomeEntity someEntity;
...

在您的示例中,您有一个名为EntityReferenceImpl的实体,它具有指向SomeEntity实体的@ManyToOne(LAZY)关系。

当Hibernate加载EntityReferenceImpl时,它会从resultSet值中填充所有字段,但是someEntity字段设置为代理对象。

此代理对象如下所示:

class SomeEntity_$$_javassist_3 extends SomeEntity implements HibernateProxy {
  + firstname = NULL;
  + lastname = NULL;
  + age = 0;
  + handler; //of type: JavassistLazyInitializer

  getFirstname() {
    handler.invoke(..., Method thisMethod, Method proceed, args);
  }
  getLastName() {...}
}

你的SomeEntity类有(例如)方法getFirstName()等,但是javassist生成的类只是继承了你的SomeEntity,并且有一些新的字节码生成的方法,如c7getFirstName()等。

最重要的是,代理类有一个新字段:handler,类型为JavassistLazyInitializer

让我们看看JavassistLazyInitializer长成什么样:

JavassistLazyInitializer {
  + target; //holds SomeEntity object
  invoke(..., Method thisMethod, Method proceed, args) {
    if (target == null) {
      target = initialize(); // calls sessionImpl.immediateLoad
    }
    return thisMethod.invoke( target, args );
  }
}

因此,当您查看代理对象时-它具有您的字段,如firstnamelastname等。当您初始化此代理时,SomeEntity将加载到target字段中。您代理对象上的firstnamelastname字段像以前一样为空-代理不使用它们,但真正的数据在由target字段持有的SomeEntity对象中。

这就是Hibernate中代理实现的方式。

你可能会问-为什么要这样做呢?这种设计来自于多态性问题。如果SomeEntity是一个抽象的父类,有两个子类EntityAEntityB,Hibernate没有问题-someEntity字段持有扩展了SomeEntiry的代理(生成)类,但在target字段内部具有具体的EntityAEntityB

然而,这种解决方案和多态性存在一些陷阱。您的someEntity字段将是SomeEntity的实例,但永远不会是EntityA的实例或EntityB的实例。


我遇到了完全相同的问题。我的对象包含一个“处理程序”。在这个处理程序中,有一个名为“target”的属性,其中包含我的真实对象。我该如何强制他首先调用自己? - Pwnstar

3
Hibernate使用代理拦截对LAZY实体的调用。您在调试中看到的结构就是代理的样子。
您不需要调用HibernateUtil.initialize,而只需使用“fetch joins”来在单个查询中加载您感兴趣的所有实体。
如果实体附加到当前会话,则脏检查机制将自动将所有实体状态转换转换为数据库DML语句。 Session.update用于重新附加已分离的实体(在已关闭的会话中加载的实体)。 Session.merge用于将实体状态复制到已加载的实体上(如果之前未加载,则会动态加载)。
请检查是否已启用事务,否则您只能选择实体。对于persist / merge和dirty checking更新,必须使用事务(使用Java EE或Spring @Transactional支持)。

1
我同意你的评论。我已经在HQL查询中使用了“fetch join”,但效果相同。这就是为什么我尝试使用Hibernate.initialize()来确保。我正在使用Spring Transactional注释,readOnly=false。似乎Hibernate无法检测到脏属性。问题在于代理未能正确初始化(如您在调试视图中所见)。Hibernate不会检查未初始化的关联(这将是巨大的开销)。问题仍然是为什么它没有正确初始化。这是否与实现接口有关? - user2054927
根据我的经验,最好避免在实体上使用接口关联。你可以使用接口,但所有关联都应该基于实际实体,因为这样更符合实际关系的建模。 - Vlad Mihalcea

0

https://forum.hibernate.org/viewtopic.php?f=1&t=997278 这篇文章对我很有用。由于实体模型对象中的getter方法被标记为final,Javaassist无法覆盖该方法,从而改变其值。我的实体对象就像这样 -

@Entity
@Table(name = "COUNTRIES")
public class Country {
  @Id
  private Long id;
  @Column(nullable = false)
  private String name;

  private final getId() {
    return id;
  }

  private final getName() {
    return name;
  }
}

2
虽然这个链接可能回答了问题,但最好在这里包含答案的必要部分,并提供参考链接。仅有链接的答案如果链接页面发生更改,可能会变得无效。 - adiga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接