如何根据单元测试方法名切换Spring不同数据源?
这个问题我之前也踩过类似的坑,核心问题确实是时机不对——你在onSetUp()里设置数据源标识的时候,Spring的测试上下文已经完成了事务管理器、数据源的初始化绑定,此时再修改DatabaseContextHolder的内容,已经无法影响事务实际使用的数据源了。下面给你几个经过验证的可行方案:
方案一:自定义TestExecutionListener(最推荐)
Spring测试框架提供的TestExecutionListener可以在测试生命周期的关键节点介入,其中beforeTestMethod的执行时机足够早,能在事务初始化前完成数据源标识的设置。
步骤1:实现自定义TestExecutionListener
public class DataSourceRoutingTestListener extends AbstractTestExecutionListener { // 在测试方法执行前设置数据源标识 @Override public void beforeTestMethod(TestContext testContext) throws Exception { Method testMethod = testContext.getTestMethod(); if (testMethod.getName().endsWith("UsingDB2")) { DatabaseContextHolder.setDatabaseType(DType.DB2); } else { DatabaseContextHolder.setDatabaseType(DType.H2); } } // 测试方法执行后清理ThreadLocal,避免污染其他测试 @Override public void afterTestMethod(TestContext testContext) throws Exception { DatabaseContextHolder.clearDatabaseType(); } }
步骤2:在测试类上注册Listener
记得要设置mergeMode,避免覆盖Spring默认的测试监听器(比如事务管理监听器):
@TestExecutionListeners( listeners = DataSourceRoutingTestListener.class, mergeMode = MergeMode.MERGE_WITH_DEFAULTS ) public class MyDBUnitTestCase extends AbstractTransactionalDataSourceSpringContextTests { // 你的原有代码... }
这个方案的优势是完全贴合Spring测试的生命周期,能确保数据源标识在事务初始化前就被正确设置,完美适配你的需求。
方案二:结合JUnit的@Before/@After注解
如果你用的是JUnit 4,可以直接在测试类中定义@Before方法,它的执行时机早于AbstractTransactionalDataSourceSpringContextTests的onSetUp()(事务初始化就在onSetUp()里):
public class MyDBUnitTestCase extends AbstractTransactionalDataSourceSpringContextTests { // 你的原有代码... @Before public void setupDataSourceRouting() { if (getName().endsWith("UsingDB2")) { DatabaseContextHolder.setDatabaseType(DType.DB2); } else { DatabaseContextHolder.setDatabaseType(DType.H2); } } @After public void clearDataSourceRouting() { DatabaseContextHolder.clearDatabaseType(); } }
注意:如果是JUnit 5,把@Before换成@BeforeEach,@After换成@AfterEach即可。
方案三:直接切换事务管理器(备选)
如果RoutingDataSource的方式还是有问题,你可以直接为两个数据源分别配置事务管理器,然后根据测试方法名动态切换使用的事务管理器:
步骤1:配置两个事务管理器
@Bean(name = "h2TransactionManager") public PlatformTransactionManager h2TransactionManager(@Qualifier("h2DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "db2TransactionManager") public PlatformTransactionManager db2TransactionManager(@Qualifier("db2DataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
步骤2:在测试类中动态切换
public class MyDBUnitTestCase extends AbstractTransactionalDataSourceSpringContextTests { @Autowired @Qualifier("h2TransactionManager") private PlatformTransactionManager h2TxManager; @Autowired @Qualifier("db2TransactionManager") private PlatformTransactionManager db2TxManager; @Before public void setupTransactionManager() { if (getName().endsWith("UsingDB2")) { super.setTransactionManager(db2TxManager); } else { super.setTransactionManager(h2TxManager); } } // 你的原有代码... }
这个方案绕过了RoutingDataSource,直接操作事务管理器,适合对RoutingDataSource有兼容性顾虑的场景。
为什么原方案不生效?
再补充解释下核心原因:AbstractTransactionalDataSourceSpringContextTests的onSetUp()方法会先调用父类的逻辑,其中已经完成了事务的初始化(包括从数据源获取连接),之后你才设置DatabaseContextHolder的标识,此时事务已经绑定了初始数据源的连接,后续的操作自然不会切换到目标数据源。
内容的提问来源于stack exchange,提问作者user2649981

