【问题标题】:Hibernate Entity proxy initializationHibernate Entity 代理初始化
【发布时间】:2014-11-27 16:47:47
【问题描述】:

我遇到了一个未初始化的 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类吗?
  • 你解决了这个问题吗?我面临着类似的问题

标签: java hibernate jpa orm proxy


【解决方案1】:

调试窗口中的实体看起来像正确初始化

当你有一些实体可能被休眠代理时,这个实体被存储在内部代理对象即使在被正确初始化之后。初始化后代理对象本身不会消失...

public class EntityReferenceImpl extends AbstractEntity implements EntityReference {

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

在您的示例中,您有 EntityReferenceImpl 实体,该实体具有 @ManyToOne(LAZY)SomeEntity 实体。

当休眠加载 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 被加载到 目标 字段中。您在代理对象上的firstnamelastname 字段和以前一样为空 - 代理不使用它们,但真实数据在SomeEntity 对象中,由target 字段持有。

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

您可能会问 - 为什么这样的解决方案?这样的设计源于多态性问题。如果SomeEntity 将是具有两个子类EntityAEntityB 的抽象父类,则休眠没有问题- someEntity 字段包含扩展SomeEntity 但具有具体EntityAEntityB 内部target 的代理(生成)类字段。

但是,这种解决方案和多态性存在一些缺陷。您的 someEntity 字段将是 instance of SomeEntity从不 instance of EntityAinstance of EntityB

【讨论】:

  • 我遇到了完全相同的问题。我的对象包含一个“处理程序”。在这个名为“目标”的属性上的处理程序中,有我的真实对象。我怎样才能强迫他首先调用自己?
【解决方案2】:

Hibernate 使用代理来拦截对 LAZY 实体的调用。您在调试中看到的结构就是代理的样子。

您无需调用HibernateUtil.initialize,只需使用“获取连接”即可加载您对单个查询感兴趣的所有实体。

如果实体附加到当前会话,dirty checking mechanism 将自动将所有entity state transitions 转换为数据库 DML 语句。

Session.update 旨在重新附加分离的实体(在已关闭的 Session 中加载的实体)。

Session.merge 用于将实体状态复制到已加载的实体(如果之前未加载,则动态加载)。

检查您是否启用了事务,否则您只能选择实体。对于持久化/合并和脏检查更新,您必须使用事务(使用 Java EE 或 Spring @Transactional 支持)。

【讨论】:

  • 我同意你们的观点。我已经在 HQL 查询中使用了“获取连接”,但效果相同。这就是为什么我尝试了 Hibernate.initialize() 只是为了确定。我正在使用带有 readOnly=false 的 Spring Transactional 注释。似乎 Hibernate 没有检测到脏属性。问题是代理没有正确初始化(就像您在调试视图中看到的那样)。 Hibernate 不检查未初始化的关联(将是一个巨大的开销)。问题仍然是为什么它没有正确初始化。它与实现接口有关吗?
  • 根据我的经验,最好避免实体上的接口关联。您可以拥有接口,但在实际实体上进行所有关联,因为无论如何这都会模拟真实的关系。
【解决方案3】:

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

【讨论】:

  • 虽然此链接可能会回答问题,但最好在此处包含essential parts of the answer 并提供链接以供参考。如果链接页面发生更改,仅链接的答案可能会失效。
猜你喜欢
  • 2011-07-12
  • 1970-01-01
  • 2012-11-09
  • 2010-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-30
  • 1970-01-01
相关资源
最近更新 更多