在 Hibernate 中禁用脏实体的自动更新

问题描述 投票:0回答:1

有没有办法完全禁用脏实体的自动更新?

在我们的应用程序中,我们加载一个实体,并且一些“模拟”业务逻辑更改一个属性(对于休眠来说,该实体现在是脏的)。 之后我们要将模拟结果保存到数据库中。我们通过显式调用

resultRepo.save(result)
来做到这一点。在我们的整个项目中,我们使用这种范式,对于我们想要保存的实体,我们明确地称之为
*Repo.save(..)

问题是,如果事务结束,Hibernate 现在也会自动保存脏实体。我们怎样才能避免这种隐含的魔力?

可能的解决方案:

  1. Transactional(readOnly=true)
    不起作用,因为我们需要存储结果。
  2. 手动将实体(或克隆所需数据)分离到新对象中 --> 非常不方便,因为我们在很多地方都有这种情况。
  3. 将休眠刷新模式设置为
    MANUAL
    -> 现在一切都按预期工作,但是
    • 我真的不知道为什么?
    • 这感觉就像一个黑客,因为我正在尝试配置脏自动保存并为此使用刷新模式。

理想情况下会有一个像

org.hibernate.autoSaveDirtyEntites: false
这样的显式切换。

以防万一:我们在 Spring Boot 2.6.4 上使用 Hibernate 5.6.5。

java hibernate spring-data-jpa
1个回答
0
投票

这个常见错误出现在许多代码库中,由于隐藏的脏检查和同步机制,经常让开发人员(包括我自己)感到困惑,像 Spring 的

CrudRepository#save()
这样的名称更增加了混乱。真正的解决方案是从一开始就考虑到这些机制。

有一个解决方法,但我不推荐它,因为它增加了开销并降低了 Hibernate 的优化优势。然而,它可以作为临时修复,直到设计完全考虑 Hibernate 的脏污检测和刷新行为。

值得注意的本地修复:

在深入研究之前,提及其他本地修复会很有帮助,其中许多您已经注意到:

  1. @Transactional(readOnly=true)
    :这会禁用脏检查和刷新,但会阻止保存,因为在此模式下不允许更新。

  2. 手动持久化上下文管理:进行修改时分离实体,或仅在需要保存时将其合并回持久化上下文。

  3. 使用非实体类:将必要的数据从实体复制到非实体类中,在那里执行操作,并仅在准备保存时将更改应用于实体。

  4. StatelessSession:此选项不涉及持久上下文跟踪或自动刷新,为特定用例提供轻量级替代方案。

注意:将

FlushMode
设置为
MANUAL
并不总能解决问题。虽然它阻止自动刷新(事务提交时除外),但实体仍然可以被检测为脏。在某些情况下,这可能会解决问题,但这取决于具体的代码流程,这里我不会介绍。

解决方法:

在此解决方法中,Hibernate 的脏污检测被调整为将实体视为干净的,直到调用

CrudRepository#save()
为止,此时实体被标记为脏,并立即触发
flush()
。我将概述实现这一目标的一种方法,并简要提及一些替代方法。

为所有要继承的实体定义一个

PersistenceBaseEntity
类,其中
persistable
字段控制何时将实体标记为脏并刷新。

@Getter @Setter
public class PersistenceBaseEntity {
  private transient boolean persistable;
}

接下来,我们需要配置Hibernate的脏检测机制。这可以通过实现和注册 Hibernate 的

CustomEntityDirtinessStrategy
来实现。

public class MyCustomEntityDirtinessStrategy extends DefaultCustomEntityDirtinessStrategy {
  @Override
  public boolean canDirtyCheck(Object entity, EntityPersister persister, Session session) {
    return entity instanceof PersistenceBaseEntity;
  }

  @Override
  public boolean isDirty(Object entity, EntityPersister persister, Session session) {
    // canDirtyCheck() implementation guarantees that entity is of PersistenceBaseEntity type.
    return ((PersistenceBaseEntity) entity).isPersistable();
  }
}

要在Spring Boot中注册它,只需添加以下配置:

spring.jpa.properties.hibernate.entity_dirtiness_strategy=<fully-qualified-class-name-of-MyCustomEntityDirtinessStrategy>

最后,自定义

CrudRepository#save()
(和其他相关方法)以通过刷新实体的状态来触发更新。这可以通过提供自定义
JpaRepository
实现来完成,如下所示:

public class MyJpaRepositoryImpl<T, ID> extends SimpleJpaRepository<T, ID> {

  public MyJpaRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
    super(entityInformation, entityManager);
  }

  public MyJpaRepositoryImpl(Class<T> domainClass, EntityManager entityManager) {
    super(domainClass, entityManager);
  }

  @Transactional
  @Override
  public <S extends T> @NonNull S save(@NonNull S entity) {
    entity = super.save(entity);
    /* Customized behaviour for PersistenceBaseEntities */
    if (entity instanceof PersistenceBaseEntity persistenceBaseEntity) {
    persistenceBaseEntity.setPersistable(true);
    flush();
    persistenceBaseEntity.setPersistable(false);
    }
    return entity;
  }

}

考虑重写其他方法,例如

saveAll()
saveAndFlush()
saveAllAndFlush()
,以确保在不同场景下一致地处理实体保存。

在主应用程序类中使用

JpaRepository
注册自定义
@EnableJpaRepositories(repositoryBaseClass=YourCustomRepository.class)
实现。

当然,在最终确定您的方法之前,还有其他值得考虑的替代方法。这里有一些:

  • 实现自定义
    FlushEntityEventListener
  • 定制
    Interceptor#findDirty()
  • 使用自定义事件侦听器,例如
    PreUpdateEventListener
    PreInsertEventListener
  • 最后,您可能会发现 Hibernate 的字节码增强对于高效的脏实体检测特别有用,可提供更快的跟踪和改进的性能。 字节码增强
© www.soinside.com 2019 - 2024. All rights reserved.