两个 EntityManagerFactories,TransactionRequiredException:没有事务正在进行

Two EntityManagerFactories, TransactionRequiredException: no transaction is in progress

Spring MVC 应用程序使用 SpringDataJPA 和休眠。生产模式利用 MySQL 但对于 单元测试,我将 H2DB 设置为 运行,两者都带有单独的 java配置。该应用程序还利用 Spring 安全性,但我在下面省略了它的配置。

应用程序从单元测试开始,但在第一次测试时(testOIC() 参见下面的代码)我得到了 "TransactionRequiredException: no transaction is in progress"

 testException = org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress, mergedContextConfiguration = [WebMergedContextConfiguration@1c581a0 testClass = OpenPositionsServiceTest, locations = '{}', classes = '{class our.dcollect.configuration.AppConfiguration, class our.dcollect.configuration.HibernateConfigurationTest}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.test.context.web.WebDelegatingSmartContextLoader', parent = [null]]].

单元测试我运行用Propagation.REQUIRES_NEW,所以它应该有事务上下文。恐怕我在应用上下文中搞砸了一些东西。我找到的与此主题相关的帖子没有太大帮助。

ApplicationContext 包含以下 bean: Spring ApplicationContext (applicationContext.getBeanDefinitionNames()) 中的 Bean 定义名称:

===================================================
Bean: appConfiguration
Bean: authenticationManagerBuilder
Bean: autowiredWebSecurityConfigurersIgnoreParents
Bean: beanNameHandlerMapping
Bean: dataSource
Bean: dataSourceTest
Bean: defaultServletHandlerMapping
Bean: delegatingApplicationListener
Bean: dtoConverter
Bean: emBeanDefinitionRegistrarPostProcessor
Bean: enableGlobalAuthenticationAutowiredConfigurer
Bean: entityManagerFactory
Bean: entityManagerFactoryTest
Bean: fileUploadController
Bean: fileUploadService
Bean: getInternalResourceViewResolverJsp
Bean: handlerExceptionResolver
Bean: hibernateConfiguration
Bean: hibernateConfigurationTest
Bean: hibernateProperties
Bean: httpRequestHandlerAdapter
Bean: initializeAuthenticationProviderBeanManagerConfigurer
Bean: initializeUserDetailsBeanManagerConfigurer
Bean: jpaContext
Bean: jpaMappingContext
Bean: jpaVendorAdapter
Bean: jpaVendorAdapterTest
Bean: multipartResolver
Bean: mvcContentNegotiationManager
Bean: mvcConversionService
Bean: mvcPathMatcher
Bean: mvcResourceUrlProvider
Bean: mvcUriComponentsContributor
Bean: mvcUrlPathHelper
Bean: mvcValidator
Bean: mvcViewResolver
Bean: objectPostProcessor
Bean: openIn....Repository
Bean: openPositionsService
Bean: org.springframework.aop.config.internalAutoProxyCreator
Bean: org.springframework.context.annotation.ConfigurationClassPostProcessor.enhancedConfigurationProcessor
Bean: org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor
Bean: org.springframework.context.annotation.internalAutowiredAnnotationProcessor
Bean: org.springframework.context.annotation.internalCommonAnnotationProcessor
Bean: org.springframework.context.annotation.internalConfigurationAnnotationProcessor
Bean: org.springframework.context.annotation.internalPersistenceAnnotationProcessor
Bean: org.springframework.context.annotation.internalRequiredAnnotationProcessor
Bean: org.springframework.context.event.internalEventListenerFactory
Bean: org.springframework.context.event.internalEventListenerProcessor
Bean: org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension#0
Bean: org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport_Predictor
Bean: org.springframework.orm.jpa.SharedEntityManagerCreator#0
Bean: org.springframework.orm.jpa.SharedEntityManagerCreator#1
Bean: org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
Bean: org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration
Bean: org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration
Bean: org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration
Bean: org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration
Bean: org.springframework.transaction.config.internalTransactionAdvisor
Bean: org.springframework.transaction.config.internalTransactionalEventListenerFactory
Bean: org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration
Bean: privilegeEvaluator
Bean: push....Repository
Bean: requestDataValueProcessor
Bean: requestMappingHandlerAdapter
Bean: requestMappingHandlerMapping
Bean: resourceHandlerMapping
Bean: securityConfiguration
Bean: sessionFactory
Bean: sessionFactoryTest
Bean: simpleControllerHandlerAdapter
Bean: springSecurityFilterChain
Bean: transactionAttributeSource
Bean: transactionInterceptor
Bean: transactionManager
Bean: transactionManagerTest
Bean: viewControllerHandlerMapping
Bean: webSecurityExpressionHandler
===================================================

你能帮忙吗?相关代码部分我贴在下面。

MVC 配置:

package our.dcollect.configuration;

import org.apache.log4j.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;


@EnableWebMvc
@Configuration
@ComponentScan(basePackages = "our.dcollect")
public class AppConfiguration {

    private static final Logger log = Logger.getLogger(AppConfiguration.class);

    @Bean
    public InternalResourceViewResolver getInternalResourceViewResolverJsp(){
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/view/");
        viewResolver.setSuffix(".jsp");
        viewResolver.setOrder(0);
        log.info("####   Internal view resolver 0 called...");
        return viewResolver;
    }

    @Bean
    public StandardServletMultipartResolver multipartResolver(){
        log.info("####   Multipart resolver called...");
        return new StandardServletMultipartResolver();
    }        
}

休眠配置:

package our.dcollect.configuration;

import java.util.Properties;

import javax.sql.DataSource;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan(basePackages = "our.dcollect")
@EnableJpaRepositories(basePackages = {"our.dcollect.repository"},  
        entityManagerFactoryRef = "entityManagerFactory",
        transactionManagerRef = "transactionManager")
@PropertySource(value = { "classpath:application.properties" })
@EnableTransactionManagement
//@PropertySource({ "/resources/hibernate.properties" })
public class HibernateConfiguration {

    @Autowired
    private Environment environment;

    @Bean(name="sessionFactory")
    public LocalSessionFactoryBean sessionFactory() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSource());
        sessionFactory.setPackagesToScan(new String[] { "our.dcollect" });
        sessionFactory.setHibernateProperties(this.hibernateProperties());
        return sessionFactory;
     }

    @Bean(name="dataSource")
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
        dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
        dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
        return dataSource;
    }

    @Bean(name="hibernateProperties")
    public Properties hibernateProperties() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
        properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
        properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
        properties.put("hibernate.hbm2ddl.auto", "create-drop");
        return properties;        
    }

    @Bean(name="transactionManager")
    @Autowired
    public HibernateTransactionManager transactionManager(@Qualifier("sessionFactory") SessionFactory s) {
       HibernateTransactionManager txManager = new HibernateTransactionManager();
       txManager.setSessionFactory(s);
       return txManager;
    }

    @Bean(name="jpaVendorAdapter")
    public JpaVendorAdapter jpaVendorAdapter() {
            HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
            hibernateJpaVendorAdapter.setShowSql(true);
            hibernateJpaVendorAdapter.setGenerateDdl(true);
            hibernateJpaVendorAdapter.setDatabase(Database.MYSQL);
            return hibernateJpaVendorAdapter;
    }

    @Bean(name="entityManagerFactory")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
            LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
            lef.setDataSource(this.dataSource());
            lef.setJpaProperties(this.hibernateProperties());
            lef.setJpaVendorAdapter(this.jpaVendorAdapter());
            lef.setPackagesToScan(new String[] { "our.dcollect.model"});
            return lef;
    }

}

单元测试的 Hibernate 配置:

package our.dcollect.configuration;

import java.util.Properties;

import javax.sql.DataSource;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/***************************************************************************************
* The same as the real HibernateConfiguration but this works with H2 DB instead of MySQL.
* In addition, the properties are not read from a property file.
****************************************************************************************/
@Configuration
@ComponentScan(basePackages = "our.dcollect")
@EnableJpaRepositories(basePackages = {"our.dcollect.repository"},  
        entityManagerFactoryRef = "entityManagerFactoryTest",
        transactionManagerRef = "transactionManagerTest")
@PropertySource(value = { "classpath:application.properties" })
@EnableTransactionManagement
public class HibernateConfigurationTest {


    @Bean(name = "sessionFactoryTest")
    public LocalSessionFactoryBean sessionFactoryTest() {
        LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
        sessionFactory.setDataSource(dataSourceTest());
        sessionFactory.setPackagesToScan(new String[] { "our.dcollect" });
        sessionFactory.setHibernateProperties(hibernatePropertiesTest());
        return sessionFactory;
    }

    @Bean(name = "dataSourceTest")
    public DataSource dataSourceTest() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
        dataSource.setUsername("...");
        dataSource.setPassword("...");
        return dataSource;
    }

    private Properties hibernatePropertiesTest() {
        Properties properties = new Properties();
        properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        properties.put("hibernate.hbm2ddl.auto", "create-drop");
        return properties;
    }

    @Bean(name = "transactionManagerTest")
    @Autowired
    public HibernateTransactionManager transactionManagerTest(@Qualifier("sessionFactoryTest") SessionFactory s) {
        HibernateTransactionManager txManager = new HibernateTransactionManager();
        txManager.setSessionFactory(s);
        return txManager;
    }


    @Bean(name = "jpaVendorAdapterTest")
    public JpaVendorAdapter jpaVendorAdapterTest() {
            HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
            hibernateJpaVendorAdapter.setShowSql(true);
            hibernateJpaVendorAdapter.setGenerateDdl(true);
            hibernateJpaVendorAdapter.setDatabase(Database.H2);
            return hibernateJpaVendorAdapter;
    }

    @Bean(name = "entityManagerFactoryTest")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryTest() {
            LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
            lef.setDataSource(this.dataSourceTest());
            lef.setJpaProperties(this.hibernatePropertiesTest());
            lef.setJpaVendorAdapter(this.jpaVendorAdapterTest());
            lef.setPackagesToScan(new String[] { "our.dcollect.model"});
            return lef;
    }

}    

单元测试class 为易读而缩写:

package our.dcollect.service;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.junit.After;
import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import our.dcollect.configuration.AppConfiguration;
import our.dcollect.configuration.HibernateConfigurationTest;
import our.dcollect.model.OpenIC;
import our.dcollect.repository.OpenICRepository;

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {AppConfiguration.class, HibernateConfigurationTest.class})
public class OpenPositionsServiceTest {

    @Autowired
    private OpenICRepository OpenICDAO;

    @PersistenceContext(unitName  = "entityManagerFactoryTest")
    private EntityManager entityManager;



    @Test
    @Transactional(value="transactionManagerTest", propagation = Propagation.REQUIRES_NEW)
    public void testOIC() {
        System.out.println("getOpenPositionsNotRegisteredInPushBanch");

        OpenIC oic = new OpenIC();
        oic.setBaId("111");
        oic.setBezeichnung("abc");

        // clear the persistence context so we don't return the previously cached location object
        // this is a test only thing and normally doesn't need to be done in prod code
        entityManager.clear();

        OpenICDAO.saveAndFlush(oic);
        List<OpenIC> list = OpenICDAO.findAll();
        assertEquals(list.size(), 1);

        OpenIC oicReadBack = list.get(0);
        OpenICDAO.delete(oicReadBack.getOpenICId());

    }
[...]    
}

解决步骤:


Funtik 的帖子帮了大忙。我执行的以下设置:

  1. @DependsOn("transactionManagerTest") 告诉 Spring
    EntityManagerFactory需要在TransactionManager之后加载 但是并没有解决问题。
  2. 为 HibernateConfigurationTest 引入 @Profile("test") 和为 HibernateConfiguration 引入 @Profile("!test")。考试中 class 我把@ActiveProfiles("test")。这把豆子分开了; 测试期间只加载了一种口味: 休眠配置测试。然而这并没有解决 也有问题。

  3. 然后我在一个单独的分支中完全删除了一个配置文件,看看数据库的两个配置是否会导致问题。我
    一个数据库配置遇到与以前相同的问题。

  4. 我添加了 @TestExecutionListeners({TransactionalTestExecutionListener.class,
    DependencyInjectionTestExecutionListener.class}) 单元测试
    class
    @Rollback(false)到测试方法看是否 任何东西都保存在 MySQL 数据库中。我也改了
    "hibernate.hbm2ddl.auto"到"create",保留
    之后的DB 测试。它改变了情况,因为消息出现在日志中:
    "[...] 开始测试上下文的事务 (1)
    [DefaultTestContext@a15b73 [...]”。所以一个事务上下文有
    绝对已创建并已进行 1 次交易。问题是
    但是 实体没有保存在数据库中 如果我调用 OpenICDAO.save(oic) 在测试中。另外,调用
    OpenICDAO.saveAndFlush(oic) 几乎导致以下异常:

"[...] 4671 [main] WARN org.springframework.test.context.TestContextManager - 在允许 TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener@45c9b3] 处理 'after' 测试执行时捕获异常:方法 [public void our.dcollect.service.OpenPositionsServiceTest.testGetOpenPositionsNotRegisteredInPushBanch()], instance [our.dcollect.service.OpenPositionsServiceTest@dc7b7d], exception [org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception是 javax.persistence.TransactionRequiredException: no transaction正在处理] org.springframework.transaction.UnexpectedRollbackException: 事务已回滚,因为它已被标记为仅回滚 [...]"

Tests in error:

  testOIC(our.dcollect.service.OpenPositionsServiceTest): no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress
  testOIC(our.dcollect.service.OpenPositionsServiceTest): Transaction rolled back because it has been marked as rollback-only

Tests run: 3, Failures: 0, Errors: 2, Skipped: 0

但是,我只有 2 种方法,而不是 3 种,其中 testOIC 被报告了两次。


解法:

transactionManager 必须是 PlatformTransactionManager 托管 JpaTransactionManager。这解决了问题。感谢您的所有评论。我发现 @Profile 注释非常有用,并且发现在应用程序上下文中列出 beans 的想法。有了正确的transactionManager后,注解DependsOn()造成了循环引用,所以去掉了; @TestExecutionListeners 在这种情况下也不需要注释。我采用了一个简单的演示应用程序,将其更改为我的配置并尝试重现原始故障。能够重现后,我检查了好状态和坏状态之间的差异,这有助于找到差异。 (对于需要 SessionFactoryTest 对象的 transactionManagerTest 也需要做同样的事情。)

//    @Bean(name="transactionManager")
//    @Autowired
//    public HibernateTransactionManager transactionManager(@Qualifier("sessionFactory") SessionFactory s) {
//       HibernateTransactionManager txManager = new HibernateTransactionManager();
//       txManager.setSessionFactory(s);
//       return txManager;
//    }

    @Bean(name="transactionManager")
    @Autowired
    public PlatformTransactionManager transactionManager(@Qualifier("sessionFactory") SessionFactory s) {
        return new JpaTransactionManager( entityManagerFactory().getObject() );
    }

我遇到了同样的问题。根据我的经验,entityManager bean 似乎是在 transactionManager bean 之前加载的,因此 spring 根本感觉不到任何事务的存在。

@DependsOn 注释为我解决了这个问题。尝试像这样设置对 TransactionManager 的显式依赖:

    @DependsOn("transactionManagerTest")
    @Bean(name = "entityManagerFactoryTest")
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryTest() {

我把原post的解决方案加在最下面

我在测试期间尝试保留实体时 运行 遇到了这个问题。

并按照您的解决方案尝试解决:

"The transactionManager must be a PlatformTransactionManager hosting a JpaTransactionManager"

将 HibernateTransactionManager 更改为 JpaTransactionManager

    @Bean
    public JpaTransactionManager transactionManager(EntityManagerFactory s) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(s);
        return txManager;
    }

对您的解决方案有效感到非常惊讶。如果有人可以指出我描述的文档,那就太好了。 谢谢。