java - 为什么 JpaRepositories 似乎使用另一个事务?
问题描述
我有一个典型的 Spring Boot (2.2.5.RELEASE) + Hibernate 应用程序。尝试保存实体时,出现以下异常:
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: org.myorg.ConfigurationInfoEntry
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:127)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:828)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:795)
at org.hibernate.engine.spi.CascadingActions$7.cascade(CascadingActions.java:298)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:490)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:415)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:216)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:149)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:428)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:266)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:196)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:139)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:62)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:804)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:789)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:314)
at com.sun.proxy.$Proxy185.persist(Unknown Source)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:554)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAndFlush(SimpleJpaRepository.java:569)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:371)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:204)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:657)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:621)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:366)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
... 95 common frames omitted
尝试保存是通过以下方法完成的(configurationEntryRepository
是一个 JpaRepository):
@Transactional(rollbackFor = Throwable.class)
public void saveConfiguration(IConfigurationType configurationType) {
final ConfigurationEntry entry = configurationEntryRepository.findByConfigurationType(configurationType);
final ConfigurationEntry configurationEntry = new ConfigurationEntry(entry.getInfoEntry());
configurationEntryRepository.saveAndFlush(configurationEntry);
}
与ConfigurationEntry
a 具有多对一关系ConfigurationInfoEntry
:
@Entity
public final class ConfigurationEntry {
@JoinColumn(name = "INFO_ENTRY")
@ManyToOne(cascade = CascadeType.ALL)
private ConfigurationInfoEntry infoEntry;
}
鉴于我在不明白实体分离的原因之前从数据库中获取了 InfoEntry。正如你所看到的,一切都应该在一个事务中运行,因为该方法saveConfiguration
被适当地注释了。
但是,当我将 ConfigurationEntry 更改为没有持久化的级联时,即更改为:
@Entity
public final class ConfigurationEntry {
@JoinColumn(name = "INFO_ENTRY")
@ManyToOne(cascade = { CascadeType.MERGE, CascadeType.REMOVE, CascadeType.DETACH, CascadeType.REFRESH } )
private ConfigurationInfoEntry infoEntry;
}
一切都按预期工作。这让我相信 JpaRepository 在读取调用和 saveAndFlush() 操作之间使用了两个不同的事务,导致读取后的实体分离。但是,鉴于整个方法都带有事务性注释,我真的不明白为什么。
这是我的数据源和 JPA 相关 bean 的配置:
@Bean(value = "datasource")
public DataSourceFactoryBeanHikari dataSourceFactoryBeanHikari() {
DataSourceFactoryBeanHikari dataSourceFactoryBeanHikari = new DataSourceFactoryBeanHikari();
dataSourceFactoryBeanHikari.setMaxPoolSize(30);
return dataSourceFactoryBeanHikari;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean("entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
em.setPackagesToScan("org.myorg");
final Map<String, Object> properties = new HashMap<>();
properties.put(AvailableSettings.DIALECT, "org.hibernate.dialect.SQLServerDialect");
properties.put(AvailableSettings.URL, "jdbc:sqlserver://localhost:1433");
properties.put(AvailableSettings.SHOW_SQL, true);
properties.put(AvailableSettings.FORMAT_SQL, true);
properties.put(AvailableSettings.HBM2DDL_AUTO, "none");
em.setJpaPropertyMap(properties);
em.setDataSource(dataSource);
em.afterPropertiesSet();
return em;
}
解决方案
线程中的讨论摘要:
检查是否存在活动的方面管理事务
System.out.println(
TransactionAspectSupport.currentTransactionStatus()
);
事实证明,没有。上面的代码抛出
NoTransactionException: No transaction aspect-managed TransactionStatus in scope
检查@Transactional 未被观察的原因
正如@EnableTransactionManagement
Spring Boot 自动配置的那样,下一个可能性是错误使用 Spring AOP 代理。
Spring AOP 对对象进行了包装,从同一个实例调用时,方法不会被拦截。
情况确实如此。
推荐阅读
- android - 将 PagingData 流从 paging3 转换为 PagingData 的 StateFlow
- node.js - 我想知道如何获取与链接日历关联的电子邮件地址
- android - Android - 删除 TableLayout 行之间的空间
- html - 谷歌地方 - 禁用自动完成
- .htaccess - 动态 .htaccess 规则 - 重定向根
- c# - 使用 C# 在 Oracle 数据库上执行 SQL 文件
- matlab - wpspectrum 的输出“Spec”是什么意思?
- docker - 如何运行一个接受多个用户完整输入的 bash 脚本,作为 dockerfile 的一部分
- java - Http 请求的响应不包含全部数据
- react-native - 像 PayTM 这样的 React Native Security Shield