Hibernate如何检测实体对象的脏状态?

74

它是使用某种字节码修改来修改原始类吗?

或者,也许Hibernate通过将给定的对象与先前持久化的版本进行比较来获取脏状态?

我在复杂对象的hashCode()equals()方法方面遇到了问题。如果该对象具有集合成员,则计算哈希码会非常慢,而循环引用也是一个问题。

如果Hibernate不使用hashCode()/equals()来检查脏状态,那么我想我不应该为实体对象(而不是值对象)使用equals()/hashCode(),但我也担心是否同一个运算符(==)足够了。

所以,问题是:

  1. Hibernate如何知道对象的属性是否已更改?

  2. 您建议为复杂对象重写hashCode()/equals()方法吗?如果它们包含循环引用怎么办?

    还有,

  3. 仅使用id字段的hashCode()/equals()是否足够?

6个回答

117
Hibernate采用一种名为“inspection”的策略,基本上是这样的:当从数据库中加载对象时,它的快照被保存在内存中。当会话被刷新时,Hibernate将存储的快照与当前状态进行比较。如果它们不同,则将对象标记为脏,并排队适当的SQL命令。如果对象仍然是瞬态的,则始终是脏的。
需要注意的是,Hibernate的脏检查与equals/hascode方法无关。Hibernate根本不查看这些方法(除了使用java.util.Set时,但这与脏检查无关,只涉及Collections API)。我之前提到的状态快照类似于值数组。让开发者处理框架的核心方面是一个非常糟糕的决定(说实话,开发者不应该关心脏检查)。毋庸置疑,equals/hascode可以按照您的需求以多种方式实现。我建议您阅读引用的书籍,作者在其中讨论了equals/hascode的实现策略。非常有见地的阅读。

5
我认为问题的一部分是比较具体是如何进行的?我的意思是,它是使用存储的快照对每个属性进行==检查,还是其他方法? - Mohammad Adnan
这并不完全正确。 如果在@Entity内部使用@Embeddable对象作为Collection,则应该实现equals/hashcode,因为它将在将实体刷新到数据库之前进行脏检查时使用。 请参阅PersistenceSet检查的相等性检查: https://github.com/hibernate/hibernate-orm/blob/5.1/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentSet.java#L95 - Mr Blowder

26
Hibernate默认的脏数据检查机制会将所有当前附加实体的所有映射属性与它们初始加载值进行匹配。您可以在下面的图表中更好地可视化此过程:

Default automatic dirty checking


为什么脏检查需要比较它缓存的所有实体?这样太低效了,为什么不仅比较从上次刷新传递到会话更新的对象? - jean
更新会自动进行。Session#update甚至没有JPA等效项,仅用于分离的实体。因此,您认为Hibernate如何知道实体是否已更改,而不仅仅是检查它是否已修改。字节码增强可以稍微加快速度,但如果脏检查机制成为瓶颈,则意味着您已经加载了太多实体,这才是真正的性能问题。 - Vlad Mihalcea
对象相等性无法帮助吗?用户根据业务逻辑实现equals方法。然后Hibernate可以使用这个equal方法在会话缓存中查找相应的快照? - jean
加载时间快照是一个Object[]。缓存的对象是相同的对象引用,它始终等于最新的实体状态。 - Vlad Mihalcea

8
Hibernate 对实体进行逐个字段的检查以确定其脏状态,因此 hashCode/equals 完全没有影响。实际上,Hibernate 进行的逐个字段的脏检查在性能方面可能相当昂贵。因此,它提供了像 Strategy 或 Interceptor.findDirty() 这样的接口来处理相同的问题。以下文章详细解释了这一点(以及一些优化应用的想法):http://prismoskills.appspot.com/lessons/Hibernate/Chapter_20_-_Dirty_checking.jsp

在逐个字段检查期间,如果字段的值不相同(使用“==”检查返回false),则使用equals方法检查该值是否已更改。请参见在脏检查方法调用链中使用的org.hibernate.internal.util.compare.EqualsHelper.equals(Object, Object)。 - Popeye

0

可能值得注意的是,如果您在持久化对象上使用了CustomType,则equals 用于脏检查。

该堆栈是从在Hibernate中设置我自定义数据类型MyType equals方法中断点,然后触发事务并看到equals方法被调用而来的。

equals:68, MyType (xxxxxx)
isEqual:105, CustomType (org.hibernate.type)
isSame:119, AbstractType (org.hibernate.type)
isDirty:79, AbstractType (org.hibernate.type)
isDirty:249, CustomType (org.hibernate.type)
findDirty:316, TypeHelper (org.hibernate.type)
findDirty:4622, AbstractEntityPersister (org.hibernate.persister.entity)
dirtyCheck:585, DefaultFlushEntityEventListener (org.hibernate.event.internal)
isUpdateNecessary:242, DefaultFlushEntityEventListener (org.hibernate.event.internal)
onFlushEntity:169, DefaultFlushEntityEventListener (org.hibernate.event.internal)
flushEntities:232, AbstractFlushingEventListener (org.hibernate.event.internal)
flushEverythingToExecutions:92, AbstractFlushingEventListener (org.hibernate.event.internal)
onAutoFlush:50, DefaultAutoFlushEventListener (org.hibernate.event.internal)
accept:-1, 765785095 (org.hibernate.internal.SessionImpl$$Lambda$1238)
fireEventOnEachListener:102, EventListenerGroupImpl (org.hibernate.event.service.internal)
autoFlushIfRequired:1327, SessionImpl (org.hibernate.internal)

-1

脏检查是否也涉及任何附加的AttributeConverters?如果Java对象中的值保持不变,但AttributeConverter逻辑发生了改变并导致不同的数据库值,会怎样呢?

因此,使用旧的AttributeConverter配置读取实体,使用新的AttributeConverter配置写入实体。

对于旧的和新的AttributeConverter,Java对象保持不变,但由于旧的和新的AttributeConverter配置而导致数据库值发生变化。


1
这不应该是一个答案。也许你可以把它作为一个问题来提出。 - tgdavies

-3

这很简单——当您通过ID加载/获取实体对象,然后使用setter方法设置其新字段值并在没有调用update()方法的情况下关闭会话时,Hibernate会自动更新表中的更改值而不影响其他字段。同时实体对象处于脏状态


这不是回答原帖问题的内容。 - MariuszS

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