当且仅当数据库发生更改时,我想发布一个事件。我在@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)
时,就会发生这种情况,因为SimpleJpaRepository
的save
方法本身用@Transactional
:进行了注释
@Transactional
@Override
public <S extends T> S save(S entity) {
...
}
如果没有活动的话,这将把调用封装在一个新事务中,并在方法返回之前在事务结束时将插入内容刷新到DB中。
您可以选择用@Transactional
对调用save
和isDirty
的方法进行注释,以便在调用方法时创建事务,并将其传播到存储库调用。这样,当save
返回时,事务将不会被提交,并且会话仍然是脏的。
(编辑,只是为了完整性:在使用identity
ID生成策略的情况下,在提交正在运行的事务之前,在存储库的save
调用期间刷新新创建的实体的插入以生成ID)