我们目前正在将用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)
在提交时间之前传播到数据库。
换句话说:级联删除生成一个删除查询,该查询在
我们检查过的事情:
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调用应该在一个事务中执行。
答案 0 :(得分:5)
正如this article中所解释的那样,Hibernate遗留FlushMode和JPA规范之间存在差异。
如果你升级到Hibernate 5.2,这一切都取决于你如何引导Hibernate。如果使用JPA方式引导(例如persistence.xml
),则将使用JPA行为。如果您通过SessionFactoryBuilder
进行引导,则会考虑遗留行为。
我怀疑count
查询是本机SQL查询,因为实体查询应该在旧版和JPA模式下触发刷新。
所以,你有多种选择:
LocalEntityManagerFactoryBean
代替LocalSessionFactoryBean
。FlushMode.ALWAYS
,如this article中所述。确保使用Session
FlushMode.ALWAYS
sessionFactory.getCurrentSession().setFlushMode(FlushMode.ALWAYS);
session.flush()
。好像 transactionCoordinator.getTransactionDriverControl()。的getStatus() 返回NOT_ACTIVE并返回transactionCoordinator.isJoined() 假的。
最有可能是Spring事务管理配置存在问题。确保Spring框架版本与您正在使用的Hibernate 5兼容。
此外,如果TransactionInterceptor
存在,请检查调试堆栈跟踪。如果不是,那么您不会在事务上下文中运行。