如何判断当前会话是否脏



当且仅当数据库发生更改时,我想发布一个事件。我在@Transaction is Spring上下文下运行,我得到了这个检查:

Session session = entityManager.unwrap(Session.class);
session.isDirty();

对于新的(瞬态)对象,这似乎失败了:

@Transactional
public Entity save(Entity newEntity) {
Entity entity = entityRepository.save(newEntity);
Session session = entityManager.unwrap(Session.class);
session.isDirty(); // <-- returns `false` ):
return entity;
}

根据这里的答案https://stackoverflow.com/a/5268617/672689我希望它能起作用,并回归真实。

我错过了什么?

UPDATE
考虑到@fladdimir的回答,尽管这个函数是在事务上下文中调用的,但我确实在函数上添加了@Transactional(来自org.springframework.transaction.annotation)。但我仍然会遇到同样的行为。isDirty返回false。

此外,正如预期的那样,当程序保持在session.isDirty()行的断点上时,新实体不会显示在DB上。

UPDATE_2
在调用回购保存之前,我还尝试更改会话刷新模式,但没有任何效果:

session.setFlushMode(FlushModeType.COMMIT);
session.setHibernateFlushMode(FlushMode.MANUAL);

首先,Session.isDirty()的含义与我所理解的不同。它告诉当前会话是否正在内存中进行尚未发送到DB的查询。虽然我认为它告诉事务是否有更改的查询。保存新实体时,即使在事务中,插入查询也必须发送到DB才能获得新实体id,因此isDirty()在它之后总是false。

因此,我最终创建了一个类来扩展SessionImpl并保持会话的change状态,在持久化和合并调用(hibernate使用的函数)时更新它

这就是我写的课:

import org.hibernate.HibernateException;
import org.hibernate.internal.SessionCreationOptions;
import org.hibernate.internal.SessionFactoryImpl;
import org.hibernate.internal.SessionImpl;
public class CustomSession extends SessionImpl {
private boolean changed;
public CustomSession(SessionFactoryImpl factory, SessionCreationOptions options) {
super(factory, options);
changed = false;
}
@Override
public void persist(Object object) throws HibernateException {
super.persist(object);
changed = true;
}
@Override
public void flush() throws HibernateException {
changed = changed || isDirty();
super.flush();        
}
public boolean isChanged() {
return changed || isDirty();
}
}

为了使用它,我不得不:

  • 扩展SessionFactoryImpl.SessionBuilderImpl以覆盖openSession函数并返回我的CustomSession
  • 扩展SessionFactoryImpl以覆盖withOptions函数以返回扩展的SessionFactoryImpl.SessionBuilderImpl
  • 扩展AbstractDelegatingSessionFactoryBuilderImplementor以覆盖build函数以返回扩展的SessionFactoryImpl
  • 实现SessionFactoryBuilderFactory实现getSessionFactoryBuilder返回扩展后的AbstractDelegatingSessionFactoryBuilderImplementor
  • 在META-INF/services下添加org.hibernate.boot.spi.SessionFactoryBuilderFactory文件,其值为我的SessionFactoryBuilderFactory实现的完整类名(让spring知道)

UPDATE
捕获";合并";调用(作为remendous7注释),所以我最终在任何刷新之前捕获isDirty状态,并在检查isChanged()时再次检查它

以下是一种不同的跟踪脏度的方法。

尽管在架构上与您的示例代码不同,但它可能更符合您的实际目标(我想发布一个事件,前提是数据库发生了更改)。

也许你可以使用一个Interceptor监听器让实体经理来完成繁重的工作,并告诉你什么是肮脏的。然后你只需要对它做出反应,而不是一开始就督促它找出脏东西。

看看这篇文章:https://www.baeldung.com/hibernate-entity-lifecycle

它有很多测试用例,基本上是检查保存在各种上下文中的对象是否脏,然后它依赖于一段名为DirtyDataInspector的代码,该代码可以有效地侦听刷新时标记为脏的任何项目,然后只记住它们(即将它们保存在列表中),这样单元测试用例就可以断言应该脏的东西实际上是冲洗得很脏。

脏数据检查器代码在他们的github上。这是方便访问的直接链接。

以下是将拦截器应用于工厂的代码,以便它能够有效。您可能需要在您的注入框架中相应地写下这一点。

它所基于的拦截器的代码有很多生命周期方法,你可能可以利用这些方法来获得"拦截"的完美行为;如果实际发生了脏保存,则执行此操作";。

你可以在这里看到它的完整文档。

我们不知道您的完整设置,但正如@Christian Beikov在评论中所建议的,在您调用isDirty()之前,插入是否已经刷新?

当您在没有运行事务的情况下调用repository.save(newEntity)时,就会发生这种情况,因为SimpleJpaRepositorysave方法本身用@Transactional:进行了注释

@Transactional
@Override
public <S extends T> S save(S entity) {
...
}

如果没有活动的话,这将把调用封装在一个新事务中,并在方法返回之前在事务结束时将插入内容刷新到DB中。

您可以选择用@Transactional对调用saveisDirty的方法进行注释,以便在调用方法时创建事务,并将其传播到存储库调用。这样,当save返回时,事务将不会被提交,并且会话仍然是脏的。


(编辑,只是为了完整性:在使用identityID生成策略的情况下,在提交正在运行的事务之前,在存储库的save调用期间刷新新创建的实体的插入以生成ID)

最新更新