我已经了解到Hibernate在为您提供查询结果时不会返回实际实体类的实例,而是会返回一个'代理'从您的实际实体的类中动态细分的实例。我理解这种行为的原因,因为它允许实现延迟初始化。但是,我对这些代理类的实现细节还有一些问题没有答案:
当我使用getter时,是否只会加载延迟获取的字段?如果我在equals
或hashCode
方法中使用该字段会怎样?当我之前没有调用此字段的getter时,这些方法的执行会导致NullPointerException
吗?
Hibernate在触发初始化时如何初始化字段?它是否执行我在实体类中定义的字段的setter方法,还是通过反射或类似的方式将值直接赋值给变量?
答案 0 :(得分:1)
首先,两条规则:
1)假设您调用a.equals(b)
,其中a
和b
都是同一实体的代理。并且假设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)。