Hibernate:代理实现的细节(延迟提取)

时间:2015-09-18 20:18:33

标签: hibernate proxy fetch lazy-evaluation

我已经了解到Hibernate在为您提供查询结果时不会返回实际实体类的实例,而是会返回一个'代理'从您的实际实体的类中动态细分的实例。我理解这种行为的原因,因为它允许实现延迟初始化。但是,我对这些代理类的实现细节还有一些问题没有答案:

  1. 当我使用getter时,是否只会加载延迟获取的字段?如果我在equalshashCode方法中使用该字段会怎样?当我之前没有调用此字段的getter时,这些方法的执行会导致NullPointerException吗?

  2. Hibernate在触发初始化时如何初始化字段?它是否执行我在实体类中定义的字段的setter方法,还是通过反射或类似的方式将值直接赋值给变量?

1 个答案:

答案 0 :(得分:1)

首先,两条规则:

  1. 代理将所有非最终方法调用委托给目标实例,但如果在映射中定义了id的属性访问权限,则获取id的方法除外。
  2. 代理对象实例从不初始化,目标实例已初始化。
  3. 1)假设您调用a.equals(b),其中ab都是同一实体的代理。并且假设equals方法是这样实现的:

    public boolean equals(Object other) {
      ...
      if (this.someField.equals(other.someField)) {
        ...
      }
      ...
    }
    

    equals a方法被委托给目标实例,强制其完全初始化。因此,您对a实例中的字段是安全的(您可以直接使用它们)。

    但是,直接访问b实例(other.someField)中的字段永远不会有效。是否初始化b并不重要,代理实例永远不会被初始化,只有目标实例。因此,someField实例中null始终为b

    正确的实现是至少为other实例使用getter:

    this.someField.equals(other.getSomeField())
    

    或保持一致:

    this.getSomeField().equals(other.getSomeField())
    

    对于final方法,情况有所不同 - Hibernate无法覆盖它们以将调用委托给目标。因此,如果上一个示例中的equals方法为final,则在访问NullPointerException时会获得this.someField

    所有这一切都可以通过将Hibernate配置为使用字节码检测而不是代理来避免,但这有其自身的缺陷并且未被广泛采用。

    2)同样,它永远不会初始化代理实例本身。当涉及目标实例初始化时,它取决于是否在映射中定义了字段或属性访问。在这两种情况下都使用反射(在字段访问的情况下直接将值分配给字段,或者在属性访问的情况下调用setter)。