【发布时间】:2020-08-22 10:20:17
【问题描述】:
我有一个典型的 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 与 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;
}
【问题讨论】:
-
这是单向还是双向关系?
-
@Lesiak 单向,即 ConfigurationInfoEntry 没有参考 ConfigurationEntry
-
检查范围内是否有事务方面管理的 TransactionStatus:`System.out.println(TransactionAspectSupport.currentTransactionStatus());`。如果没有活动的事务,它会抛出异常。您是否启用了基于注释的事务管理?
-
@Lesiak 不,没有活动事务(抛出 NoTransactionException:范围内没有事务方面管理的 TransactionStatus)。但是应该启用基于注释的事务管理,首先因为这应该是 Spring Boot 默认设置,而且当我在我的应用程序中使用“@EnableTransactionManagement”明确启用它时
-
确实,它是自动配置的。您是否有机会从同一类中的其他方法调用此方法?
标签: java spring hibernate jpa spring-data-jpa