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

相关推荐
lizi8888832 分钟前
单组件的编写
java
java_heartLake35 分钟前
设计模式之代理模式
java·设计模式·代理模式
DT——1 小时前
Vite项目中eslint的简单配置
前端·javascript·代码规范
魏 无羡1 小时前
pgsql 分组查询方法
java·服务器·数据库
兩尛2 小时前
java--面向对象编程(中级部分)
java·开发语言
Xxxx. .Xxxx2 小时前
C语言程序设计实验与习题指导 (第4版 )课后题-第二章+第三章
java·c语言·开发语言
姜西西_2 小时前
[Spring]Spring MVC 请求和响应及用到的注解
java·spring·mvc
逸狼2 小时前
【JavaEE初阶】多线程6(线程池\定时器)
java·开发语言·算法
qq_35323353892 小时前
【原创】java+springboot+mysql科研成果管理系统设计与实现
java·spring boot·mysql·mvc·web
dawn1912282 小时前
SpringMVC 入门案例详解
java·spring·html·mvc