FlushMode AUTO不适用于JPA和Hibernate [英] FlushMode AUTO is not working with JPA and Hibernate

查看:75
本文介绍了FlushMode AUTO不适用于JPA和Hibernate的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们目前正在将用Spring/Hibernate编写的旧版应用程序迁移到Spring Boot(以减少冗长的配置和其他好处).
由于Spring Boot遵守JPA,因此我们必须将用native Hibernate(版本5)编写的旧代码迁移"到JPA.

我们现在面临的一个问题是,即使在FlushMode被定义为AUTO

We're currently migrating a legacy application written in Spring/Hibernate to Spring Boot (for having a less verbose config and other benefits).
Because Spring Boot adheres to JPA, we have to 'migrate' our legacy code - written in native Hibernate (version 5) - to JPA.

We're now facing an issue where Hibernate doesn't flush the session before firing a query, even when FlushMode is defined AUTO

配置如下:

1)Main Spring Boot Config,它是应用程序的入口

1) Main Spring Boot Config that is the entry of the application

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


2)持久性配置:
-创建JPA Transaction Manager;
-创建HibernateJpaSessionFactoryBean以防止我们不必适应EntityManagerFactory使用(自动布线)SessionFactory的所有地方,并确保SessionFactoryEntityManagerFactory都参与相同的(JPA)Transaction.


2) Persistence Config:
- creates a JPA Transaction Manager;
- creates a HibernateJpaSessionFactoryBean to prevent we don't have to adapt all places where a SessionFactory is used (and autowired) by EntityManagerFactory and to ensure SessionFactory and EntityManagerFactory both participate in same (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;
    }
 }


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


The responsible code that generates the issue is as follows:

@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();
        }
    }
}


通过执行级联删除,可以从问题"(父级)实体中删除一个可能的答案"(子级)实体,如下代码所示:


A PossibleAnswer (child) entity is removed from a Question (parent) entity by performing a cascading delete as shown in the code below:

@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方法是一种方便的方法,可确保双向关联的两端都解耦.

The remove method is a convenience method that ensures both ends of bidirectional association are decoupled.


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


The big problem now is that question.remove(possibleAnswer) is propagated to database just before commit time.
Put in other words: the cascading remove generates a delete query which is triggered after the 'count' query causing stale results as it depends on the PossibleAnswer being deleted.

我们检查的事情:
1)SessionFlushModeSessionFactory/EntityManagerFactory->的默认FlushMode都设置为AUTO
2)在触发查询之前手动添加session.flush()->这将在触发查询
之前将question.remove(possibleAnswer)传播到DB时提供所需的结果 3)在运行native Hibernate

Things we checked:
1) FlushMode of the Session and default FlushMode of SessionFactory/EntityManagerFactory -> both are set to AUTO
2) Manually adding session.flush() before query is triggered -> this gives desired result where question.remove(possibleAnswer) is propagated to DB before firing query
3) We don't face the issue in our unit-test when running native Hibernate

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

Does anyone have a clue why we encounter this strange behavior???

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

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

-- UPDATE 1--
Things I've checked:
1) Default FlushMode 'AUTO' is correctly set on EntityManager;
2) 'count' query is executed BEFORE cascading remove.

-- UPDATE 2--
It seems when the 'count' query is executed, Hibernate first checks (code depicted below) if the Session must be flushed before really executing the query.

    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确定是否必须执行冲洗. 该实现如下所示:

The method isTransactionInProgressdetermines if a flush must be performed. The implementation looks like follows:

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

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

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

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

This is causing the issue that cascaded delete is not executed before firing the query.
I really don't have any idea why the underlying transaction is not is progress.
My setup is plain Spring Boot and Hibernate where I have a Service method annotated @Transactional so all underlying db-calls should be executed in one transaction.

推荐答案

Hibernate旧版

There's a difference between the Hibernate legacy FlushMode and JPA specification.

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

If you upgrade to Hibernate 5.2, it all depends on how you bootstrap Hibernate. If you bootstrap using the JPA way (e.g. persistence.xml), then the JPA behavior will be used. If you bootstrap through the SessionFactoryBuilder, the legacy behavior is considered.

我怀疑count查询是本机SQL查询,因为实体查询应同时触发传统模式和JPA模式的刷新.

I suspect that the count query is a native SQL query since entity queries should trigger a flush on both legacy and JPA modes.

因此,您有多种选择:

  1. 您可以作为JPA进行引导.这意味着您必须使用LocalEntityManagerFactoryBean而不是LocalSessionFactoryBean.
  2. 您可以使用 FlushMode.ALWAYS .确保使用FlushMode.ALWAYS设置每个新的Session:sessionFactory.getCurrentSession().setFlushMode(FlushMode.ALWAYS);
  3. 您在进行任何本机SQL查询之前手动调用session.flush().
  1. You can bootstrap as JPA. This means you have to use the LocalEntityManagerFactoryBean instead of LocalSessionFactoryBean.
  2. You can use FlushMode.ALWAYS. Make sure that every new Session is set with the FlushMode.ALWAYS: sessionFactory.getCurrentSession().setFlushMode(FlushMode.ALWAYS);
  3. You call a session.flush() manually prior to any native SQL query.

更新

似乎 transactionCoordinator.getTransactionDriverControl().getStatus() 返回NOT_ACTIVE,transactionCoordinator.isJoined()返回 错误.

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

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

Most likely there is a problem with the Spring transaction management config. Make sure the Spring framework version is compatible with the Hibernate 5 you are using.

此外,如果TransactionInterceptor存在,请检查调试堆栈跟踪.如果不是,则表明您不在事务上下文中运行.

Also, check the debug stack trace if the TransactionInterceptor is there. If it's not, then you are not running within a transactional context.

这篇关于FlushMode AUTO不适用于JPA和Hibernate的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆