【问题标题】: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://stackoverflow.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从何而来?

标签: java hibernate spring-mvc jpa


【解决方案1】:

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

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

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

如果没有一个已经处于活动状态,这会将调用包装在一个新事务中,并在方法返回之前在事务结束时将插入刷新到 DB。

您可以选择在调用saveisDirty 的方法上使用@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 - 是的,修复了代码。谢谢!
    猜你喜欢
    • 1970-01-01
    • 2012-08-23
    • 2011-10-22
    • 2011-03-09
    • 1970-01-01
    • 2011-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多