@Sql 失败的 SQL 脚本:配置的数据源 [*](名为“fooDS”)不是与事务管理器 [*](名为“fooTM”)关联的那个
Posted
技术标签:
【中文标题】@Sql 失败的 SQL 脚本:配置的数据源 [*](名为“fooDS”)不是与事务管理器 [*](名为“fooTM”)关联的那个【英文标题】:@Sql Failed SQL scripts: The configured DataSource [*] (named 'fooDS') is not the one associated with transaction manager [*] (named 'fooTM') 【发布时间】:2018-03-08 11:52:18 【问题描述】:更新 1(向下滚动)
设置如下:
我们的应用程序数据库由两个不同的用户构建和使用:
SCHEMA - 有权创建和授予表权限的用户和 APP - 被授予权限(INSERT、UPDATE、DELETE、SELECT)的用户(由 SCHEMA)使用上述表格。这使我们能够在需要之前锁定任何架构更改,因此不会通过应用用户发生重大更改。
我正在使用包含这两个用户的实时 Oracle 数据库运行集成测试。在课堂上,我使用@SqlConfig(dataSource = "schemaDataSource", transactionManager = "transactionManagerSchema")
。
在测试方法中,我放置了两个失败的@Sql
,因为在SqlScriptsTestExecutionListener
类中,事务没有管理相同的数据源。 (因此错误消息在下面)。
我已尝试手动将数据源设置为事务管理器,如下面的配置类所示,但似乎每次都有一些未知进程覆盖它。 (我最好的猜测是通过@DataJpaTest
注释,但我不知道究竟是哪个11 Auto Configurations 做到了,你可以看到我已经禁用了一对没有任何效果的)。
测试类:
@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 个或更多,假设就会失效,程序员需要手动填写突然(大量) 需要配置差距。
我是否缺少对 DataSource 和 TransactionManagers 的一些基本了解?
更新 1
经过一些调试,我发现在检索 TransactionManager 以与 @Sql
脚本注释一起使用时,我创建的 bean 上调用了 afterPropertiesSet()
方法。这会导致它拥有的任何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】:最后,只有 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
类完成的自动休眠配置之外,不需要休眠特定的类。
@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 的正确实现方面为我提供了很好的帮助,而不依赖于任何休眠特定类。可以注入@Autowired
。 builder bean 本身是通过HibernateJpaAutoConfiguration
类(扩展JpaBaseConfiguration
)(由@DataJpaTest
导入)配置的。
JpaProperties
- 用于在生成的 entitymanagerfactories 中维护 application.properties
配置。通过此配置类上方的 @EnableConfigurationProperties(JpaProperties.class)
注释启用。
@PersistenceContext(unitName = "...")
- 我可以用这个注解在我的测试类中注入正确的EntityManager
。
【讨论】:
以上是关于@Sql 失败的 SQL 脚本:配置的数据源 [*](名为“fooDS”)不是与事务管理器 [*](名为“fooTM”)关联的那个的主要内容,如果未能解决你的问题,请参考以下文章
相同的 SQl 查询在访问中运行,但在使用 java 的访问中的 sql 脚本中失败