有些测试用例会修改数据库。如果配置了自动回滚 ,测试用例就可以多次运行。加入此库还有服务在运行,那么也不会影响服务的运行。
之前一直采用注解TransactionConfiguration ,AbstractTransactionalJUnit4SpringContextTests以及注解Transactional实现。
但是发现TransactionConfiguration 已经废弃了,所以采用了新的方式配置。
通过TestExecutionListeners定义事物的listener,通过listener实现功能。
首先定义基类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { TestConfig.class, DatasourceConfig.class })
@TestPropertySource({ "classpath:application.properties" })
@TestExecutionListeners(listeners = { DependencyInjectionTestExecutionListener.class,
TransactionalTestExecutionListener.class })
public class BasicSpringTest {
}
由于项目需求,定义了多个DataSource,transanction manager。每组定义写在不同的config中,并通过ContextConfiguration加入spring。
测试用的链接属性信息都存放在properties文件中,通过TestPropertySource将属性加载。
DependencyInjectionTestExecutionListener 对于test class中的autowired进行注入。
TransactionalTestExecutionListener 开启事物。
然后定义测试类:
public class MonitorServiceTest extends BasicSpringTest {
@Autowired
private MonitorService monitorService;
/**
*
* @author yunzhong
* @time 2017年6月21日上午10:44:27
*/
@Test
@Transactional
@Rollback(true)
public void testInsert() {
}
}
Rollback定义是否回滚。如果参数为true,则不会滚。还有注解@Commit ,等同于Rollback(true).
定义config
@Configuration
@MapperScan(basePackages = {
"cn.das.hive.dao" }, sqlSessionFactoryRef = HiveDatasourceConfig.SQL_SESSION_FACTORY_NAME)
public class HiveDatasourceConfig {
public static final String SQL_SESSION_FACTORY_NAME = "sessionFactoryHive";
public static final String TX_MANAGER = "txManagerHive";
public static final String DATASOURCE_BEAN_NAME = "datasourceHive";
@Bean(name = DATASOURCE_BEAN_NAME)
@Primary
@ConfigurationProperties(prefix = "datasource.hive")
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
return dataSource;
}
@Bean(name = SQL_SESSION_FACTORY_NAME)
@Primary
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
return sessionFactoryBean.getObject();
}
@Bean(name = TX_MANAGER)
@Primary
public DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
}
@Configuration
@ComponentScan(basePackages = "cn", excludeFilters = {})
@MapperScan(basePackages = { "cn.core.*.repository", "cn.das.dao" }, sqlSessionFactoryRef = TestConfig.SQL_SESSION_FACTORY_NAME)
@EnableConfigurationProperties
@ImportResource("classpath:hadoop-conf.xml")
public class TestConfig {
public static final String SQL_SESSION_FACTORY_NAME = "sessionFactoryNew";
public static final String TX_MANAGER = "transactionManager";
private Interceptor pageHelper;
@Value("${zookeeper.address}")
private String address;
@Bean
public static PropertySourcesPlaceholderConfigurer propertiesResolver() {
return new PropertySourcesPlaceholderConfigurer();
}
@Bean(name = "datasourceChorus")
@Primary
@ConfigurationProperties(prefix = "datasource")
@DependsOn("propertiesResolver")
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
return dataSource;
}
@Bean(name = SQL_SESSION_FACTORY_NAME)
@Primary
@DependsOn(value = {"propertiesResolver"})
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource());
sessionFactoryBean.setPlugins(new Interceptor[] { pageHelper });
return sessionFactoryBean.getObject();
}
@Bean(name = TX_MANAGER)
@Primary
@DependsOn("propertiesResolver")
public DataSourceTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
}
有两个DataSource和transanctionmanager,那么listener怎知道使用哪一个呢?这个问题是在使用过程中产生的。
开始的时候transanction并不生效,每次数据库都会变动。查看源码发现,是定位不到使用哪个manager。
这个问题可以通过两种方式解决:
一是采用transanction manager 默认bean的id,另一种方式是transanctional注解设置tm的id。
第一种方式:
首先看TransactionalTestExecutionListener在测试开始之前的准备工作:
public void beforeTestMethod(final TestContext testContext) throws Exception {
final Method testMethod = testContext.getTestMethod();
final Class<?> testClass = testContext.getTestClass();
Assert.notNull(testMethod, "The test method of the supplied TestContext must not be null");
TransactionContext txContext = TransactionContextHolder.removeCurrentTransactionContext();
if (txContext != null) {
throw new IllegalStateException("Cannot start a new transaction without ending the existing transaction.");
}
PlatformTransactionManager tm = null;
TransactionAttribute transactionAttribute = this.attributeSource.getTransactionAttribute(testMethod, testClass);
if (transactionAttribute != null) {
transactionAttribute = TestContextTransactionUtils.createDelegatingTransactionAttribute(testContext,
transactionAttribute);
if (logger.isDebugEnabled()) {
logger.debug("Explicit transaction definition [" + transactionAttribute + "] found for test context "
+ testContext);
}
if (transactionAttribute.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
return;
}
tm = getTransactionManager(testContext, transactionAttribute.getQualifier()); //从beanfactory中获取tm。
}
if (tm != null) { //如果找到tm就开启事物管理,否则就啥也不做了。
txContext = new TransactionContext(testContext, tm, transactionAttribute, isRollback(testContext));
runBeforeTransactionMethods(testContext);
txContext.startTransaction();
TransactionContextHolder.setCurrentTransactionContext(txContext);
}
}
主要是因为getTransactionManager这个方法返回null,所以事物并没有开启。看下getTransactionManager的源码。
protected PlatformTransactionManager getTransactionManager(TestContext testContext, String qualifier) {
// look up by type and qualifier from @Transactional
if (StringUtils.hasText(qualifier)) {
// Use autowire-capable factory in order to support extended qualifier
// matching (only exposed on the internal BeanFactory, not on the
// ApplicationContext).
BeanFactory bf = testContext.getApplicationContext().getAutowireCapableBeanFactory();
return BeanFactoryAnnotationUtils.qualifiedBeanOfType(bf, PlatformTransactionManager.class, qualifier);
}
// else
return getTransactionManager(testContext);
}
TestContextTransactionUtils
public static final String DEFAULT_TRANSACTION_MANAGER_NAME = "transactionManager";
public static PlatformTransactionManager retrieveTransactionManager(TestContext testContext, String name) {
......
return bf.getBean(DEFAULT_TRANSACTION_MANAGER_NAME, PlatformTransactionManager.class);
}
在getTransactionManager的内部调用了TestContextTransactionUtils.retrieveTransactionManager 获得manager。如果没有定义beanname,则采用默认的名字:transactionManager
所以,只要将manager的名字改成transactionManager就可以了。
第二种方式:
在TransactionalTestExecutionListener的源码中,可以看到一行:
TransactionAttribute transactionAttribute = this.attributeSource.getTransactionAttribute(testMethod, testClass);
之后transanction manager的bean id也是从attribute取得。bean id 是通过@Transanctional注解传递的。所以只要给注解添加manager属性,就可以指定manager了。