FlushMode AUTO无法使用JPA和Hibernate

时间:2017-02-15 15:23:11

标签: java spring hibernate jpa spring-boot

我们目前正在将用Spring/Hibernate编写的遗留应用程序迁移到Spring Boot(因为配置和其他好处较少)。
由于Spring Boot遵守JPA,因此我们必须将我们的遗留代码(native Hibernate(第5版))“迁移”到JPA

我们现在面临的问题是,即使定义FlushMode AUTO,Hibernate也不会在触发查询之前刷新会话@Configuration @EnableAutoConfiguration @ComponentScan @Slf4j(topic = "system") public class MainApp { public static void main(String[] args) { SpringApplication.run(MainApp.class, args); }

Config如下所示:

1)主Spring Boot Config是应用程序的入口

JPA Transaction Manager


2)持久性配置:
- 创建HibernateJpaSessionFactoryBean;
- 创建SessionFactory以防止我们不必调整EntityManagerFactory使用SessionFactory(并自动装配)的所有地方,并确保EntityManagerFactory和{{1}两者都参与同一个(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();
        }
    }
}


通过执行级联删除从Questions(父)实体中删除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)在提交时间之前传播到数据库。
换句话说:级联删除生成一个删除查询,该查询在查询后触发导致过时结果,因为它取决于要删除的PossibleAnswer。

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

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

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

- 更新1 -
我检查过的事情:
1)默认FlushMode'AUTO'在EntityManager上正确设置;
2)在级联删除之前执行'count'查询。

- 更新2 -
在执行'count'查询时,Hibernate首先检查(下面描述的代码)是否必须在真正执行查询之前刷新Session。

native Hibernate

方法 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()

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

1 个答案:

答案 0 :(得分:5)

正如this article中所解释的那样,Hibernate遗留FlushMode和JPA规范之间存在差异。

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

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

所以,你有多种选择:

  1. 您可以自举为JPA。这意味着您必须使用LocalEntityManagerFactoryBean代替LocalSessionFactoryBean
  2. 您可以使用FlushMode.ALWAYS,如this article中所述。确保使用Session FlushMode.ALWAYS
  3. 设置每个新sessionFactory.getCurrentSession().setFlushMode(FlushMode.ALWAYS);
  4. 在任何本机SQL查询之前手动调用session.flush()
  5. 更新

      

    好像   transactionCoordinator.getTransactionDriverControl()。的getStatus()   返回NOT_ACTIVE并返回transactionCoordinator.isJoined()   假的。

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

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