【问题标题】:Why does JpaRepositories seems to use another transaction?为什么 JpaRepositories 似乎使用另一个事务?
【发布时间】: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);
}

ConfigurationEntryConfigurationInfoEntry 具有多对一关系:

@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


【解决方案1】:

话题中的讨论总结:

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

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

原来没有。 上面的代码扔了

NoTransactionException: No transaction aspect-managed TransactionStatus in scope

检查了@Transactional没有被观察到的原因

由于 @EnableTransactionManagement 是由 Spring Boot 自动配置的,下一个可能性是不正确地使用 Spring AOP 代理。

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

确实如此。

Spring @Transaction method call by the method within the same class, does not work?

【讨论】:

    猜你喜欢
    • 2014-01-29
    • 2013-11-26
    • 1970-01-01
    • 1970-01-01
    • 2014-01-04
    • 1970-01-01
    • 2012-07-28
    • 2023-04-06
    • 2023-04-09
    相关资源
    最近更新 更多