如何判断当前会话是不是脏?
Posted
技术标签:
【中文标题】如何判断当前会话是不是脏?【英文标题】:How can I tell if current session is dirty?如何判断当前会话是否脏? 【发布时间】:2021-08-09 15:39:22 【问题描述】:当且仅当数据库发生更改时,我想发布一个事件。我在 @Transaction 下运行是 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://***.com/a/5268617/672689 此处的答案,我希望它能够工作并返回 true。
我错过了什么?
更新
考虑到@fladdimir 的回答,虽然这个函数是在事务上下文中调用的,但我确实在函数上添加了@Transactional
(来自 org.springframework.transaction.annotation)。但我仍然遇到同样的行为。 isDirty 返回 false。
此外,正如预期的那样,当程序在session.isDirty()
行的断点处暂停时,新实体不会显示在数据库上。
UPDATE_2 我还尝试在调用 repo 保存之前更改会话刷新模式,也没有任何效果:
session.setFlushMode(FlushModeType.COMMIT);
session.setHibernateFlushMode(FlushMode.MANUAL);
【问题讨论】:
你使用什么休眠版本?您是否尝试过使用普通的休眠应用程序? 使用 org.springframework.boot:spring-boot-starter-data-jpa 版本 2.3.3.RELEASE 如果您查看SessionImpl
中该方法的实现,您应该会看到插入已被覆盖,所以您的插入可能已经被刷新了?
@Christian Beikov - 我知道,这就是上周让我发疯的原因......
指令entityRepository.save(newEntity)
中的newEntity从何而来?
【参考方案1】:
我们不知道您的完整设置,但正如@Christian Beikov 在评论中建议的那样,在您致电isDirty()
之前插入是否可能已经刷新?
当您在没有运行事务的情况下调用 repository.save(newEntity)
时会发生这种情况,因为 SimpleJpaRepository
的 save
方法本身是用 @Transactional
注释的:
@Transactional
@Override
public <S extends T> S save(S entity)
...
如果没有一个已经处于活动状态,这会将调用包装在一个新事务中,并在方法返回之前在事务结束时将插入刷新到 DB。
您可以选择在调用save
和isDirty
的方法上使用@Transactional
进行注释,以便在调用您的方法时创建事务并传播到存储库调用。这样当save
返回时事务不会被提交,会话仍然是脏的。
(编辑,仅出于完整性考虑:在使用identity
ID 生成策略的情况下,新创建的实体的插入在存储库的save
调用期间刷新以生成 ID,然后提交正在运行的事务)
【讨论】:
虽然这个函数是在事务上下文中调用的,但我确实尝试在函数上添加@Transactional(来自 org.springframework.transaction.annotation)。但我仍然遇到同样的行为。 isDirty 返回 false。 ): 刚刚尝试使用最小的演示项目和this integration test 重现该问题 - 您能发现差异,或提供更多有关您的设置的详细信息吗? (正如上面@tremendous 建议的那样,@Transactional
仅在特定条件下有效,但我想你已经知道了?blog.staynoob.cn/post/2019/02/…)【参考方案2】:
以下是您可以用来跟踪脏污的另一种方式。
虽然在架构上与您的示例代码不同,但它可能更符合您的实际目标(当且仅当数据库发生更改时,我想发布一个事件)。 p>
也许您可以使用拦截器侦听器让实体管理器完成繁重的工作,然后告诉您什么是脏的。然后你只需要对它做出反应,而不是一开始就刺激它来找出脏东西。
看看这篇文章:https://www.baeldung.com/hibernate-entity-lifecycle
它有很多测试用例,基本上检查保存在各种上下文中的对象的脏污程度,然后它依赖于一段称为 DirtyDataInspector 的代码,该代码有效地监听任何在刷新时标记为脏的项目,然后只记住它们(即将它们保存在一个列表中),因此单元测试用例可以断言应该是脏的东西实际上被刷新为脏的。
脏数据检查器代码在他们的 github 上。这是direct link,方便访问。
这是拦截器应用于工厂的代码,以便它可以生效。你可能需要在你的注入框架中相应地write this up。
它所基于的拦截器的代码有大量的生命周期方法,您可以利用这些方法来获得“如果确实发生了脏保存,请执行此操作”的完美行为。
你可以看到它的完整文档here。
【讨论】:
【参考方案3】:首先,Session.isDirty()
和我理解的意思不一样。它告诉当前会话是否正在保留尚未发送到数据库的内存查询。虽然我认为它会告诉交易是否有更改的查询。保存新实体时,即使在事务中,也必须将插入查询发送到数据库以获取新的实体 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 意识到它)。
更新
捕获“合并”调用时存在错误(作为巨大的 7 评论),因此我最终在任何刷新之前捕获了 isDirty 状态,并在检查 isChanged()
时再次检查它。
【讨论】:
附加说明:当使用IDENTITY
ID 生成策略时,插入仅被刷新以确定 ID 值,使用例如SEQUENCE
不会在 save
上触发立即插入(使会话变脏)
如果调用 session.merge 而没有编辑实体的任何字段,你会说 session.isDirty 是真的吗?
@tremendous7 - 是的,修复了代码。谢谢!以上是关于如何判断当前会话是不是脏?的主要内容,如果未能解决你的问题,请参考以下文章