当前位置: 首页 > 知识库问答 >
问题:

@Sql失败的Sql脚本:配置的数据源[*](名为“fooDS”)不是与事务管理器[*](名为“fooTM”)关联的数据源

孟浩然
2023-03-14

设置如下:

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

  • ScheMA-有权创建和授予对表和
  • 的权限的用户
  • APP-被授予要使用的上述表的权限(INSERT、UPDATE、DELETE、SELECT)的用户。

这使我们能够在需要之前锁定任何模式更改,从而不会通过应用程序用户发生深刻的更改。

我正在对包含这两个用户的live 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个或更多,假设就会崩溃,程序员需要手动填补所需配置中的突然(大量)空白。

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

经过一些调试,我发现当检索TransactionManager以与@Sql脚本注释一起使用时,我创建的bean上调用了后属性集()方法。这会导致它拥有的任何EntityManagerFactory(即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级别的抽象。乍一看很难配置。

共有1个答案

公羊子真
2023-03-14

解决方案是使用提供的spring组件控制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在其定义中都有主bean,以消除任何自连线依赖项的歧义。除了通过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) // 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());
    }
}

值得注意的类别:

  • entitymanagerfactorbuilder-我不喜欢工厂工厂,但这一个很好地帮助我创建了EntityManagerFactory的正确实现,而不依赖于任何特定于hibernate的类。可注入自动接线。生成器bean本身是通过hibernatejpaaautoconfiguration类(扩展JpaBaseConfiguration)配置的(由DataJpaTest导入)
 类似资料:
  • 问题内容: 当我们使用Hibernate配置数据源时,我们应该添加属性(或者如果您正在使用EclipseLink)。 我想知道 方言 是什么意思?我根据Hibernate的文档配置了此属性,但我不知道它的含义是什么。 问题答案: 方言的意思是“一种语言的变体”。众所周知,Hibernate与数据库无关。它可以与不同的数据库一起使用。但是,数据库具有专有的扩展名/本机SQL变体,以及SQL标准实现的

  • 根据这里的baeldung 如果我们使用的是Spring Boot项目,并且在类路径上有spring-data-*或spring-tx>依赖项,那么事务管理将通过>default启用。“ serviceConfig.java 哪里出了问题?,我在SpringBoot配置中遗漏了什么? 提前感谢你的帮助

  • CREATE TABLE充当来自CSVREAD的SELECT*('c://users/h/downloads/SERVES.csv');SQL语句“create TABLE SERVES AS SELECT*FROM CSVREAD([*]'c://users/h/downloads/SERVES.csv')”中的语法错误;SQL语句:CREATE TABLE充当从CSVREAD中选择*('c:/

  • 我有一个Spring mvc rest webservice,它带有一个保存数据源地图的数据源管理器bean。每个客户都有自己的数据库,因此也有自己的数据源。webservice以编程方式从DataSourceManager加载客户数据源,如果不存在,则创建一个新的。 通过这种实现,可以使用注释驱动的事务管理吗?我阅读的所有示例都在配置文件中声明了一个或多个数据源。

  • 我正在开发一个应用程序,其中我必须根据从客户端传递的客户id连接到不同的数据库。所有数据库的模式都是相同的。它是一种多租户应用程序。由于我不知道会有多少客户,我无法使用xml配置静态创建数据源,因此我必须手动创建数据源。 我们使用Spring JdbcTemplate连接到数据库,连接参数来自另一个保存应用程序配置的数据库。我能够正确连接到数据库,但方法调用不会在事务中发生。下面的代码片段只对一个

  • 问题内容: 我可以使用此查询找出重复的数据 我能够获取重复数据。我只需要知道如何使用名称将重复数据重命名为 new 问题答案: 假设您在表上有某种主键,例如自动增量ID,则可以执行以下操作。 为了说明,它将查找重复的任何内容,为该集中的所有内容获取最大ID,并在其末尾附加“副本1”。如果您有3次或多次使用某些名称,则可能仍会留下一些重复项。只需再次运行它,这次使用“副本2”而不是“副本1”。继续重