java - FlushMode AUTO 不适用于 JPA 和 Hibernate

标签 java spring hibernate jpa spring-boot

我们目前正在将一个用 Spring/Hibernate 编写的遗留应用程序迁移到 Spring Boot(为了减少冗长的配置和其他好处)。
因为 Spring Boot 遵守 JPA,我们必须“迁移”我们的遗留代码 - 用 native Hibernate(版本 5)编写 - 到 JPA

我们现在面临一个问题,即 Hibernate 在触发查询之前不会刷新 session ,即使定义了 FlushMode AUTO

配置如下:

1) Main Spring Boot Config,即应用的入口

@Configuration
@EnableAutoConfiguration
@ComponentScan
@Slf4j(topic = "system")
public class MainApp {
    public static void main(String[] args) {
        SpringApplication.run(MainApp.class, args);
    }


2)持久化配置:
- 创建一个 JPA 事务管理器
- 创建一个 HibernateJpaSessionFactoryBean 以防止我们不必调整 EntityManagerFactory 使用(和 Autowiring )SessionFactory 的所有地方并确保SessionFactoryEntityManagerFactory 都参与相同的 (JPA) Transaction

@Configuration
public class PersistenceConfig {
  @Bean
    public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
        JpaTransactionManager transactionManager = new JpaTransactionManager(entityManagerFactoryBean.getObject());
        transactionManager.setDefaultTimeout(30);

        return transactionManager;
    }

    @Bean
    public HibernateJpaSessionFactoryBean sessionFactory(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
        HibernateJpaSessionFactoryBean sessionFactoryBean = new HibernateJpaSessionFactoryBean();
        sessionFactoryBean.setEntityManagerFactory(entityManagerFactoryBean.getObject());

        return sessionFactoryBean;
    }
 }


产生问题的负责代码如下:

@Override
public void deletePossibleAnswerAndRemoveFromQuestion(Long definitionId, Long questionId, Long possibleAnswerId) {
    Definition definition = checkEntity(Definition.class, definitionId);
    Question question = checkEntity(Question.class, questionId);
    PossibleAnswer possibleAnswer = checkEntity(PossibleAnswer.class, possibleAnswerId);

    question.remove(possibleAnswer);

    if (definition.isHasRefinement()) {
         // this fires a 'select count(*) ...' query
        if (!possibleAnswerRepository.existsByType(definitionId, QuestionType.REFINE)) {
            definition.markNoRefinementsPresent();
        }
    }
}


通过执行级联删除从问题(父)实体中删除 PossibleAnswer(子)实体,如下面的代码所示:

@Table(name = "questions")
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Question extends AbstractEntity {

@OneToMany(fetch = FetchType.LAZY, mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<PossibleAnswer> possibleAnswers = new HashSet<>();

public void remove(PossibleAnswer possibleAnswer) {
    getPossibleAnswers().remove(possibleAnswer);
    possibleAnswer.setQuestion(null);
}

remove 方法是一种确保双向关联两端解耦的便捷方法。


现在的大问题是 question.remove(possibleAnswer) 在提交时间之前传播到数据库。
换句话说:级联删除生成一个删除查询,该查询在“count”查询之后触发,导致过时的结果,因为它取决于被删除的 PossibleAnswer。

我们检查的事情:
1) SessionFlushModeSessionFactory/EntityManagerFactory的默认FlushMode ->两者都设置为 AUTO
2) 在触发查询之前手动添加 session.flush() -> 这给出了期望的结果,其中 question.remove(possibleAnswer) 在触发查询之前传播到 DB
3) 在运行 native Hibernate

时,我们不会在单元测试中遇到问题

有谁知道为什么我们会遇到这种奇怪的行为???

-- 更新 1--
我检查过的东西:
1) 在 EntityManager 上正确设置了默认的 FlushMode 'AUTO';
2) 在级联删除之前执行“计数”查询。

-- 更新 2--
似乎在执行“计数”查询时,Hibernate 首先检查(如下所示的代码)是否必须在真正执行查询之前刷新 session 。

    protected boolean autoFlushIfRequired(Set querySpaces) throws HibernateException {
    errorIfClosed();
    if ( !isTransactionInProgress() ) {
        // do not auto-flush while outside a transaction
        return false;
    }
    AutoFlushEvent event = new AutoFlushEvent( querySpaces, this );
    listeners( EventType.AUTO_FLUSH );
    for ( AutoFlushEventListener listener : listeners( EventType.AUTO_FLUSH ) ) {
        listener.onAutoFlush( event );
    }
    return event.isFlushRequired();
}

方法 isTransactionInProgress 确定是否必须执行刷新。 实现如下所示:

@Override
public boolean isTransactionInProgress() {
    checkTransactionSynchStatus();
    return !isClosed() && transactionCoordinator.getTransactionDriverControl()
            .getStatus() == TransactionStatus.ACTIVE && transactionCoordinator.isJoined();
}

看来 transactionCoordinator.getTransactionDriverControl().getStatus() 返回 NOT_ACTIVE 并且 transactionCoordinator.isJoined() 返回 false

这导致在触发查询之前未执行级联删除的问题。
我真的不知道为什么基础交易不是进展。
我的设置是普通的 Spring Boot 和 Hibernate,其中我有一个 Service 注释方法 @Transactional 所以所有底层数据库调用都应该在一个事务中执行。

最佳答案

Hibernate legacy FlushMode 之间存在差异和 JPA 规范。

如果您升级到 Hibernate 5.2,这完全取决于您如何引导 Hibernate。如果您使用 JPA 方式(例如 persistence.xml)进行引导,那么将使用 JPA 行为。如果您通过 SessionFactoryBuilder 进行引导,则会考虑遗留行为。

我怀疑 count 查询是 native SQL 查询,因为实体查询应该在旧模式和 JPA 模式下触发刷新。

因此,您有多种选择:

  1. 您可以作为 JPA 进行引导。这意味着您必须使用 LocalEntityManagerFactoryBean 而不是 LocalSessionFactoryBean
  2. 您可以使用 FlushMode.ALWAYS .确保每个新的 Session 都设置了 FlushMode.ALWAYS:sessionFactory.getCurrentSession().setFlushMode(FlushMode.ALWAYS);<
  3. 您在任何 native SQL 查询之前手动调用 session.flush()

更新

It seems that transactionCoordinator.getTransactionDriverControl().getStatus() returns NOT_ACTIVE and transactionCoordinator.isJoined() returns false.

很可能是 Spring 事务管理配置有问题。确保 Spring 框架版本与您使用的 Hibernate 5 兼容。

此外,如果 TransactionInterceptor 存在,请检查调试堆栈跟踪。如果不是,那么您就没有在事务上下文中运行。

关于java - FlushMode AUTO 不适用于 JPA 和 Hibernate,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42253404/

相关文章:

java - 使用 DevicePolicyManager 在 Android 上隐藏应用程序

java - 表单值未提交到 servlet

java - 处理 Spring 集成文件的预期返回

nhibernate - Hibernate配置如何创建一个对象

java - @Transactional 注解 Spring boot 2.0 和 hibernate LazyInitializationException

java - org.hibernate.tutorial.annotations.Event 类未找到导致的错误

java - SSLContext.getInstance ("TLS") 漏洞

java - 有没有办法在方法声明中定义两个泛型类型

java - 使用当前系统日期在单独的目录中为每个日期创建日志文件

java - 如何为不同的 URL 堆叠 CORS 过滤器?