关于多数据源下Spring声明式事务管理失效问题的分析与解决

背景

项目中数据持久层采用了mybatis组件,存在多个数据源,通过@MapperScan的方式声明不同包路径下的dao使用哪个数据源,代码示例如下:

java 复制代码
@Configuration
@MapperScan(basePackages = {"com.company.first.**.dao"}, sqlSessionFactoryRef = "firstSqlSessionFactory")
public class FirstDataSourceConfig {
    @Autowired
    private FirstDataSourceProperties dataSourceProperties;

    @Bean(name = "firstDataSource")
    @Override
    public DataSource dataSource() {
        HikariConfig config = getConfig(dataSourceProperties);
        HikariDataSource dataSource = new HikariDataSource(config);
        return dataSource;
    }

    @Bean(name = "firstTransactionManager")
    @Primary
    @Override
    public DataSourceTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
    
    @Bean(name = "firstSqlSessionFactory")
    @Primary
    @Override
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        return createSqlSessionFactory(dataSource());
    }
}

其他 SecondDataSourceConfig 代码类似,只是没有 @Primary 注解。

现在有两个服务类A和B,分别有方法1和方法2,方法1调用方法2,方法1的的数据源为1,方法2的数据源为2,示例如下:

java 复制代码
@Service
public class A {
    @Transactional(rollbakFor = Exception.class)
    public void method1() {
        //通过数据源1 操作数据
        dao1.insert(record1);
        
        b.method2();
        
        //其他操作发生错误
        throw new RuntimeException();
    }
}
@Service
public class B {
    @Transactional(rollbakFor = Exception.class)
    public void method2() {
        //通过数据源2 操作数据
        dao2.insert(record2);
    }
}

问题描述:在调用a.method1()时发生了错误,此时数据库中数据1发生了回滚,数据2未发生回滚,这是不合理的,预期的结果是 数据1和2同时回滚。

分析解决

方式一(失败的)

可以看到 方法1和2都通过Spring的注解式声明开启了事务,且这个时候数据源1的操作是有事务管理,数据源2的操作没有事务管理,于是想是不是事务管理器的问题? 这里两个注解声明都没有指定事务管理器,因此这里默认的事务管理器均为 firstTransactionManager。

Spring在没有指定事务管理器的时候会优先找有没有被标注为@Primary的事务管理器,如果没有会找名称为transactionManager的事务管理器,如果都找不到,Spring将无法确定要使用的事务管理器,从而抛出异常。

而方法2的数据源2其实是不被 firstTransactionManager管理的,因此这里方法2是没有事务管理的,于是想通过指定方法2的事务管理器是不是就解决问题了。

java 复制代码
    @Transactional(rollbakFor = Exception.class, transactionManager = "secondTransactionManager")
    public void method2() {
        //通过数据源2 操作数据
        dao2.insert(record2);
    }

经过测试,问题仍然存在。即使通过设置事务传播行为,开启新的事务,问题仍然存在。

java 复制代码
    @Transactional(rollbakFor = Exception.class, transactionManager = "secondTransactionManager" propagation = Propagation.REQUIRES_NEW)
    public void method2() {
        //通过数据源2 操作数据
        dao2.insert(record2);
    }

其实这里因为事务管理器不同,即使方法2的事务传播行为是默认的 Propagation.REQUIRED加入现有事务的类型,因为不同管理器管理的事务是相互独立的,无法直接加入,所以方法2也是在对应的事务管理器下开启新的事务。那么这里为什么方法2的数据2没有跟方法1中的数据1一起回滚?

因为这里方法2是独立的事务2,在方法2调用结束后即结束了事务(提交或回滚),这里方法2本身没有异常,所以事务2提交成功。而事务1在方法2之后发生异常,事务1回滚。其流程如下:

复制代码
方法1调用开始
事务1开启
操作数据1
方法2调用开始
事务2开启
操作数据2
事务2提交
方法2调用结束
其他操作,发生异常
事务1回滚数据1

方式二

通过方式一的分析我们知道需要事务1和事务2一起提交或回滚。那么事务1和事务2能否合并在一个事务内,也就是说方法1和方法2使用同一个事务管理器。

链式事务管理器

java 复制代码
    @Bean(name = "chainedTransactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("firstTransactionManager") DataSourceTransactionManager ds1,
            @Qualifier("secondTransactionManager") DataSourceTransactionManager ds2) {
        return new ChainedTransactionManager(ds1, ds2);
    }

指定事务管理器

java 复制代码
@Service
public class A {
    @Transactional(rollbakFor = Exception.class, transactionManager = "chainedTransactionManager")
    public void method1() {
        //通过数据源1 操作数据
        dao1.insert(record1);
        
        b.method2();
        
        //其他操作发生错误
        throw new RuntimeException();
    }
}

@Service
public class B {
    @Transactional(rollbakFor = Exception.class)
    public void method2() {
        //通过数据源2 操作数据
        dao2.insert(record2);
    }
}

可以看到方法2未指定事务管理器,因为方法2会继承方法1中chainedTransactionManager管理的事务上下文,方法2中操作都会被纳入同一个chainedTransactionManager事务管理。 不过此时因为事务管理器不同,当我指定事务传播行为是 Propagation.NOT_SUPPORTED不起作用,需要指定使用同一个事务管理器。

但是指定 Propagation.NEVER时却会报存在事务的错误,有点奇怪。

其他方案

1 使用JTA/XA分布式事务管理器(推荐用于强一致性场景)

2 使用Seata等分布式事务框架(微服务架构推荐)

3 使用编程式事务管理(精细控制)

4 使用Spring Data的AbstractRoutingDataSource 来管理多个数据源(适合读写分离等场景)

相关推荐
卷毛的技术笔记8 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
键盘上的猫头鹰10 小时前
【MySQL 教程(八)】索引、事务、用户管理、导入导出与分页查询
数据库·python·mysql
Royzst10 小时前
数据库知识点
数据库
雪的季节11 小时前
企业级 Qt 全功能项目
开发语言·数据库·qt
宋浮檀s11 小时前
应急响应——Web漏洞:命令执行+SSRF+弱口令
运维·数据库·sql·网络安全·oracle·应急响应
云烟成雨TD12 小时前
Spring AI 1.x 系列【33】RAG Advisor 组件与四大分层架构
java·人工智能·spring
yurenpai(27届找实习中)12 小时前
redis_点评(21.好友关注——关注、取关功能实现;共同关注功能实现)
数据库·redis·缓存
Rick199312 小时前
索引的排序和分组
数据库·mysql
爱莉希雅&&&12 小时前
zabbix快速搭建和使用
android·linux·数据库·zabbix·监控
JohnYan13 小时前
工作笔记 - PG分组极值
数据库·后端·postgresql