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和缺省FlushMode的SessionFactory/ 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_ACTIVE并transactionCoordinator.isJoined()返回false.
这导致在触发查询之前未执行级联删除的问题.
我真的不知道为什么底层交易不是进步.
我的设置是普通的Spring Boot和Hibernate,我有一个Service注释方法,@Transactional所以所有底层的db调用都应该在一个事务中执行.
正如本文所解释的,Hibernate遗留FlushMode和JPA规范之间存在差异.
如果你升级到Hibernate 5.2,这一切都取决于你如何引导Hibernate.如果使用JPA方式(例如persistence.xml)进行引导,则将使用JPA行为.如果您通过引导程序SessionFactoryBuilder,则会考虑遗留行为.
我怀疑count查询是本机SQL查询,因为实体查询应该在遗留和JPA模式下触发刷新.
所以,你有多种选择:
LocalEntityManagerFactoryBean而不是LocalSessionFactoryBean.FlushMode.ALWAYS,如本文中所述.确保每个新Session设置为FlushMode.ALWAYS:sessionFactory.getCurrentSession().setFlushMode(FlushMode.ALWAYS);session.flush()可以在任何本机SQL查询之前手动调用.似乎transactionCoordinator.getTransactionDriverControl().getStatus()返回NOT_ACTIVE,而transactionCoordinator.isJoined()返回false.
很可能是Spring事务管理配置存在问题.确保Spring框架版本与您正在使用的Hibernate 5兼容.
另外,检查调试堆栈跟踪是否TransactionInterceptor存在.如果不是,那么您不会在事务上下文中运行.
| 归档时间: |
|
| 查看次数: |
1956 次 |
| 最近记录: |