SpringBoot整合atomikos实现跨库事务的详细方案

 更新时间:2022年06月02日 11:57:10   作者:code2roc  
这篇文章主要介绍了SpringBoot整合atomikos实现跨库事务,业务主要涉及政府及企业且并发量不大,所以采用XA事务,虽然性能有所损失,但是可以保证数据的强一致性,需要的朋友可以参考下

背景

框架之前完成了多数据源的动态切换及事务的处理,想更近一步提供一个简单的跨库事务处理功能,经过网上的搜索调研,大致有XA事务/SEGA事务/TCC事务等方案,因为业务主要涉及政府及企业且并发量不大,所以采用XA事务,虽然性能有所损失,但是可以保证数据的强一致性

方案设计

针对注册的数据源拷贝一份用于XA事务,使得本地事务和XA全局事务相互独立可选择的使用

Maven配置

引入atomikos第三方组件

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

注册XA数据源

使用Druid连接池,需要使用DruidXADataSource数据源对象,再使用AtomikosDataSourceBean进行包装

注册数据源时针对同一个连接注册两份,一份正常数据源,一份用于XA事务的数据源,数据源标识区分并关联

因为spring默认注册了XA事务管理器后,所有事务操作不再走本地事务,我们通过切换不同的数据源决定走本地事务还是XA事务

 //主数据源xa模式
    @Bean
    @Qualifier("masterXADataSource")
    public DataSource masterXADataSource() {
        DruidXADataSource datasource = new DruidXADataSource();
        if(driverClassName.equals("com.mysql.cj.jdbc.Driver")){
            if(!dbUrl.contains("useOldAliasMetadataBehavior")){
                dbUrl += "&useOldAliasMetadataBehavior=true";
            }
            if(!dbUrl.contains("useAffectedRows")){
                dbUrl += "&useAffectedRows=true";
            }
        }
        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);
        //configuration
        datasource.setInitialSize(1);
        datasource.setMinIdle(3);
        datasource.setMaxActive(20);
        datasource.setMaxWait(60000);
        datasource.setTimeBetweenEvictionRunsMillis(60000);
        datasource.setMinEvictableIdleTimeMillis(60000);
        datasource.setValidationQuery("select 'x'");
        datasource.setTestWhileIdle(true);
        datasource.setTestOnBorrow(false);
        datasource.setTestOnReturn(false);
        datasource.setPoolPreparedStatements(true);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(20);
        datasource.setLogAbandoned(false); //移除泄露连接发生是是否记录日志
        try {
            datasource.setFilters("stat,slf4j");
        } catch (SQLException e) {
            logger.error("druid configuration initialization filter", e);
        }
        datasource.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000");//connectionProperties);

        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
        atomikosDataSourceBean.setUniqueResourceName("master-xa");
        atomikosDataSourceBean.setXaDataSource(datasource);
        atomikosDataSourceBean.setPoolSize(5);
        atomikosDataSourceBean.setMaxPoolSize(20);
        atomikosDataSourceBean.setTestQuery("select 1");
        return atomikosDataSourceBean;
    }

注册XA事务管理器

使用spring内置的JtaTransactionManager事务管理器对象,设置AllowCustomIsolationLevels为true,否则指定自定义事务隔离级别会报错

    //xa模式全局事务管理器
    @Bean(name = "jtaTransactionManager")
    public PlatformTransactionManager transactionManager() throws Throwable {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        UserTransaction userTransaction = new UserTransactionImp();
        JtaTransactionManager jtaTransactionManager =  new JtaTransactionManager(userTransaction, userTransactionManager);
        jtaTransactionManager.setAllowCustomIsolationLevels(true);
        return jtaTransactionManager;
    }

定义XA事务切面

自定义注解@GlobalTransactional并定义对应切面,使用指定注解时在ThreadLocal变量值进行标识,组合

@Transactional注解指定XA事务管理器,在切换数据原时判断当前是否在XA事物中,从而切换不同的数据源

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Transactional(rollbackFor = Exception.class,isolation = Isolation.READ_UNCOMMITTED,transactionManager = "jtaTransactionManager")
public @interface GlobalTransactional {
}
@Aspect
@Component
@Order(value = 99)
public class GlobalTransitionAspect {
    private static Logger logger = LoggerFactory.getLogger(GlobalTransitionAspect.class);
    @Autowired
    private DynamicDataSource dynamicDataSource;

    /**
     * 切面点 指定注解
     */
    @Pointcut("@annotation(com.code2roc.fastkernel.datasource.GlobalTransactional) " +
            "|| @within(com.code2roc.fastkernel.datasource.GlobalTransactional)")
    public void dataSourcePointCut() {

    }

    /**
     * 拦截方法指定为 dataSourcePointCut
     */
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        GlobalTransactional methodAnnotation = method.getAnnotation(GlobalTransactional.class);
        if (methodAnnotation != null) {
            DataSourceContextHolder.tagGlobal();
            logger.info("标记全局事务");
        }
        try {
            return point.proceed();
        } finally {
            logger.info("清除全局事务");
            DataSourceContextHolder.clearGlobal();
        }
    }
}
public class DataSourceContextHolder {
    private static Logger log = LoggerFactory.getLogger(DataSourceContextHolder.class);
    // 对当前线程的操作-线程安全的
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    private static final ThreadLocal<String> contextGlobalHolder = new ThreadLocal<String>();

    // 调用此方法,切换数据源
    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
        log.debug("已切换到数据源:{}", dataSource);
    }

    // 获取数据源
    public static String getDataSource() {
        String value = contextHolder.get();
        if (StringUtil.isEmpty(value)) {
            value = "master";
        }
        if (!StringUtil.isEmpty(getGlobal())) {
            value = value + "-xa";
        }
        return value;
    }

    // 删除数据源
    public static void clearDataSource() {
        contextHolder.remove();
        log.debug("已切换到主数据源");
    }

    //====================全局事务标记================
    public static void tagGlobal() {
        contextGlobalHolder.set("1");
    }

    public static String getGlobal() {
        String value = contextGlobalHolder.get();
        return value;
    }

    public static void clearGlobal() {
        contextGlobalHolder.remove();
    }
    //===================================================
}

配置XA事务日志

通过在resource文件夹下创建transactions.properties文件可以指定XA事务日志的存储路径

com.atomikos.icatch.log_base_dir= tempfiles/transition/

到此这篇关于SpringBoot整合atomikos实现跨库事务的文章就介绍到这了,更多相关SpringBoot整合atomikos内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 关于@Valid注解大全以及用法规范

    关于@Valid注解大全以及用法规范

    这篇文章主要介绍了关于@Valid注解大全以及用法规范,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • 七个Spring核心模块详解

    七个Spring核心模块详解

    这篇文章主要为大家详细介绍了七个Spring的核心模块,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03
  • java集合中的list详解

    java集合中的list详解

    这篇文章主要介绍了java集合中的list详解,还是比较不错的,这里分享给大家,需要的朋友可以参考下。
    2017-11-11
  • Java反射机制详解

    Java反射机制详解

    Java的反射机制是在运行状态中,对于任何一个类,都可以知道这个类的所有属性和方法,对于任何一个对象,都可以调用它所有的方法和属性,修改部分类型信息。本文就来详细讲讲Java反射机制的使用
    2022-07-07
  • Java编程Nashorn实例代码

    Java编程Nashorn实例代码

    这篇文章主要介绍了Java编程Nashorn实例代码,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • mybatis类型转换器如何实现数据加解密

    mybatis类型转换器如何实现数据加解密

    这篇文章主要介绍了mybatis类型转换器如何实现数据加解密,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Java中内部类使用方法实战案例分析

    Java中内部类使用方法实战案例分析

    这篇文章主要介绍了Java中内部类使用方法,结合具体案例形式分析了Java内部类原理、调用方法及相关使用注意事项,需要的朋友可以参考下
    2019-09-09
  • 基于Java编写第一个区块链项目

    基于Java编写第一个区块链项目

    区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式,下面这篇文章主要给大家介绍了基于Java实现区块链的相关资料,需要的朋友可以参考下
    2021-08-08
  • SpringBoot如何进行对象复制的实践

    SpringBoot如何进行对象复制的实践

    本文主要介绍了SpringBoot 如何进行对象复制,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • springboot集成shiro遭遇自定义filter异常的解决

    springboot集成shiro遭遇自定义filter异常的解决

    这篇文章主要介绍了springboot集成shiro遭遇自定义filter异常的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11

最新评论