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

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) 时会发生这种情况,因为 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 - 是的,修复了代码。谢谢!

以上是关于如何判断当前会话是不是脏?的主要内容,如果未能解决你的问题,请参考以下文章

无法判断获取或关系是不是是问题

检测会话当前是不是处于活动状态并加入而不是开始新的会话

如何从实体框架 DbContext 收集当前的 SQL Server 会话 ID?

myBatis 日记

如何显示bash会话的当前进程树?

Nginx 会话保持