这也太酷了吧!AOP+自定义注解,实现一次请求,多个数据库操作,保证事务同时生效

场景与方案

当一个服务,连接两个数据库,一次接口请求需要向两个数据库同时写入数据,我们使用 @Transactional 注解方式加入事务,此时A数据库写入成功,B数据库写入失败,这时可能数据库A写入的数据不能回滚,原因是 @Transactional 注解中 transactionManager 的参数决定的,只能指定一个事务管理。

A数据库配置代码

java 复制代码
@Configuration
@MapperScan(basePackages = "com.p.test.testadb.mapper",
       annotationClass = Repository.class,
       sqlSessionFactoryRef = DataSourceConfig.SQL_SESSION_FACTORY_NAME)
public class DataSourceConfig {

   static final String SQL_SESSION_FACTORY_NAME = "aSessionFactory";
   public static final String A_TX_MANAGER = "aTxManager";


   @Bean(name = "aDataSource")
   @ConfigurationProperties(prefix = "spring.datasource")
   @Primary
   public DataSource dataSource() {
       return DataSourceBuilder.create().build();
   }

   @Bean(name = A_TX_MANAGER)
   @Primary
   public PlatformTransactionManager txManager() {
       return new DataSourceTransactionManager(dataSource());
   }

   @Bean(name = SQL_SESSION_FACTORY_NAME)
   @Primary
   public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
       SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
       sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
               .getResources("classpath*:mapper-testadb/*.xml"));
       sqlSessionFactoryBean.setDataSource(dataSource());

       org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
       configuration.setCacheEnabled(false);
       sqlSessionFactoryBean.setConfiguration(configuration);

       return sqlSessionFactoryBean.getObject();
   }

}

B数据库配置代码

java 复制代码
@Configuration
@MapperScan(basePackages = "com.p.test.testbdb.mapper",
        annotationClass = Repository.class,
        sqlSessionFactoryRef = DataSourceConfig.SQL_SESSION_FACTORY_NAME)
public class DataSourceConfig {

    static final String SQL_SESSION_FACTORY_NAME = "bSessionFactory";
    public static final String B_TX_MANAGER = "bTxManager";


    @Bean(name = "bDataSource")
    @ConfigurationProperties(prefix = "spring.datasource")
    @Primary
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = B_TX_MANAGER)
    @Primary
    public PlatformTransactionManager txManager() {
        return new DataSourceTransactionManager(dataSource());
    }

    @Bean(name = SQL_SESSION_FACTORY_NAME)
    @Primary
    public SqlSessionFactory sqlSessionFactoryBean() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath*:mapper-testbdb/*.xml"));
        sqlSessionFactoryBean.setDataSource(dataSource());

        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setCacheEnabled(false);
        sqlSessionFactoryBean.setConfiguration(configuration);

        return sqlSessionFactoryBean.getObject();
    }

}

解决方案

解决方案很简单,我们知道 @Transactional 方式加入事务底层其实是AOP实现的,所以我们可以写两个方法,在两个方法上加 @Transactional 事务注解,分别操作数据库,下面上代码

typescript 复制代码
@Component
public class ABService{
   
   @Autowired
   private AService aService;
   @Autowired
   private BService bService;
   public void add(){
       aService.a();
       bService.b();
   } 
}


@Component
public class AService{
   
   @Transactional(transactionManager = "aTxManager")
   public void a(){
       //写入数据到数据库A
   }
   
}

@Component
public class BService {
   
   @Transactional(transactionManager = "bTxManager")
   public void b() {
       //写入数据到数据库B
   }

}

上面这种方式有点low,让他高大上一点,AOP+自定义注解,下面先定义一个多事务注解

java 复制代码
/**
 * @Author: pp
 * @DateTime: 2022/3/29 14:33
 * @Description: 多事务注解
 */

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiTransactional {

    String[] value() default {};

}

下面是AOP相关代码,通过AOP技术拦截所有使用了 @MultiTransactional 注解的方法

java 复制代码
/**
 * @Author: pp
 * @DateTime: 2022/3/29 14:36
 * @Description: TODO
 */
@Aspect
@Component
public class MultiTransactionAop {


    private final ComboTransaction comboTransaction;

    @Autowired
    public MultiTransactionAop(ComboTransaction comboTransaction) {
        this.comboTransaction = comboTransaction;
    }

    @Pointcut("@annotation(com.modian.chain.config.datasource.annotation.MultiTransactional)")
    public void pointCut() {
    }

    @Around("pointCut() && @annotation(multiTransactional)")
    public Object inMultiTransaction(ProceedingJoinPoint pjp, MultiTransactional multiTransactional) {

        return comboTransaction.inCombinedTx(() -> {
            try {
                return pjp.proceed();
            } catch (Throwable throwable) {
                if (throwable instanceof RuntimeException) {
                    throw (RuntimeException) throwable;
                }
                throw new RuntimeException(throwable);
            }
        }, multiTransactional.value());

    }


}

下面实现一个公共事务方法,这代码很简单,将注解中的参数带过来,循环参数中的事务,判断是哪个事务就执行哪个事务对应的的方法

php 复制代码
/**
 * @Author: pp
 * @DateTime: 2022/3/29 14:38
 * @Description: TODO
 */
@Component
public class ComboTransaction {


    @Autowired
    private ATxBroker aTxBroker;
    @Autowired
    private BTxBroker bTxBroker;

    public <V> V inCombinedTx(Callable<V> callable, String[] transactions) {
        if (callable == null) {
            return null;
        }

        Callable<V> combined = Stream
                .of(transactions)
                .filter(ele -> !StringUtils.isEmpty(ele))
                .distinct()
                .reduce(callable, (r, tx) -> {
                    switch (tx) {
                        case DataSourceBusinessConfig.A_TX_MANAGER:
                            return () -> aTxBroker.inTransaction(r);
                        case DataSourceChainConfig.B_TX_MANAGER:
                            return () -> aTxBroker.inTransaction(r);
                        default:
                            return null;
                    }
                }, (r1, r2) -> r2);
        try {
            return combined.call();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }

}

ATxBroker和BTxBroker的实现,下面这个代码应该很眼熟吧,和上面我说的很low的实现思想是一样的,只不过这里我们统一封装了而已

php 复制代码
/**
 * @Author: pp
 * @DateTime: 2022/3/29 14:39
 * @Description: TODO
 */
@Component
public class ATxBroker {


    @Transactional(DataSourceBusinessConfig.A_TX_MANAGER)
    public <V> V inTransaction(Callable<V> callable){
        try {
            return callable.call();
        }catch (RuntimeException e){
            throw e;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }


}

/**
 * @Author: pp
 * @DateTime: 2022/3/29 14:39
 * @Description: TODO
 */
@Component
public class BTxBroker {


    @Transactional(DataSourceBusinessConfig.B_TX_MANAGER)
    public <V> V inTransaction(Callable<V> callable){
        try {
            return callable.call();
        }catch (RuntimeException e){
            throw e;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }


}

使用方式如下

csharp 复制代码
    @Component
    public class ABService{


        @MultiTransactional(value = {DataSourceBusinessConfig.A_TX_MANAGER, DataSourceChainConfig.B_TX_MANAGER})
        public void add(){
            //A库写入数据
            a.add();
            //B库写入数据
            b.add();
        }
         
    }

就此优雅的解决了这个问题,从技术上来说没有任何实现难度,我们只需要知道 @Transactional 注解的使用方式,知道AOP原理即可,主要还是设计思想。

相关推荐
最新资讯动态20 分钟前
HDC 2026 | 对话鲸鸿动能:存量时代,品牌如何夺回营销“主动权”?
前端
最新资讯动态21 分钟前
游戏出海,从产品走向体系
前端
最新资讯动态21 分钟前
20人团队跑出百万DAU、大厂也来抢量:谁在鸿蒙生态跑出加速度
前端
最新资讯动态34 分钟前
千万开发者背后,鸿蒙商业化的B面
前端
爱勇宝2 小时前
AI 时代:智商决定起点,情商决定走多远
前端·ai编程
kyriewen3 小时前
用了半年 Claude Code 后,我尝试关掉它写了一周代码——结果比想象中严重
前端·javascript·ai编程
IT_陈寒3 小时前
Vite的静态资源打包让我熬夜到三点,这坑千万别跳
前端·人工智能·后端
小bo波4 小时前
使用Thread子类创建线程 VS 使用Runnable接口创建线程的区别
java·多线程·thread·并发编程·runnable
徐小夕4 小时前
万字拆解 JitWord:企业级实时协同文档底层架构 + 大模型 AI 融合完整实践
前端·vue.js·github
一份执念4 小时前
uni-app 小程序分包限制处理与主包体积优化实战
前端·微信小程序