SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源)

 
 
往期热门文章:
1、解决 IDEA 占用大量 CPU 导致的卡顿问题!
2、JDK 18 / Java 18 GA 来了
3、@Transactional 注解失效的3种原因及解决办法
4、14 个写 Java 的习惯
5、Google 工作十年后,我选择离开!

首先,到底啥是分布式事务呢,比如我们在执行一个业务逻辑的时候有两步分别操作A数据源和B数据源,当我们在A数据源执行数据更改后,在B数据源执行时出现运行时异常,那么我们必须要让B数据源的操作回滚,并回滚对A数据源的操作;这种情况在支付业务时常常出现;比如买票业务在最后支付失败,那之前的操作必须全部回滚,如果之前的操作分布在多个数据源中,那么这就是典型的分布式事务回滚;

了解了什么是分布式事务,那分布式事务在java的解决方案就是JTA(即Java Transaction API);springboot官方提供了 Atomikos or Bitronix的解决思路;

其实,大多数情况下很多公司是使用消息队列的方式实现分布式事务。

本篇文章重点讲解springboot环境下,整合 Atomikos +mysql+mybatis+tomcat/jetty;

一、项目依赖

pom.xml中添加atomikos的springboot相关依赖:

<!--分布式事务-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>

点进去会发现里面整合好了:transactions-jmstransactions-jtatransactions-jdbcjavax.transaction-api

二、把数据源的相关配置项单独提炼到一个application.yml中:

注意:

  1. 这回我们的spring.datasource.typecom.alibaba.druid.pool.xa.DruidXADataSource;

  2. spring.jta.transaction-manager-id的值在你的电脑中是唯一的,这个详细请阅读官方文档;

0bca400a4131fcf8e091305acc1f4981.png

完整的yml文件如下:

spring:
  datasource:
    type: com.alibaba.druid.pool.xa.DruidXADataSource
    druid:
    
      systemDB:
        name: systemDB
        url: jdbc:mysql://localhost:3306/springboot-mybatis?useUnicode=true&characterEncoding=utf-8
        username: root
        password: root
        # 下面为连接池的补充设置,应用到上面所有数据源中
        # 初始化大小,最小,最大
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 30
        validationQuery: SELECT 1
        validationQueryTimeout: 10000
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打开PSCache,并且指定每个连接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall
        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        # 合并多个DruidDataSource的监控数据
        useGlobalDataSourceStat: true
 
      businessDB:
        name: businessDB
 
        url: jdbc:mysql://localhost:3306/springboot-mybatis2?useUnicode=true&characterEncoding=utf-8
        username: root
        password: root
        # 下面为连接池的补充设置,应用到上面所有数据源中
        # 初始化大小,最小,最大
        initialSize: 5
        minIdle: 5
        maxActive: 20
        # 配置获取连接等待超时的时间
        maxWait: 60000
        # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # 配置一个连接在池中最小生存的时间,单位是毫秒
        minEvictableIdleTimeMillis: 30
        validationQuery: SELECT 1
        validationQueryTimeout: 10000
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打开PSCache,并且指定每个连接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        filters: stat,wall
        # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
        # 合并多个DruidDataSource的监控数据
        useGlobalDataSourceStat: true
 
  #jta相关参数配置
  jta:
    log-dir: classpath:tx-logs
    transaction-manager-id: txManager
三、在DruidConfig.java中实现多个数据源的注册;分布式事务管理器的注册;druid的注册;
package com.zjt.config;
 
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.druid.wall.WallConfig;
import com.alibaba.druid.wall.WallFilter;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
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.jta.JtaTransactionManager;
 
import javax.sql.DataSource;
import javax.transaction.UserTransaction;
import java.util.Properties;
 
/**
 * Druid配置
 *
 * 
 */
@Configuration
public class DruidConfig {
    @Bean(name = "systemDataSource")
    @Primary
    @Autowired
    public DataSource systemDataSource(Environment env) {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        Properties prop = build(env, "spring.datasource.druid.systemDB.");
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        ds.setUniqueResourceName("systemDB");
        ds.setPoolSize(5);
        ds.setXaProperties(prop);
        return ds;
 
    }
 
    @Autowired
    @Bean(name = "businessDataSource")
    public AtomikosDataSourceBean businessDataSource(Environment env) {
 
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        Properties prop = build(env, "spring.datasource.druid.businessDB.");
        ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        ds.setUniqueResourceName("businessDB");
        ds.setPoolSize(5);
        ds.setXaProperties(prop);
 
        return ds;
    }
 
 
    /**
     * 注入事物管理器
     * @return
     */
    @Bean(name = "xatx")
    public JtaTransactionManager regTransactionManager () {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        UserTransaction userTransaction = new UserTransactionImp();
        return new JtaTransactionManager(userTransaction, userTransactionManager);
    }
 
 
    private Properties build(Environment env, String prefix) {
 
        Properties prop = new Properties();
        prop.put("url", env.getProperty(prefix + "url"));
        prop.put("username", env.getProperty(prefix + "username"));
        prop.put("password", env.getProperty(prefix + "password"));
        prop.put("driverClassName", env.getProperty(prefix + "driverClassName", ""));
        prop.put("initialSize", env.getProperty(prefix + "initialSize", Integer.class));
        prop.put("maxActive", env.getProperty(prefix + "maxActive", Integer.class));
        prop.put("minIdle", env.getProperty(prefix + "minIdle", Integer.class));
        prop.put("maxWait", env.getProperty(prefix + "maxWait", Integer.class));
        prop.put("poolPreparedStatements", env.getProperty(prefix + "poolPreparedStatements", Boolean.class));
 
        prop.put("maxPoolPreparedStatementPerConnectionSize",
                env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
 
        prop.put("maxPoolPreparedStatementPerConnectionSize",
                env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
        prop.put("validationQuery", env.getProperty(prefix + "validationQuery"));
        prop.put("validationQueryTimeout", env.getProperty(prefix + "validationQueryTimeout", Integer.class));
        prop.put("testOnBorrow", env.getProperty(prefix + "testOnBorrow", Boolean.class));
        prop.put("testOnReturn", env.getProperty(prefix + "testOnReturn", Boolean.class));
        prop.put("testWhileIdle", env.getProperty(prefix + "testWhileIdle", Boolean.class));
        prop.put("timeBetweenEvictionRunsMillis",
                env.getProperty(prefix + "timeBetweenEvictionRunsMillis", Integer.class));
        prop.put("minEvictableIdleTimeMillis", env.getProperty(prefix + "minEvictableIdleTimeMillis", Integer.class));
        prop.put("filters", env.getProperty(prefix + "filters"));
 
        return prop;
    }
 
    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
 
        //控制台管理用户,加入下面2行 进入druid后台就需要登录
        //servletRegistrationBean.addInitParameter("loginUsername", "admin");
        //servletRegistrationBean.addInitParameter("loginPassword", "admin");
        return servletRegistrationBean;
    }
 
    @Bean
    public FilterRegistrationBean filterRegistrationBean() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.addInitParameter("profileEnable", "true");
        return filterRegistrationBean;
    }
 
    @Bean
    public StatFilter statFilter(){
        StatFilter statFilter = new StatFilter();
        statFilter.setLogSlowSql(true); //slowSqlMillis用来配置SQL慢的标准,执行时间超过slowSqlMillis的就是慢。
        statFilter.setMergeSql(true); //SQL合并配置
        statFilter.setSlowSqlMillis(1000);//slowSqlMillis的缺省值为3000,也就是3秒。
        return statFilter;
    }
 
    @Bean
    public WallFilter wallFilter(){
        WallFilter wallFilter = new WallFilter();
        //允许执行多条SQL
        WallConfig config = new WallConfig();
        config.setMultiStatementAllow(true);
        wallFilter.setConfig(config);
        return wallFilter;
    }
 
}
四、分别配置每个数据源对应的sqlSessionFactory,以及MapperScan扫描的包:

MybatisDatasourceConfig.java

package com.zjt.config;
 
import com.zjt.util.MyMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
 
import javax.sql.DataSource;
 
/**
 * 
 * @description
 */
@Configuration
// 精确到 mapper 目录,以便跟其他数据源隔离
@MapperScan(basePackages = "com.zjt.mapper", markerInterface = MyMapper.class, sqlSessionFactoryRef = "sqlSessionFactory")
public class MybatisDatasourceConfig {
 
    @Autowired
    @Qualifier("systemDataSource")
    private DataSource ds;
 
    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(ds);
        //指定mapper xml目录
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));
        return factoryBean.getObject();
 
    }
 
    @Bean
    public SqlSessionTemplate sqlSessionTemplate() throws Exception {
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory()); // 使用上面配置的Factory
        return template;
    }
 
    //关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager
    // 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。
    //在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。
    /*@Bean(name = "transactionManager")
    @Primary
    public DataSourceTransactionManager masterTransactionManager() {
        //MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源
        // 与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
        return new DataSourceTransactionManager(ds);
    }*/
 
}

MybatisDatasource2Config.java

package com.zjt.config;
 
import com.zjt.util.MyMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
 
import javax.sql.DataSource;
 
/**
 * 
 * @description
 */
@Configuration
// 精确到 mapper 目录,以便跟其他数据源隔离
@MapperScan(basePackages = "com.zjt.mapper2", markerInterface = MyMapper.class, sqlSessionFactoryRef = "sqlSessionFactory2")
public class MybatisDatasource2Config {
 
    @Autowired
    @Qualifier("businessDataSource")
    private DataSource ds;
 
    @Bean
    public SqlSessionFactory sqlSessionFactory2() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(ds);
        //指定mapper xml目录
        ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        factoryBean.setMapperLocations(resolver.getResources("classpath:mapper2/*.xml"));
        return factoryBean.getObject();
 
    }
 
    @Bean
    public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
        SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2()); // 使用上面配置的Factory
        return template;
    }
 
    //关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager
    // 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。
    //在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。
    /*@Bean(name = "transactionManager2")
    @Primary
    public DataSourceTransactionManager masterTransactionManager() {
        //MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源
        // 与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
        return new DataSourceTransactionManager(ds);
    }*/
 
}

由于我们本例中只使用一个事务管理器:xatx,故就不在使用TxAdviceInterceptor.javaTxAdvice2Interceptor.java中配置的事务管理器了;有需求的童鞋可以自己配置其他的事务管理器;(见DruidConfig.java中查看)

五、新建分布式业务测试接口JtaTestService.java和实现类JtaTestServiceImpl.java

其实就是一个很简单的test01()方法,在该方法中我们分别先后调用classService.saveOrUpdateTClass(tClass);teacherService.saveOrUpdateTeacher(teacher);

实现先后操作两个数据源:然后我们可以自己debug跟踪事务的提交时机,此外,也可以在在两个方法全执行结束之后,手动制造一个运行时异常,来检查分布式事务是否全部回滚;

注意:

在实现类的方法中我使用的是:

@Transactional(transactionManager = "xatx", propagation = Propagation.REQUIRED, rollbackFor = { java.lang.RuntimeException.class })

从而指定了使用哪个事务管理器,事务隔离级别(一般都用我这个默认的),回滚的条件(一般可以使用Exception),这三个可以自己根据业务实际修改;

package com.zjt.service3;
 
import java.util.Map;
 
public interface JtaTestService {
 
    public Map<String,Object> test01();
 
}
package com.zjt.service3.impl;
 
 
import com.zjt.entity.TClass;
import com.zjt.entity.Teacher;
import com.zjt.service.TClassService;
import com.zjt.service2.TeacherService;
import com.zjt.service3.JtaTestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
 
import java.util.LinkedHashMap;
import java.util.Map;
 
@Service("jtaTestServiceImpl")
public class JtaTestServiceImpl implements JtaTestService{
 
    @Autowired
    @Qualifier("teacherServiceImpl")
    private TeacherService teacherService;
    @Autowired
    @Qualifier("tclassServiceImpl")
    private TClassService tclassService;
 
    @Override
    @Transactional(transactionManager = "xatx", propagation = Propagation.REQUIRED, rollbackFor = { java.lang.RuntimeException.class })
    public Map<String, Object> test01() {
        LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
        TClass tClass=new TClass();
        tClass.setName("8888");
        tclassService.saveOrUpdateTClass(tClass);
 
        Teacher teacher=new Teacher();
        teacher.setName("8888");
        teacherService.saveOrUpdateTeacher(teacher);
 
        System.out.println(1/0);
 
        resultMap.put("state","success");
        resultMap.put("message","分布式事务同步成功");
        return resultMap;
    }
}
六、建立JtaTestContoller.java,接受一个来自前端的http请求,触发JtaTestService 的test01方法:
package com.zjt.web;
 
 
import com.zjt.service3.JtaTestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
 
import java.util.LinkedHashMap;
import java.util.Map;
 
@Controller
@RequestMapping("/jtaTest")
public class JtaTestContoller {
 
    @Autowired
    @Qualifier("jtaTestServiceImpl")
    private JtaTestService taTestService;
 
 
 
    @ResponseBody
    @RequestMapping("/test01")
    public Map<String,Object> test01(){
        LinkedHashMap<String,Object> resultMap=new LinkedHashMap<String,Object>();
        try {
            return taTestService.test01();
        }catch (Exception e){
            resultMap.put("state","fail");
            resultMap.put("message","分布式事务同步失败");
            return resultMap;
        }
    }
}
七、在test.ftl中增加一个按钮来测试;
//分布式事务测试
$("#JTATest").click(function(){
    $.ajax({
        type: "POST",
        url: "${basePath!}/jtaTest/test01",
        data: {}    ,
        async: false,
        error: function (request) {
            layer.alert("与服务器连接失败/(ㄒoㄒ)/~~");
            return false;
        },
        success: function (data) {
            if (data.state == 'fail') {
                layer.alert(data.message);
                return false;
            }else if(data.state == 'success'){
                layer.alert(data.message);
            }
        }
    });
});
 
<button class="layui-btn" id="JTATest">同时向班级和老师表插入名为8888的班级和老师</button>
八、启动服务,验证结果:
98df4bf39553c90dc79028c5f319a6cb.png

点击这个按钮,跳转到controller:

06d83e6f0ea74e39c273f97d5f9d59f6.png

当正常执行了sql语句之后,我们可以发现数据库并没有变化,因为整个方法的事务还没有走完,当我们走到1/0这步时:

125c7883c75da0859c24aa2c19d6a0e8.png

抛出运行时异常,并被spring事务拦截器拦截,并捕获异常:

0cffc67e1dc89fb3e4f5921fd19bffd4.png

this.completeTransactionAfterThrowing(txInfo, var16);方法中会将事务全部回滚:

22:09:04.243 logback [http-nio-8080-exec-5] INFO c.a.i.imp.CompositeTransactionImp - rollback() done of transaction 192.168.1.103.tm0000400006

此时,当我们再次打开数据库验证,依旧没有变化,证明分布式事务配置成功;

大家可以基于我的代码自己练习一下,自己尝试着使用多事务管理器的情况下的灵活配置;

九、后记:

本文源代码:

https://github.com/zhaojiatao/springboot-zjt-chapter10-springboot-atomikos-mysql-mybatis-druid.git

代码在tomcat和jetty环境下均可完成事务回滚;

在事务回滚时可能报一个Transactional not active的警告,我google后,老外也说不出这个具体作用,大部分人认为这只是一个警告,可以忽略; 

转自:jaryle

链接:blog.csdn.net/jaryle/article/details/88638780

 
 
 
 
最近热文阅读:

1、解决 IDEA 占用大量 CPU 导致的卡顿问题!

2、JDK 18 / Java 18 GA 来了

3、图解:订单系统从0到1设计思路

4、@Transactional 注解失效的3种原因及解决办法

5、14 个写 Java 的习惯

6、Google 工作十年后,我选择离开!

7、推荐好用的 Spring Boot 内置工具类

8、面试官:内存耗尽后Redis会发生什么 ?

9、监控告警满飞天,运维在家睡到自然醒...

10、@Bean与@Component 同时作用同一个类,会怎么样?

a55f8c8ebad791b474d378d42e4e350c.png

关注公众号,你想要的Java都在这里

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 是一个用于快速构建 Java 应用程序的框架。它可以与多种其他框架和组件进行整合,以实现更丰富的功能。在这里,我们将讨论如何使用 Spring Boot 整合 Druid、MyBatisJTA 分布式事务以及多数据源,同时使用 AOP 注解实现动态切换。 首先,我们可以在 Spring Boot 中集成 Druid 数据源。Druid 是一个高性能的 JDBC 连接池,可以提供监控和统计功能。我们可以通过在 pom.xml 文件中添加相关的依赖,并在 application.properties 文件中配置数据源信息,来实现 Druid 的集成。 接下来,我们可以整合 MyBatis 框架,它是一种优秀的持久化解决方案。我们可以使用 MyBatis 来操作数据库,并将其与 Druid 数据源进行整合。为此,我们需要在 pom.xml 文件中添加 MyBatisMyBatis-Spring 的依赖,并配置 MyBatis 的相关配置文件。 此外,我们还可以使用 JTAJava Transaction API)实现分布式事务JTA 可以在分布式环境中协调多个参与者的事务操作。我们可以在 pom.xml 文件中添加 JTA 的依赖,并在 Spring Boot 的配置文件中配置 JTA 的相关属性,以实现分布式事务的支持。 在实现多数据源时,我们可以使用 Spring Boot 的 AbstractRoutingDataSource 来实现动态切换数据源。这个类可以根据当前线程或其他条件选择不同的数据源来进行数据操作。我们可以通过继承 AbstractRoutingDataSource 并实现 determineCurrentLookupKey() 方法来指定当前数据源的 key。然后,在配置文件中配置多个数据源,并将数据源注入到 AbstractRoutingDataSource 中,从而实现动态切换。 最后,我们可以使用 AOP(Aspect Oriented Programming)注解来实现动态切换。AOP 是一种编程范式,可以通过在代码中插入特定的切面(Aspect)来实现横切关注点的处理。我们可以在代码中使用注解来标记需要切换数据源的方法,然后使用 AOP 技术来拦截这些方法,并根据注解中指定的数据源信息来进行数据源的切换。 综上所述,通过整合 Druid、MyBatisJTA 分布式事务以及多数据源,并使用 AOP 注解实现动态切换,我们可以在 Spring Boot 中实现强大而灵活的应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值