springboot动态数据源+分布式事务的实现

 更新时间:2025年09月12日 10:18:03   作者:wenzheng_du  
本文主要介绍了springboot动态数据源+分布式事务的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1.引入jta-atomikos

这个springboot 是自带的。我的springboot版本为2.5.9,数据库为mysql。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>

2.配置数据源(application.yml)

spring:
    # 数据源配置
    jta:
        enabled: true
    datasource:
        master:
            # 主库数据源
            url: 
            username: 
            password: 
            driver-class-name: com.mysql.cj.jdbc.Driver
        slave:
            # 从库数据源
            url: 
            username: 
            password: 
            driver-class-name: com.mysql.cj.jdbc.Driver

新建文件 DataSourceConfig ,配置数据源及线程池。你有几个数据源就建立几个XXXDataSource方法,最后放到dynamicDataSource方法中的Map中去。事务管理一定要用JtaTransactionManager。SqlSessionFactory 是可以不用写的,如果要写的话,如果你用了mybatis-plus,一定要用MybatisSqlSessionFactoryBean,不然在启动或者调用Mybatis-plus的方法时,会报找不到bean的错。

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.sql.DataSource;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

@Aspect
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {

    @Autowired
    private Environment env;

    @Bean(name = "masterDataSource")
    public DataSource masterDataSource() {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        ds.setUniqueResourceName("master");
        ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        ds.setXaProperties(getXaProperties("master"));
        ds.setMaxPoolSize(10);
        ds.setMinPoolSize(5);
        ds.setBorrowConnectionTimeout(60);
        return ds;
    }

    @Bean(name = "slaveDataSource")
    public DataSource slaveDataSource() {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        ds.setUniqueResourceName("slave");
        ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        ds.setXaProperties(getXaProperties("slave"));
        ds.setMaxPoolSize(10);
        ds.setMinPoolSize(5);
        ds.setBorrowConnectionTimeout(60);
        return ds;
    }

    @Bean
    @Primary
    public DataSource dynamicDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                        @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        DynamicDataSource dataSource = new DynamicDataSource();
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", masterDataSource);
        targetDataSources.put("slave", slaveDataSource);
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(masterDataSource);
        return dataSource;
    }

    @Bean
    public UserTransaction userTransaction() throws SystemException {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }

    @Bean(initMethod = "init", destroyMethod = "close")
    public UserTransactionManager userTransactionManager() {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(false);
        return userTransactionManager;
    }

    @Bean
    public PlatformTransactionManager transactionManager(UserTransaction userTransaction,
                                                         UserTransactionManager userTransactionManager) {
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        jtaTransactionManager.setUserTransaction(userTransaction);
        jtaTransactionManager.setTransactionManager(userTransactionManager);
        return jtaTransactionManager;
    }

    private Properties getXaProperties(String dataSourceType) {
        Properties xaProps = new Properties();
        String username = getPropertyValue("spring.datasource." + dataSourceType + ".username");
        String password = getPropertyValue("spring.datasource." + dataSourceType + ".password");
        String url = getPropertyValue("spring.datasource." + dataSourceType + ".url");
        xaProps.put("user", username);
        xaProps.put("password", password);
        xaProps.put("url", url);
        return xaProps;
    }

    private String getPropertyValue(String str) {
        return env.getProperty(str);
    }

}

新建文件DynamicDataSource,这个类继承AbstractRoutingDataSource。

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        String dataSourceKey = DataSourceContextHolder.getDataSource();
        if (dataSourceKey == null) {
            return "master"; // 默认数据源
        }
        return dataSourceKey;
    }
}

建立一个上下文DataSourceContextHolder文件,用于读取当前数据源。

public class DataSourceContextHolder {

    public static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void setDataSource(String dataSourceName) {
        threadLocal.set(dataSourceName);
    }

    public static String getDataSource() {
        return threadLocal.get();
    }

    public static void clearDataSource() {
        threadLocal.remove();
    }
}

3.配置注解

这个没啥好说的,文件名自己定义都可以。注意后面的默认数据源参数,配置了的话一定要是你的。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MDS {

    String value() default "master";

}

4.切面

在类上使用上面的自定义注解。然后用切面去切换数据源。这里我用了3个判断,首先会先判断方法上有没有自定义注解,其次类上,都没有就用默认的数据源。

import cn.hutool.core.annotation.AnnotationUtil;
import com.rs.common.annotation.MDS;
import com.rs.framework.config.dynamic.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Order(0)
@Component
public class DataSourceAspect {

    @Before("@within(mds) || @annotation(mds)")
    public void before(JoinPoint joinPoint, MDS mds) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Class<?> targetClass = method.getDeclaringClass();
        Object value = AnnotationUtil.getAnnotationValue(targetClass, MDS.class);
        if(mds != null && mds.value() != null){
            DataSourceContextHolder.setDataSource(mds.value());
        } else if (value != null) {
            DataSourceContextHolder.setDataSource((String) value);
        } else {
            DataSourceContextHolder.setDataSource("master");
        }
    }

    @AfterReturning(pointcut = "@within(mds) ||@annotation(mds)", returning = "returnVal")
    public void afterReturning(JoinPoint joinPoint, MDS mds, Object returnVal) {
        DataSourceContextHolder.clearDataSource();
    }

    @AfterThrowing(pointcut = "@within(mds) ||@annotation(mds)", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, MDS mds, Throwable ex) {
        DataSourceContextHolder.clearDataSource();
    }

}

5.如何使用

一个方法中执行不同数据源操作。getUserPageList方法使用的是默认数据源master,即最后的返回是返回master的查询结果。updateBusinessStatus方法使用的是从库数据源,在执行完sql后我写了一个异常。

这里需要注意的是,一定要写@Transactional(rollbackFor = Exception.class),且是写在方法上!!!别为了省事写在类上,写在类上发生异常不会回滚(亲测)。

    @Override
    @Transactional(rollbackFor = Exception.class)
    public IPage<SysUserDto> getUserPageList(SysUser user, Page<SysUser> page) {
        user = new SysUser();
        user.setUserId(29L);
        user.setDelFlag("0");
        sysUserMapper.updateById(user);
        flwProcessService.updateBusinessStatus("1851531692768452608",3);
        return sysUserMapper.getUserPageList(user,page.page());
    }

    @Override
    @MDS("slave")
    @Transactional(rollbackFor = Exception.class)
    public void updateBusinessStatus(String procInstId, Integer businessStatus) {
        flwProcessMapper.update(null,new UpdateWrapper<FlwProcess>().lambda()
                .eq(FlwProcess::getProcInstId,procInstId)
                .set(FlwProcess::getBusinessStatus,businessStatus)
        );
        int i = 1/0;
    }

注意事项

一个方法不同数据源的操作@Transactional(rollbackFor = Exception.class)写在类上!!!

一个方法不同数据源的操作@Transactional(rollbackFor = Exception.class)写在类上!!!

一个方法不同数据源的操作@Transactional(rollbackFor = Exception.class)写在类上!!!

到此这篇关于springboot动态数据源+分布式事务的实现的文章就介绍到这了,更多相关springboot动态数据源+分布式事务内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java实现RTF文件查看器(附带源码)

    java实现RTF文件查看器(附带源码)

    这篇文章主要介绍了一个基于 Java Swing 的 RTF 文件查看器,既可作为轻量级桌面应用,也可嵌入到更大规模的管理系统中,满足用户对 RTF 文档的即时预览、样式保真和简单导航需求
    2025-06-06
  • 使用Maven打包、发布、配置版本号命令

    使用Maven打包、发布、配置版本号命令

    在软件开发过程中,打包和发布是关键步骤,本文介绍了如何在打包和发布时跳过测试,如何指定项目版本号,以及如何指定配置文件,提供了实用的技巧和方法,希望对开发者有所帮助
    2024-09-09
  • 详细介绍Java关键字throw throws Throwable的用法与区别

    详细介绍Java关键字throw throws Throwable的用法与区别

    这篇文章主要介绍了java中throws与throw及Throwable的用法和区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-04-04
  • Java中的观察者模式实例讲解

    Java中的观察者模式实例讲解

    这篇文章主要介绍了Java中的观察者模式实例讲解,本文先是讲解了观察者模式的概念,然后以实例讲解观察者模式的实现,以及给出了UML图,需要的朋友可以参考下
    2014-12-12
  • springboot中@RequestMapping的用法

    springboot中@RequestMapping的用法

    这篇文章主要介绍了springboot中@RequestMapping的用法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • 如何在logback日志配置里获取服务器ip和端口

    如何在logback日志配置里获取服务器ip和端口

    这篇文章主要介绍了如何在logback日志配置里获取服务器ip和端口的方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java使用OSS实现上传文件功能

    Java使用OSS实现上传文件功能

    这篇文章主要为大家详细介绍了Java如何使用OSS实现上传文件功能,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的小伙伴可以了解一下
    2024-01-01
  • 详解Spring循环依赖的解决方案

    详解Spring循环依赖的解决方案

    这篇文章主要介绍了详解Spring循环依赖的解决方案,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-05-05
  • 详解Nacos中注册中心和配置中心的实现

    详解Nacos中注册中心和配置中心的实现

    Spring Cloud Alibaba 是阿里巴巴提供的一站式微服务开发解决方案。而 Nacos 作为 Spring Cloud Alibaba 的核心组件之一,提供了两个非常重要的功能:注册中心和配置中心,我们今天来了解和实现一下二者
    2022-08-08
  • 详解Java的JDBC API的存储过程与SQL转义语法的使用

    详解Java的JDBC API的存储过程与SQL转义语法的使用

    这篇文章主要介绍了详解Java的JDBC API的存储过程与SQL转义语法的使用,JDBC是Java用于连接使用各种数据库的API,需要的朋友可以参考下
    2015-12-12

最新评论