【问题标题】:Does a final method prevent Hibernate from creating a proxy for such an entity?最终方法是否会阻止 Hibernate 为此类实体创建代理?
【发布时间】:2011-09-30 07:05:40
【问题描述】:

Hibernate 使用代理来启用集合的延迟加载,甚至是单端关联。根据 Hibernate 的 (3.6.5) 参考文档(第 21.1.3 节,单端关联代理),如果这样的代理包含“任何最终方法”,则 Hibernate 无法构建这样的代理”。
我的问题是,此限制是否仅适用于持久字段的 getter/setter 或真的适用于实体类中的任何方法?那么,是否有这样的方法:

public final String toString() {
   return this.getClass().getSimpleName() + id;
}

真的阻止为该实体创建(CGLIB 或 Javassist)代理吗? 是否使用基于字段的访问或属性访问是否重要?既然 CGLIB 被 Javassist 取代了,这是否提供了更多这方面的功能?

我喜欢在我的实体层次结构中使用继承,因此需要定义一些最终方法, 例如,在基类中防止子类覆盖这些方法。

提前致谢!

【问题讨论】:

    标签: hibernate


    【解决方案1】:

    我很确定,正如参考资料所说,它适用于任何方法。实际上,在我的理解中,代理只不过是实体的子类,除了最初的实体 ID 之外没有任何状态,并且一旦初始化,它就会将每个方法调用委托给实体类的实际实例。因此,它必须重写所有方法才能

    • 必要时自行初始化
    • 将调用委托给实际的实例方法

    【讨论】:

    • cglib 和 javassist 都直接操作一个类的字节码,所以它不是常识中的代理。因此,在我看来,不需要真正拦截 每个 方法,而只需要拦截持久字段的 getter 和 setter。
    • 同意,但是我仍然看到一个例外:当使用继承并且一个实体与两个实体的基类一对一时,代理是第三个子类的实例,因为Hibernat 无法知道代理使用哪种实际类型。那岂不是要重写基类的所有方法?
    【解决方案2】:

    借助 Hibernate 邮件列表(感谢 Emmanuel Bernardt!),我能够回答我自己的问题,总结如下:
    Final 方法通常不会阻止 Hibernate 创建代理,但除非这些方法不使用实体的任何状态,否则这是非常不可取的。

    一些背景信息:Hibernate 既不使用 cglib 也不使用 Javassist 的字节码增强,因此,为了让代理延迟初始化其目标实体,它必须拦截任何可能使用该目标实体状态的方法。 现在有了这样的 final 方法就完全没问题了

    public final doSomething(String a, Integer b ) {
      // do complicated stuff using only a and b (no instance members accessed!)
    }
    

    但只要此方法直接或通过另一个实例方法使用任何持久性字段,就会绕过代理,从而导致意外行为。

    作为旁注,这与您不应直接访问其他实例的字段的原因相同,例如在实体 equals 方法中:

    // XXX bad code!
    public boolean equals(Object o) {
      if (this == o) return true;
      if (!(o instanceof Profile)) return false;
      Profile profile = (Profile) o;
      // XXX this bypasses a possible proxy, use profile.getName() instead!
      return (name == null ? profile.name == null : name.equals(profile.name));
    }
    

    【讨论】:

    • 我认为“只要此方法直接或通过其他实例方法使用任何持久性字段,就会绕过代理” 有点不正确。在 final 方法中,this 必然是代理,因此一旦调用了非 final 实例方法,目标就会被初始化。当然,直接在 final 方法中访问任何字段都会访问代理上从未初始化的字段,所以这总是一个问题。
    • 澄清@MichałPolitowski 刚才所说的:不访问状态的最终方法很好。仅通过另一种方法访问状态的最终方法也可以。但是如果它使用直接实例字段,最终方法将失败(并且可能会静默失败)。有3种方法可以解决这个问题。首先是不要将方法声明为final。其次是不要直接从 final 方法中访问字段。第三是通过设置lazy="false"来禁用代理(出于性能原因不推荐)。
    • 如果您使用 Intellij IDEA,请投票创建解决此问题的检查:youtrack.jetbrains.com/issue/IDEA-128132
    • 这可以用作资产。我正在使用它来获取实体的 id 而无需加载其字段。
    • @Robin:我终于为自己创建了一个 Intellij IDEA 插件来解决这个问题。请在下面查看我的答案,并附上链接。
    【解决方案3】:

    受这个问题的启发,我为 Intellij IDEA 创建了一个插件,可以解决这个问题。这里是:

    https://plugins.jetbrains.com/plugin/7866

    简单的描述是这样的:

    Hibernate 在某些情况下会静默失败,从而导致错误 很难追踪。这个插件有助于查找和修复 其中一些问题。在设置 > 检查 > 休眠下 检查它增加了以下检查: • 持久类是最终的; • 持久类的最终方法使用直接字段访问。

    ===============================================

    附注:

    正如@BartvanHeukelom 在 cmets 中所说,最终方法不能被代理的事实可以用作资产:那么您可以 从 getter 获取实体的 id,无需初始化代理并加载其字段。这是我使用的代码:

    @SuppressWarnings ("AccessingFieldFromAFinalMethodOfPersistedClass")
    public final Id getId() {
        if (this instanceof HibernateProxy) return (Id)((HibernateProxy)this).getHibernateLazyInitializer().getIdentifier();
        else return id;
        }
    

    请注意@SuppressWarnings。仅当您将 IntelliJ IDEA 与我的插件一起使用时才需要这样做。

    【讨论】:

      猜你喜欢
      • 2019-12-24
      • 2016-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-15
      • 1970-01-01
      相关资源
      最近更新 更多