如何判断当前会话是否脏?
How can I tell if current session is dirty?
当且仅当数据库发生更改时,我想发布一个事件。我在 @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;
}
根据这里的回答 我希望它能工作并且 return 正确。
我错过了什么?
更新
考虑到@fladdimir 的回答,虽然这个函数是在事务上下文中调用的,但我确实在函数上添加了 @Transactional
(来自 org.springframework.transaction.annotation)。但我仍然遇到同样的行为。 isDirty 是 returning false。
此外,正如预期的那样,当程序停留在 session.isDirty()
.
行的断点时,新实体不会显示在数据库中
UPDATE_2
我还尝试在调用 repo 保存之前更改会话刷新模式,但也没有任何效果:
session.setFlushMode(FlushModeType.COMMIT);
session.setHibernateFlushMode(FlushMode.MANUAL);
我们不知道您的完整设置,但正如@Christian Beikov 在评论中建议的那样,是否有可能在您调用 isDirty()
之前插入已经刷新?
当您在没有 运行 事务的情况下调用 repository.save(newEntity)
时,会发生这种情况,因为 SimpleJpaRepository
的 save
方法本身用 @Transactional
注释:
@Transactional
@Override
public <S extends T> S save(S entity) {
...
}
如果 none 已经处于活动状态,这会将调用包装在一个新的事务中,并在事务结束时将插入刷新到 DB,就在方法 returns.[=22 之前=]
您可以选择用 @Transactional
注释调用 save
和 isDirty
的方法,以便在调用您的方法时创建事务,并将其传播到存储库称呼。这样在save
returns时事务不会被提交,会话仍然是脏的。
(编辑,只是为了完整性:在使用 identity
ID 生成策略的情况下,新创建的实体的插入在存储库的 save
调用期间刷新以生成 ID,在运行 事务已提交)
以下是您可以用来跟踪脏污程度的另一种方式。
虽然在体系结构上与您的示例代码不同,但它可能更符合您的实际目标(我想发布事件当且仅当数据库发生变化时).
也许您可以使用拦截器侦听器让实体管理器完成繁重的工作,并告诉您什么是脏的。然后你只需要对它做出反应,而不是首先刺激它来解决问题。
看看这篇文章:https://www.baeldung.com/hibernate-entity-lifecycle
它有很多测试用例,基本上检查在各种上下文中保存的对象的脏度,然后它依赖于一段称为 DirtyDataInspector 的代码,该代码有效地侦听在刷新时标记为脏的任何项目,然后只记住它们(即将它们保存在列表中),这样单元测试用例就可以断言本应脏的东西实际上被冲刷成了脏的。
脏数据检查器代码在他们的 github 上。为了便于访问,这里是 direct link。
这是将拦截器应用于工厂的代码,因此它可以生效。您可能需要相应地在您的注入框架中 write this up。
它所基于的拦截器的代码有大量的生命周期方法,您可以利用它来获得完美的行为“如果实际上发生了脏保存,请执行此操作”。
您可以查看它的完整文档here。
首先,Session.isDirty()
和我理解的意思不一样。它告诉当前会话是否正在保存尚未发送到数据库的内存查询。虽然我认为它告诉交易是否有变化的查询。保存新实体时,即使在事务中,也必须将插入查询发送到数据库才能获取新实体 ID,因此之后的 isDirty() 将始终为 false。
所以我最终创建了一个 class 来扩展 SessionImpl 并保持会话的 change
状态,在持续和合并调用时更新它(休眠正在使用的函数)
所以这是 class 我写的:
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
函数和 return 我的 CustomSession
- extend
SessionFactoryImpl
覆盖 withOptions
函数到 return 扩展 SessionFactoryImpl.SessionBuilderImpl
- extend
AbstractDelegatingSessionFactoryBuilderImplementor
覆盖 build
函数到 return 扩展 SessionFactoryImpl
- 实施
SessionFactoryBuilderFactory
实施 getSessionFactoryBuilder
到 return 扩展 AbstractDelegatingSessionFactoryBuilderImplementor
- 在 META-INF/services 下添加
org.hibernate.boot.spi.SessionFactoryBuilderFactory
文件,其中值是我的 SessionFactoryBuilderFactory
实现完整 class 名称(以便 spring 知道)。
更新
捕获“合并”调用时存在一个错误(作为巨大的评论),所以我最终在任何刷新之前捕获了 isDirty 状态,并在检查 isChanged()
时再次检查它
当且仅当数据库发生更改时,我想发布一个事件。我在 @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;
}
根据这里的回答 我希望它能工作并且 return 正确。
我错过了什么?
更新
考虑到@fladdimir 的回答,虽然这个函数是在事务上下文中调用的,但我确实在函数上添加了 @Transactional
(来自 org.springframework.transaction.annotation)。但我仍然遇到同样的行为。 isDirty 是 returning false。
此外,正如预期的那样,当程序停留在 session.isDirty()
.
UPDATE_2
我还尝试在调用 repo 保存之前更改会话刷新模式,但也没有任何效果:
session.setFlushMode(FlushModeType.COMMIT);
session.setHibernateFlushMode(FlushMode.MANUAL);
我们不知道您的完整设置,但正如@Christian Beikov 在评论中建议的那样,是否有可能在您调用 isDirty()
之前插入已经刷新?
当您在没有 运行 事务的情况下调用 repository.save(newEntity)
时,会发生这种情况,因为 SimpleJpaRepository
的 save
方法本身用 @Transactional
注释:
@Transactional
@Override
public <S extends T> S save(S entity) {
...
}
如果 none 已经处于活动状态,这会将调用包装在一个新的事务中,并在事务结束时将插入刷新到 DB,就在方法 returns.[=22 之前=]
您可以选择用 @Transactional
注释调用 save
和 isDirty
的方法,以便在调用您的方法时创建事务,并将其传播到存储库称呼。这样在save
returns时事务不会被提交,会话仍然是脏的。
(编辑,只是为了完整性:在使用 identity
ID 生成策略的情况下,新创建的实体的插入在存储库的 save
调用期间刷新以生成 ID,在运行 事务已提交)
以下是您可以用来跟踪脏污程度的另一种方式。
虽然在体系结构上与您的示例代码不同,但它可能更符合您的实际目标(我想发布事件当且仅当数据库发生变化时).
也许您可以使用拦截器侦听器让实体管理器完成繁重的工作,并告诉您什么是脏的。然后你只需要对它做出反应,而不是首先刺激它来解决问题。
看看这篇文章:https://www.baeldung.com/hibernate-entity-lifecycle
它有很多测试用例,基本上检查在各种上下文中保存的对象的脏度,然后它依赖于一段称为 DirtyDataInspector 的代码,该代码有效地侦听在刷新时标记为脏的任何项目,然后只记住它们(即将它们保存在列表中),这样单元测试用例就可以断言本应脏的东西实际上被冲刷成了脏的。
脏数据检查器代码在他们的 github 上。为了便于访问,这里是 direct link。
这是将拦截器应用于工厂的代码,因此它可以生效。您可能需要相应地在您的注入框架中 write this up。
它所基于的拦截器的代码有大量的生命周期方法,您可以利用它来获得完美的行为“如果实际上发生了脏保存,请执行此操作”。
您可以查看它的完整文档here。
首先,Session.isDirty()
和我理解的意思不一样。它告诉当前会话是否正在保存尚未发送到数据库的内存查询。虽然我认为它告诉交易是否有变化的查询。保存新实体时,即使在事务中,也必须将插入查询发送到数据库才能获取新实体 ID,因此之后的 isDirty() 将始终为 false。
所以我最终创建了一个 class 来扩展 SessionImpl 并保持会话的 change
状态,在持续和合并调用时更新它(休眠正在使用的函数)
所以这是 class 我写的:
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
函数和 return 我的CustomSession
- extend
SessionFactoryImpl
覆盖withOptions
函数到 return 扩展SessionFactoryImpl.SessionBuilderImpl
- extend
AbstractDelegatingSessionFactoryBuilderImplementor
覆盖build
函数到 return 扩展SessionFactoryImpl
- 实施
SessionFactoryBuilderFactory
实施getSessionFactoryBuilder
到 return 扩展AbstractDelegatingSessionFactoryBuilderImplementor
- 在 META-INF/services 下添加
org.hibernate.boot.spi.SessionFactoryBuilderFactory
文件,其中值是我的SessionFactoryBuilderFactory
实现完整 class 名称(以便 spring 知道)。
更新
捕获“合并”调用时存在一个错误(作为巨大的评论),所以我最终在任何刷新之前捕获了 isDirty 状态,并在检查 isChanged()