java @Sql失败的SQL脚本:配置的DataSource [*](名为'fooDS')不是与事务管理器[*](名为'fooTM')关联的DataSource [*]

kuhbmx9i  于 2023-04-10  发布在  Java
关注(0)|答案(2)|浏览(79)

更新1(向下滚动)

设置如下:

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

*SCHEMA-有权创建和授予对表和
*APP-被SCHEMA授予以上表使用权限(INSERT、UPDATE、DELETE、SELECT)的用户。

这使我们能够锁定任何模式更改,直到需要为止,因此不会通过应用程序用户发生深刻的更改。
我正在使用一个包含这两个用户的实时Oracle数据库运行集成测试。在类本身上,我使用@SqlConfig(dataSource = "schemaDataSource", transactionManager = "transactionManagerSchema")
在测试方法中,我放置了两个失败的@Sql,因为在SqlScriptsTestExecutionListener类中,事务没有管理相同的数据源(因此下面有错误消息)。
我尝试过手动设置数据源到事务管理器,如下面的配置类所示,然而一些未知的进程似乎每次都覆盖它。(我最好的猜测是通过@DataJpaTest注解,但我不知道11个自动配置中的哪一个,正如你所看到的,我已经禁用了一对夫妇,没有效果)。

  • 测试类别 *:
@RunWith(SpringRunner.class)
@DataJpaTest(excludeAutoConfiguration = {TestDatabaseAutoConfiguration.class, DataSourceAutoConfiguration.class})
@FlywayTest
@SqlConfig(dataSource = TestDataSourceConfig.SCHEMA_DATA_SOURCE, transactionManager = "transactionManagerSchema")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = {TestDataSourceConfig.class, TestFlywayConfig.class})
@EntityScan(basePackageClasses = BaseEnum.class)
public class NotificationTypeEnumTest {

    @Autowired
    private EntityManager em;

    @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);
        NotificationTypeEnum result = query.getSingleResult();
        assertEquals("foo", result.getValue());
        assertEquals(NotificationTypeEnum.DTYPE, result.getConfigType());
    }
}
  • 数据源和TM配置 *:
@Slf4j @Configuration @EnableTransactionManagement
public class TestDataSourceConfig {
    public static final String SCHEMA_DATA_SOURCE = "schemaDataSource";
    public static final String SCHEMA_TRANSACTION_MANAGER = "schemaTransactionManager";

    /*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(name = SCHEMA_DATA_SOURCE) @ConfigurationProperties(prefix = "myapp.datasource.test_schema")
    public DataSource schemaDataSource() { return new DriverManagerDataSource(); }

    @Bean(name = SCHEMA_TRANSACTION_MANAGER) @Autowired
    public PlatformTransactionManager transactionManagerSchema(@Qualifier(SCHEMA_DATA_SOURCE) DataSource dataSource) {
        JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
        jpaTransactionManager.setDataSource(dataSource);
        return jpaTransactionManager;
    }
}

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

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').

当有一个DataSource时,Spring自动配置模型似乎工作正常,但是,一旦有2个或更多,假设就会崩溃,程序员需要手动填充所需配置中的突然(大量)间隙。

我是否错过了一些关于DataSources和TransactionManagers的基本知识?

更新1

经过一些调试我发现在检索TransactionManager以与@Sql脚本注解一起使用时,在我创建的bean上调用了afterPropertiesSet()方法。(即JpaTransactionManager.entityManagerFactory)根据其配置的EntityManagerFactoryInfo.getDataSource()设置数据源。EntityManagerFactory本身是由于调用JpaTransactionManager.setBeanFactory方法而被设置的(因为它实现了BeanFactoryAware)。
下面是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()));
    }
}

然后,我尝试创建自己的EntityManagerFactory bean,试图将其注入到我创建的事务管理器中,但这似乎打开了Hibernate特定的类,我希望在JPA级别保持抽象。

tquggr8v

tquggr8v1#

最后,一个仅JPA的解决方案!

解决方案是使用提供的spring EntityManagerFactoryBuilder组件控制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. 
  }
}

下面是两个数据源的配置。与应用程序相关的DataSource bean在其定义中都有@Primary,以消除任何@Autowired依赖项的歧义。除了通过@DataJpaTest类完成的自动休眠配置之外,不需要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) // 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());
    }
}
  • 值得注意的类:*
  • EntityManagerFactoryBuilder-我不喜欢工厂工厂,但这个工厂工厂很好地帮助我创建了EntityManagerFactory的正确实现,而不依赖于任何hibernate特定的类。可以注入@Autowired。构建器bean本身通过HibernateJpaAutoConfiguration类(扩展JpaBaseConfiguration)(由@DataJpaTest导入)进行配置。
  • JpaProperties-用于在生成的entitymanagerfactories中维护application.properties配置。通过此配置类上面的@EnableConfigurationProperties(JpaProperties.class)注解启用。
  • @PersistenceContext(unitName = "...")-我可以用这个注解在我的测试类中注入正确的EntityManager
hmmo2u0o

hmmo2u0o2#

年份#2023-

org.springframework. Boot :spring-boot-starter-test:jar:2.7.10:test
在我的应用程序中,已经有配置文件,但同样没有加载,导致上述错误。发现HikariConfig正在加载属性为value ie。并试图加载数据库驱动程序为${primary.datasources.abc.driverClassName} ie

配置文件

@Configuration
@EnableJpaRepositories(
entityManagerFactoryRef =“preAlertEntityManagerFactory”,
transactionManagerRef =“preAlertTransactionManager”,
basePackages =“com.abc.repository”

@SuppressWarnings(value = {“unused”})
public class Uncategorized {
@Value(“${主要.数据源.abc.驱动程序类名}”)
private String textName;
@Value(“${primary.datasources.abc.url}”)
public String url;
....

Junit测试文件****为上下文初始化所做的更改(如下所示)

@ActiveProfiles(profiles = {“integration”})// application-integration.yml文件
@Sql(scripts = {“/dml/prealert.sql”})
@SpringBootTest
@DirtiesContext**@ContextConfiguration(
初始化器= {ConfigDataApplicationContextInitializer.class})**
class ABC class{
……
}

相关问题