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的解决方案!
解决方案是使用提供的springEntityManagerFactoryBeans
组件控制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());
}
}