Reputation: 6218
I have a typical Spring Boot (2.2.5.RELEASE) + Hibernate application. When trying to save an entity I get the following exception:
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
Trying to save is done in the following method (configurationEntryRepository
is a 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);
}
The ConfigurationEntry
has a Many-To-One relationsship to a ConfigurationInfoEntry
:
@Entity
public final class ConfigurationEntry {
@JoinColumn(name = "INFO_ENTRY")
@ManyToOne(cascade = CascadeType.ALL)
private ConfigurationInfoEntry infoEntry;
}
Given that I fetch the InfoEntry from the database before I don't get why the entity is detached. As you can see, everything should be running within one transaction, as the method saveConfiguration
is appropriatly annotated.
However, when I change ConfigurationEntry to cascade without persist, i.e. changing to:
@Entity
public final class ConfigurationEntry {
@JoinColumn(name = "INFO_ENTRY")
@ManyToOne(cascade = { CascadeType.MERGE, CascadeType.REMOVE, CascadeType.DETACH, CascadeType.REFRESH } )
private ConfigurationInfoEntry infoEntry;
}
everything works as expected. This leads me to believe that the JpaRepository uses two different transactions between the read call and the saveAndFlush() operation, leading to a detached entity after the read. However, given that the whole method is annotated with transactional, I don't really get why.
Here is the configuration for my data source and JPA related beans:
@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;
}
Upvotes: 0
Views: 744
Reputation: 25946
Summary of the discussion in thread:
Checked if there is an active aspect-managed transaction
System.out.println(
TransactionAspectSupport.currentTransactionStatus()
);
It turned out that there is none. The code above threw
NoTransactionException: No transaction aspect-managed TransactionStatus in scope
Checked the reasons of @Transactional not being observed
As @EnableTransactionManagement
is auto-configured by Spring Boot, the next possibility was incorrect use of Spring AOP proxies.
Spring AOP wraps the object, and the methods are not intercepted when called from the same instance.
This was indeed the case.
See Spring @Transaction method call by the method within the same class, does not work?
Upvotes: 2