Spring多数据源事务配置与回滚测试实战
在Spring项目中配置多数据源并保证事务的正确性,是开发中常见的需求,也是容易踩坑的环节。本文将从多数据源配置、事务管理器绑定、事务回滚测试三个维度,完整讲解Spring多数据源事务的实现与验证,同时揭示常见的事务失效问题及解决方案。
一、场景背景
在实际开发中,我们可能需要同时操作多个数据库(如本文中的java库和sql50库),每个数据库需要独立的数据源和事务管理器,且要保证事务的原子性(异常时能正确回滚)。本文将基于Spring Boot + JdbcTemplate实现多数据源配置,并通过测试验证事务回滚效果。
二、核心配置实现
2.1 多数据源与事务管理器配置
首先创建数据源配置类,分别配置两个数据源、对应的JdbcTemplate和事务管理器,注意每个事务管理器必须绑定对应的数据源。
package org.example.multidb.db;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
@Configuration
public class DatasourceConfig {
// 主数据源(db1:java库),@Primary标注默认数据源
@Bean("db1")
@Primary
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/java");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
// db1对应的JdbcTemplate
@Bean("db1JdbcTemplate")
public JdbcTemplate jdbcTemplate(@Qualifier("db1") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// db1对应的事务管理器
@Bean("transactionManager")
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
// 第二个数据源(db2:sql50库)
@Bean("db2")
public DataSource dataSource2() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/sql50");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
// db2对应的JdbcTemplate
@Bean("db2JdbcTemplate")
public JdbcTemplate jdbcTemplate2(@Qualifier("db2") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// db2对应的事务管理器(关键:绑定dataSource2()而非dataSource())
@Bean("transactionManager2")
public DataSourceTransactionManager transactionManager2() {
return new DataSourceTransactionManager(dataSource2());
}
}
关键注意点:
@Primary标注主数据源,当未显式指定数据源/事务管理器时,Spring会默认使用主数据源;- 每个事务管理器必须绑定对应的数据源(如
transactionManager2必须关联dataSource2()),否则会导致事务管理错位。
2.2 事务业务层实现
创建事务服务类,封装事务方法和通用操作方法。核心:事务方法必须放在Spring管理的Bean中,且通过注入调用(避免内部调用导致事务失效)。
package org.example.multidb.service;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TransactionTestService {
// 通用方法:查询表数据量
public int countData(JdbcTemplate jdbcTemplate) {
return jdbcTemplate.queryForObject("SELECT COUNT(*) FROM test_transaction", Integer.class);
}
// 初始化方法:清空测试表
public void initTable(JdbcTemplate jdbcTemplate) {
jdbcTemplate.execute("TRUNCATE TABLE test_transaction");
}
// db1事务方法:指定transactionManager,异常时回滚
@Transactional(transactionManager = "transactionManager", rollbackFor = RuntimeException.class)
public void executeDb1Transaction(JdbcTemplate jdbcTemplate) {
// 插入测试数据
String insertSql = "INSERT INTO test_transaction (content) VALUES ('db1_test_data')";
jdbcTemplate.execute(insertSql);
// 打印回滚前数据量
int beforeRollbackCount = countData(jdbcTemplate);
System.out.println("回滚前(事务内):test_transaction 表数据量:" + beforeRollbackCount);
// 主动抛异常触发回滚
throw new RuntimeException("主动触发 db1 事务回滚");
}
// db2事务方法:指定transactionManager2,异常时回滚
@Transactional(transactionManager = "transactionManager2", rollbackFor = RuntimeException.class)
public void executeDb2Transaction(JdbcTemplate jdbcTemplate) {
// 插入测试数据
String insertSql = "INSERT INTO test_transaction (content) VALUES ('db2_test_data')";
jdbcTemplate.execute(insertSql);
// 打印回滚前数据量
int beforeRollbackCount = countData(jdbcTemplate);
System.out.println("回滚前(事务内):test_transaction 表数据量:" + beforeRollbackCount);
// 主动抛异常触发回滚
throw new RuntimeException("主动触发 db2 事务回滚");
}
// 错误示例:db2操作但使用db1的事务管理器
@Transactional(transactionManager = "transactionManager", rollbackFor = RuntimeException.class)
public void executeDb2Transaction2(JdbcTemplate jdbcTemplate) {
// 插入测试数据
String insertSql = "INSERT INTO test_transaction (content) VALUES ('db2_test_data')";
jdbcTemplate.execute(insertSql);
// 打印回滚前数据量
int beforeRollbackCount = countData(jdbcTemplate);
System.out.println("回滚前(事务内):test_transaction 表数据量:" + beforeRollbackCount);
// 主动抛异常触发回滚
throw new RuntimeException("主动触发 db2 事务回滚");
}
}
关键配置说明:
@Transactional(transactionManager = "xxx"):显式指定事务管理器,绑定对应数据源;rollbackFor = RuntimeException.class:显式指定运行时异常触发回滚(默认已支持,但显式配置更清晰);- 事务方法中主动抛出异常,用于验证事务回滚效果。
三、事务回滚测试
3.1 测试准备
在java库和sql50库中分别创建测试表:
CREATE TABLE IF NOT EXISTS test_transaction (
id INT PRIMARY KEY AUTO_INCREMENT,
content VARCHAR(50) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
3.2 测试类实现
编写测试类,验证不同场景下的事务回滚效果,重点测试「正确绑定事务管理器」和「错误绑定事务管理器」两种场景。
package org.example.multidb;
import org.example.multidb.service.TransactionTestService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
// 禁用测试方法默认事务(避免干扰业务事务)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@SpringBootTest
public class MultiDbTransactionRollbackTests {
@Autowired
private TransactionTestService transactionTestService;
// 测试db1事务回滚(正确绑定transactionManager)
@Test
void testDb1TransactionRollback(@Qualifier("db1JdbcTemplate") JdbcTemplate db1JdbcTemplate) {
System.out.println("===== 开始测试 db1 (java 库) 事务回滚 =====");
// 初始化:清空测试表
transactionTestService.initTable(db1JdbcTemplate);
System.out.println("初始化:清空 test_transaction 表,初始数据量:" + transactionTestService.countData(db1JdbcTemplate));
try {
// 调用事务方法(通过注入的Service调用,触发AOP代理)
transactionTestService.executeDb1Transaction(db1JdbcTemplate);
} catch (RuntimeException e) {
System.out.println("捕获到异常,事务触发回滚:" + e.getMessage());
}
// 验证回滚结果
int afterRollbackCount = transactionTestService.countData(db1JdbcTemplate);
System.out.println("回滚后:test_transaction 表数据量:" + afterRollbackCount);
System.out.println("===== db1 事务回滚测试结束 =====\n");
}
// 测试db2事务回滚(正确绑定transactionManager2)
@Test
void testDb2TransactionRollback(@Qualifier("db2JdbcTemplate") JdbcTemplate db2JdbcTemplate) {
System.out.println("===== 开始测试 db2 (sql50 库) 事务回滚 =====");
// 初始化:清空测试表
transactionTestService.initTable(db2JdbcTemplate);
System.out.println("初始化:清空 test_transaction 表,初始数据量:" + transactionTestService.countData(db2JdbcTemplate));
try {
// 调用事务方法
transactionTestService.executeDb2Transaction(db2JdbcTemplate);
} catch (RuntimeException e) {
System.out.println("捕获到异常,事务触发回滚:" + e.getMessage());
}
// 验证回滚结果
int afterRollbackCount = transactionTestService.countData(db2JdbcTemplate);
System.out.println("回滚后:test_transaction 表数据量:" + afterRollbackCount);
System.out.println("===== db2 事务回滚测试结束 =====\n");
}
// 测试db2事务回滚(错误绑定transactionManager)
@Test
void testDb2TransactionRollbackUsingWrongTxManager(@Qualifier("db2JdbcTemplate") JdbcTemplate db2JdbcTemplate) {
System.out.println("===== 开始测试 db2 (sql50 库) 事务回滚(错误TxManager) =====");
// 初始化:清空测试表
transactionTestService.initTable(db2JdbcTemplate);
System.out.println("初始化:清空 test_transaction 表,初始数据量:" + transactionTestService.countData(db2JdbcTemplate));
try {
// 调用错误的事务方法
transactionTestService.executeDb2Transaction2(db2JdbcTemplate);
} catch (RuntimeException e) {
System.out.println("捕获到异常,事务触发回滚:" + e.getMessage());
}
// 验证回滚结果
int afterRollbackCount = transactionTestService.countData(db2JdbcTemplate);
System.out.println("回滚后:test_transaction 表数据量:" + afterRollbackCount);
System.out.println("===== db2 事务回滚测试结束 =====\n");
}
}
测试类关键配置:
@Transactional(propagation = Propagation.NOT_SUPPORTED):禁用测试方法的默认事务,避免测试框架的事务覆盖业务事务;- 通过注入
TransactionTestService调用事务方法,确保Spring AOP代理生效。
四、测试结果与分析
4.1 正确绑定事务管理器(db1/db2)
===== 开始测试 db1 (java 库) 事务回滚 =====
初始化:清空 test_transaction 表,初始数据量:0
回滚前(事务内):test_transaction 表数据量:1
捕获到异常,事务触发回滚:主动触发 db1 事务回滚
回滚后:test_transaction 表数据量:0
===== db1 事务回滚测试结束 =====
===== 开始测试 db2 (sql50 库) 事务回滚 =====
初始化:清空 test_transaction 表,初始数据量:0
回滚前(事务内):test_transaction 表数据量:1
捕获到异常,事务触发回滚:主动触发 db2 事务回滚
回滚后:test_transaction 表数据量:0
===== db2 事务回滚测试结束 =====
结论:正确绑定事务管理器时,异常触发后数据回滚,数据量从1变回0,事务生效。
4.2 错误绑定事务管理器(db2用db1的TxManager)
===== 开始测试 db2 (sql50 库) 事务回滚(错误TxManager) =====
初始化:清空 test_transaction 表,初始数据量:0
回滚前(事务内):test_transaction 表数据量:1
捕获到异常,事务触发回滚:主动触发 db2 事务回滚
回滚后:test_transaction 表数据量:1
===== db2 事务回滚测试结束 =====
结论:事务管理器与数据源不匹配时,异常无法触发回滚,数据被持久化,事务失效。
五、核心踩坑点总结
- 事务管理器与数据源绑定错误:每个事务管理器必须绑定对应数据源,否则事务无法管理目标数据库;
- 内部调用导致事务失效:事务方法必须放在独立的Spring Bean中,通过注入调用(避免本类内部调用,绕开AOP代理);
- 测试方法默认事务干扰:测试类需禁用默认事务(
Propagation.NOT_SUPPORTED),否则无法正确验证业务事务的回滚效果; - 未显式指定事务管理器:多数据源场景下,必须通过
@Transactional(transactionManager = "xxx")指定事务管理器,否则默认使用主数据源的事务管理器。
六、总结
Spring多数据源事务的核心是「数据源-事务管理器-JdbcTemplate」三者一一对应,且保证事务方法通过Spring代理调用。通过本文的配置和测试,可清晰验证多数据源事务的正确性,同时规避常见的事务失效问题。在实际开发中,需重点关注事务管理器的绑定和AOP代理的触发条件,确保多数据源场景下事务的原子性。