首页 > 解决方案 > 为什么 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);
}

ConfigurationEntrya 具有多对一关系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;
}

标签: javaspringhibernatejpaspring-data-jpa

解决方案


线程中的讨论摘要:

检查是否存在活动的方面管理事务

System.out.println(
  TransactionAspectSupport.currentTransactionStatus()
);

事实证明,没有。上面的代码抛出

NoTransactionException: No transaction aspect-managed TransactionStatus in scope

检查@Transactional 未被观察的原因

正如@EnableTransactionManagementSpring Boot 自动配置的那样,下一个可能性是错误使用 Spring AOP 代理。

Spring AOP 对对象进行了包装,从同一个实例调用时,方法不会被拦截。

情况确实如此。

看到Spring @Transaction方法被同一个类内的方法调用,不行吗?


推荐阅读