如何判断当前会话是否脏?

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) 时,会发生这种情况,因为 SimpleJpaRepositorysave 方法本身用 @Transactional 注释:

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

如果 none 已经处于活动状态,这会将调用包装在一个新的事务中,并在事务结束时将插入刷新到 DB,就在方法 returns.[=22 之前=]

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


(编辑,只是为了完整性:在使用 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()

时再次检查它