FlushMode AUTO无法使用JPA和Hibernate

use*_*927 3 java spring hibernate jpa spring-boot

目前,我们正在迁移写的遗留应用程序Spring/Hibernate,以Spring Boot(对于具有更简洁的配置及其他福利).
因为Spring Boot坚持JPA,我们必须"迁移"我们的遗留代码 - 用native Hibernate(第5版)编写- 来JPA.

我们现在面临的问题FlushMode是,即使定义了Hibernate,也不会在触发查询之前刷新会话AUTO

配置如下:

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

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


2)持久性配置:
- 创建JPA Transaction Manager;
- 创建一个HibernateJpaSessionFactoryBean以防止我们不必调整SessionFactory使用(和自动装配)的所有地方EntityManagerFactory并确保SessionFactory并且EntityManagerFactory都参与相同(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;
    }
 }
Run Code Online (Sandbox Code Playgroud)


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

@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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


通过执行级联删除从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);
}
Run Code Online (Sandbox Code Playgroud)

remove方法是一种方便的方法,可确保双向关联的两端解耦.


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

事情我们检查:
1)FlushMode的中Session和缺省FlushModeSessionFactory/ EntityManagerFactory- >都被设置为AUTO
2)手动添加session.flush()查询被触发前- >这给了其中期望的结果question.remove(possibleAnswer)烧成查询之前被传播到DB
3)我们不面对的问题我们运行时的单元测试native Hibernate

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

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

- 更新2 -
似乎在执行'count'查询时,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();
}
Run Code Online (Sandbox Code Playgroud)

该方法isTransactionInProgress确定是否必须执行刷新.实现如下:

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

似乎 transactionCoordinator.getTransactionDriverControl().getStatus()返回NOT_ACTIVEtransactionCoordinator.isJoined()返回false.

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

Vla*_*cea 5

正如本文所解释的,Hibernate遗留FlushMode和JPA规范之间存在差异.

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

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

所以,你有多种选择:

  1. 你可以作为JPA引导.这意味着你必须使用LocalEntityManagerFactoryBean而不是LocalSessionFactoryBean.
  2. 您可以使用FlushMode.ALWAYS,如本文中所述.确保每个新Session设置为FlushMode.ALWAYS:sessionFactory.getCurrentSession().setFlushMode(FlushMode.ALWAYS);
  3. session.flush()可以在任何本机SQL查询之前手动调用.

更新

似乎transactionCoordinator.getTransactionDriverControl().getStatus()返回NOT_ACTIVE,而transactionCoordinator.isJoined()返回false.

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

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