这也太酷了吧!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原理即可,主要还是设计思想。

相关推荐
侠客行031713 小时前
Mybatis连接池实现及池化模式
java·mybatis·源码阅读
蛇皮划水怪13 小时前
深入浅出LangChain4J
java·langchain·llm
子兮曰13 小时前
OpenClaw入门:从零开始搭建你的私有化AI助手
前端·架构·github
吴仰晖13 小时前
使用github copliot chat的源码学习之Chromium Compositor
前端
1024小神13 小时前
github发布pages的几种状态记录
前端
老毛肚15 小时前
MyBatis体系结构与工作原理 上篇
java·mybatis
风流倜傥唐伯虎15 小时前
Spring Boot Jar包生产级启停脚本
java·运维·spring boot
不像程序员的程序媛15 小时前
Nginx日志切分
服务器·前端·nginx
Yvonne爱编码15 小时前
JAVA数据结构 DAY6-栈和队列
java·开发语言·数据结构·python
Re.不晚15 小时前
JAVA进阶之路——无奖问答挑战1
java·开发语言