Java @Sql失败的Sql脚本:配置的数据源[*](名为';fooDS';)不是与事务管理器[*](名为';fooTM';)关联的数据源 更新1(向下滚动)

Java @Sql失败的Sql脚本:配置的数据源[*](名为';fooDS';)不是与事务管理器[*](名为';fooTM';)关联的数据源 更新1(向下滚动),java,spring,spring-boot,spring-data-jpa,spring-transactions,Java,Spring,Spring Boot,Spring Data Jpa,Spring Transactions,设置如下: 我们的应用程序数据库由两个独立的用户构建和使用: 架构-有权创建和授予表和应用程序的权限的用户 应用程序-被授予上述表格使用权限(插入、更新、删除、选择)(通过模式)的用户 这使我们能够在需要之前锁定任何模式更改,从而不会通过应用程序用户发生深刻的更改 我正在使用包含这两个用户的live Oracle数据库运行集成测试。在类本身上,我使用@SqlConfig(dataSource=“schemaDataSource”,transactionManager=“transactio

设置如下:

我们的应用程序数据库由两个独立的用户构建和使用:

  • 架构-有权创建和授予表和应用程序的权限的用户
  • 应用程序-被授予上述表格使用权限(插入、更新、删除、选择)(通过模式)的用户
这使我们能够在需要之前锁定任何模式更改,从而不会通过应用程序用户发生深刻的更改


我正在使用包含这两个用户的live Oracle数据库运行集成测试。在类本身上,我使用
@SqlConfig(dataSource=“schemaDataSource”,transactionManager=“transactionManagerSchema”)

在测试方法上,我放置了两个失败的
@Sql
,因为在
sqlscriptstexecutionlistener
类中,事务没有管理相同的数据源。(下面将进一步显示错误消息)

我已尝试将数据源手动设置为事务管理器,如下面的配置类所示,但是每次都有一些未知进程覆盖它。(我最好的猜测是通过
@DataJpaTest
注释,但我不知道到底是哪一个做的,正如您所看到的,我已经禁用了一些,但没有任何效果)

测试等级:

我无法在标题中找到的完整错误是:

java.lang.IllegalStateException: Failed to execute SQL scripts for test context
...
SOME LONG STACK TRACE
...
the configured DataSource [org.springframework.jdbc.datasource.DriverManagerDataSource] (named 'schemaDataSource') is not the one associated with transaction manager [org.springframework.orm.jpa.JpaTransactionManager] (named 'transactionManagerSchema').

当只有一个
数据源时,Spring自动配置模型似乎可以正常工作,但是,一旦有两个或更多数据源,假设就会失效,程序员需要手动填补所需配置中突然出现的(大量)空白

我是否对数据源和事务管理器缺乏一些基本的了解?


更新1 经过一些调试后,我发现在检索TransactionManager以用于
@Sql
脚本注释时,正在对我创建的bean调用
AfterPropertieSet()
方法。这会导致它拥有的任何
EntityManagerFactory
(即
JpaTransactionManager.EntityManagerFactory
)根据其配置的
EntityManagerFactoryInfo.getDataSource()
设置数据源。由于调用了
JpaTransactionManager.setBeanFactory
方法(因为它实现了
BeanFactoryAware
),所以设置了
EntityManagerFactory
本身

以下是spring代码:

// JpaTransactionManager.java
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
    if (getEntityManagerFactory() == null) {
        if (!(beanFactory instanceof ListableBeanFactory)) {
            throw new IllegalStateException("Cannot retrieve EntityManagerFactory by persistence unit name " +
                    "in a non-listable BeanFactory: " + beanFactory);
        }
        ListableBeanFactory lbf = (ListableBeanFactory) beanFactory;
        setEntityManagerFactory(EntityManagerFactoryUtils.findEntityManagerFactory(lbf, getPersistenceUnitName()));
    }
}
然后,我尝试创建自己的EntityManagerFactorybean,试图将其注入到我创建的事务管理器中,但这似乎打开了Hibernate特定的类,我希望在
JPA
级别保持抽象。而且乍一看很难配置。

最后,一个只有JPA的解决方案! 解决方案是使用提供的spring
EntityManagerFactoryBeans
组件控制
EntityManagerFactoryBeans
的创建,并使用
@PersistenceContext
注释将EntityManager注入测试

@SqlConfig(dataSource = TestDataSourceConfig.SCHEMA_DATA_SOURCE, transactionManager = SCHEMA_TRANSACTION_MANAGER, transactionMode = SqlConfig.TransactionMode.ISOLATED)
...
public class MyJUnitTest {
  @PersistenceContext(unitName = "pu")
  private EntityManager em;
  ...

  @Test
  @Sql(statements = {"SOME SQL USING THE PRIVILEGED SCHEMA CONNECTION"}, ...)
  public void myTest() {
    em.createQuery("...").getResultList() // uses the APP database user. 
  }
}
下面是两个数据源的配置。与应用程序相关的数据源bean在其定义中都有
@Primary
,以消除任何
@Autowired
依赖项的歧义。除了通过
@DataJpaTest
类完成的自动Hibernate配置之外,不需要特定于Hibernate的类

@Configuration
@EnableTransactionManagement
@EnableConfigurationProperties(JpaProperties.class)
public class TestDataSourceConfig {

    public static final String SCHEMA_DATA_SOURCE = "schemaDS";
    public static final String SCHEMA_TRANSACTION_MANAGER = "schemaTM";
    public static final String SCHEMA_EMF = "schemaEMF";

    /*Main Datasource and supporting beans*/

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        return new DriverManagerDataSource();
    }

    @Bean @Primary @Autowired
    public PlatformTransactionManager transactionManager(EntityManagerFactory emf) { return new JpaTransactionManager(emf); }

    @Bean @Primary
    public LocalContainerEntityManagerFactoryBean emfBean(
            EntityManagerFactoryBuilder entityManagerFactoryBuilder,
            DataSource datasource,
            JpaProperties jpaProperties) {
        return entityManagerFactoryBuilder
                .dataSource(datasource)
                .jta(false)
                .packages(CourseOffering.class)
                .persistenceUnit("pu")
                .properties(jpaProperties.getProperties())
                .build();
    }

    @Bean(name = SCHEMA_EMF)
    public LocalContainerEntityManagerFactoryBean emfSchemaBean(
            EntityManagerFactoryBuilder entityManagerFactoryBuilder,
            @Qualifier(SCHEMA_DATA_SOURCE) DataSource schemaDataSource,
            JpaProperties jpaProperties) {
        return entityManagerFactoryBuilder
                .dataSource(schemaDataSource)
                .jta(false)
                .packages(CourseOffering.class)
                .persistenceUnit("spu")
                .properties(jpaProperties.getProperties())
                .build();
    }

    @Bean(name = SCHEMA_DATA_SOURCE)
    @ConfigurationProperties(prefix = "myapp.datasource.test_schema")
    public DataSource schemaDataSource() { return new DriverManagerDataSource(); }

    @Bean(name = SCHEMA_TRANSACTION_MANAGER)
    public PlatformTransactionManager transactionManagerSchema(
            @Qualifier(SCHEMA_EMF) EntityManagerFactory emfSchemaBean) {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
        jpaTransactionManager.setEntityManagerFactory(emfSchemaBean);
        return jpaTransactionManager;
    }
}
实际测试等级:

@RunWith(SpringRunner.class)//所有spring测试都需要
@DataJpaTest(excludeAutoConfiguration={TestDatabaseAutoConfiguration.class,DataSourceAutoConfiguration.class})//这将停止配置默认数据源和数据库。
@SqlConfig(dataSource=TestDataSourceConfig.SCHEMA\u DATA\u SOURCE,transactionManager=SCHEMA\u TRANSACTION\u MANAGER,transactionMode=SqlConfig.transactionMode.ISOLATED)//确保使用SCHEMA datasource和txManager以隔离的方式运行@Sql语句,以免在运行要求运行这些语句的测试方法时出现问题。
@SpringBootTest(webEnvironment=SpringBootTest.webEnvironment.NONE,class={TestDataSourceConfig.class})
@TestExecutionListeners({
sqlscriptstexecutionListener.class,//使@Sql脚本注释能够工作。
SpringBootDependencyInjectionTestExecutionListener.class,//将spring组件注入测试(即EntityManager)
TransactionalTestExecutionListener.class})//尽管@Transactional注释还不存在,但我在这里有这些注释,因为我计划在进一步的测试中使用它们。
公共类NotificationTypeEnumTest{
@PersistenceContext(unitName=“pu”)//需要注入正确的EntityManager
私人实体管理者;
//这些声明是
@试验
@Sql(语句={“插入MYAPP\u枚举(枚举ID、\“类型\”、\“值\”)值(MYAPP\u枚举ID、“+NotificationTypeEnum.DTYPE+”、“foo”)”),executionPhase=Sql.executionPhase.BEFORE\u TEST\u方法)
@Sql(语句={“从MYAPP_ENUM删除”},executionPhase=Sql.executionPhase.AFTER_TEST_方法)
public void canFetchNotificationTypeEnum()引发异常{
TypedQuery query=em.createQuery(“从NotificationTypeEnum a中选择一个”,NotificationTypeEnum.class);//通知类型只是BaseEnum类型的一个子类
NotificationTypeEnum结果=query.getSingleResult();
assertEquals(“foo”,result.getValue());
assertEquals(NotificationTypeEnum.DTYPE,result.getConfigType());
}
}

值得注意的类别:

  • EntityManagerFactoryBuilder
    -我不喜欢工厂,但这一个很好地帮助我创建了EntityManagerFac的正确实现
    @SqlConfig(dataSource = TestDataSourceConfig.SCHEMA_DATA_SOURCE, transactionManager = SCHEMA_TRANSACTION_MANAGER, transactionMode = SqlConfig.TransactionMode.ISOLATED)
    ...
    public class MyJUnitTest {
      @PersistenceContext(unitName = "pu")
      private EntityManager em;
      ...
    
      @Test
      @Sql(statements = {"SOME SQL USING THE PRIVILEGED SCHEMA CONNECTION"}, ...)
      public void myTest() {
        em.createQuery("...").getResultList() // uses the APP database user. 
      }
    }
    
    @Configuration
    @EnableTransactionManagement
    @EnableConfigurationProperties(JpaProperties.class)
    public class TestDataSourceConfig {
    
        public static final String SCHEMA_DATA_SOURCE = "schemaDS";
        public static final String SCHEMA_TRANSACTION_MANAGER = "schemaTM";
        public static final String SCHEMA_EMF = "schemaEMF";
    
        /*Main Datasource and supporting beans*/
    
        @Bean
        @Primary
        @ConfigurationProperties(prefix = "spring.datasource")
        public DataSource dataSource() {
            return new DriverManagerDataSource();
        }
    
        @Bean @Primary @Autowired
        public PlatformTransactionManager transactionManager(EntityManagerFactory emf) { return new JpaTransactionManager(emf); }
    
        @Bean @Primary
        public LocalContainerEntityManagerFactoryBean emfBean(
                EntityManagerFactoryBuilder entityManagerFactoryBuilder,
                DataSource datasource,
                JpaProperties jpaProperties) {
            return entityManagerFactoryBuilder
                    .dataSource(datasource)
                    .jta(false)
                    .packages(CourseOffering.class)
                    .persistenceUnit("pu")
                    .properties(jpaProperties.getProperties())
                    .build();
        }
    
        @Bean(name = SCHEMA_EMF)
        public LocalContainerEntityManagerFactoryBean emfSchemaBean(
                EntityManagerFactoryBuilder entityManagerFactoryBuilder,
                @Qualifier(SCHEMA_DATA_SOURCE) DataSource schemaDataSource,
                JpaProperties jpaProperties) {
            return entityManagerFactoryBuilder
                    .dataSource(schemaDataSource)
                    .jta(false)
                    .packages(CourseOffering.class)
                    .persistenceUnit("spu")
                    .properties(jpaProperties.getProperties())
                    .build();
        }
    
        @Bean(name = SCHEMA_DATA_SOURCE)
        @ConfigurationProperties(prefix = "myapp.datasource.test_schema")
        public DataSource schemaDataSource() { return new DriverManagerDataSource(); }
    
        @Bean(name = SCHEMA_TRANSACTION_MANAGER)
        public PlatformTransactionManager transactionManagerSchema(
                @Qualifier(SCHEMA_EMF) EntityManagerFactory emfSchemaBean) {
            JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
            jpaTransactionManager.setEntityManagerFactory(emfSchemaBean);
            return jpaTransactionManager;
        }
    }
    
    @RunWith(SpringRunner.class) // required for all spring tests
    @DataJpaTest(excludeAutoConfiguration = {TestDatabaseAutoConfiguration.class, DataSourceAutoConfiguration.class}) // this stops the default data source and database being configured.
    @SqlConfig(dataSource = TestDataSourceConfig.SCHEMA_DATA_SOURCE, transactionManager = SCHEMA_TRANSACTION_MANAGER, transactionMode = SqlConfig.TransactionMode.ISOLATED) // make sure the @Sql statements are run using the SCHEMA datasource and txManager in an isolated way so as not to cause problems when running test methods requiring these statements to be run.
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = {TestDataSourceConfig.class})
    @TestExecutionListeners({ 
        SqlScriptsTestExecutionListener.class, // enables the @Sql script annotations to work.
        SpringBootDependencyInjectionTestExecutionListener.class, // injects spring components into the test (i.e. the EntityManager)
        TransactionalTestExecutionListener.class}) // I have this here even though the @Transactional annotations don't exist yet as I plan on using them in further tests.
    public class NotificationTypeEnumTest {
    
        @PersistenceContext(unitName = "pu") // required to inject the correct EntityManager
        private EntityManager em;
    
        // these statements are 
        @Test
        @Sql(statements = {"INSERT INTO MYAPP_ENUM (ENUM_ID, \"TYPE\", \"VALUE\") VALUES (MYAPP_ENUM_ID_SEQ.nextval, '" + NotificationTypeEnum.DTYPE + "', 'foo')"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
        @Sql(statements = {"DELETE FROM MYAPP_ENUM"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
        public void canFetchNotificationTypeEnum() throws Exception {
            TypedQuery<NotificationTypeEnum> query = em.createQuery("select a from NotificationTypeEnum a", NotificationTypeEnum.class); // notification type is just a subclass of the BaseEnum type
            NotificationTypeEnum result = query.getSingleResult();
            assertEquals("foo", result.getValue());
            assertEquals(NotificationTypeEnum.DTYPE, result.getConfigType());
        }
    }