springboot+JTA+atomikos多数据源分布式事务管理

news2024/11/26 13:55:26

一、项目需求

        1、同时操作两个数据库,一个在本地服务器,一个在云服务器。

        2、数据库数据是同步的,两个数据库同时做update、insert等操作时,无论哪个数据库操作失败,要求两个数据库数据同时回滚。

        3、两个数据库均为SQL server数据库。

二、特殊要求

        1、数据库支持XA事务。网上其他教程都很复杂,但是这个博主写的很不错,不会配的可以参考一下:无法创建 XA 控制连接。错误:“找不到存储过程 'master..xp_sqljdbc_xa_init_ex'_com.microsoft.sqlserver.jdbc.sqlserverexception: 无-CSDN博客

        2、设置允许远程操作数据库。

        3、云服务器在控制台配置入站或出站规则,tcp协议,开放数据库端口。本地数据库在防火墙配置入站出站规则。

三、简单认识XA、JTA和Atomikos

XA

        XA是定义于数据库的分布式事务处理规范,XA事务支持不同数据库之间实现分布式事务。

JTA

        JTA(Java Transaction API):是Java平台上一个标准API,用于管理和控制分布式事务的执行流程。它是数据库XA事务在Java的一个映射。

        核心类:
javax.transaction.UserTransaction:暴露给应用使用,用来启动、提交、回滚事务。
javax.transaction.TransactionManager:提供给事务管理器的接口,用于协调和控制分布式事务的执行过程。
javax.transaction.XAResource:表示一个资源管理器,用于管理和操作资源。
javax.transaction.Xid:用于唯一标识一个分布式事务。

Atomikos

        Atomikos是一个开源的事务管理器,用于管理和控制分布式事务的执行过程。Atomikos可以解决,在同一个应用下,连接多个数据库,实现分布式事务。

四、项目案例

项目目录:

0、pom配置:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.13</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<groupId>com.gloyel</groupId>
	<artifactId>twodbgn</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>twodbgn</name>
	<description>twodbgn</description>

	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.2.2</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.32</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter-test</artifactId>
			<version>3.0.3</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jta-atomikos</artifactId>
			<version>2.6.13</version>
		</dependency>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
			<version>3.6.0</version>
		</dependency>
		<dependency>
			<groupId>jakarta.transaction</groupId>
			<artifactId>jakarta.transaction-api</artifactId>
			<version>1.3.3</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>5.3.23</version>
		</dependency>
		<dependency>
			<groupId>com.microsoft.sqlserver</groupId>
			<artifactId>mssql-jdbc</artifactId>
			<version>11.2.0.jre17</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

1、bean

        User是实体类,与数据库表对应可自行配置

2、config

        JTA 事务配置,固定配置,通过JTATransactionManager实现分布式事务

import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;

/**
 * JTA 事务配置
 */
@Configuration
public class AtomikosConfig {
    @Bean(name = "userTransaction")
    public UserTransaction userTransaction() throws Throwable {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(10000);
        return userTransactionImp;
    }

    @Bean(name = "atomikosTransactionManager")
    public TransactionManager atomikosTransactionManager() throws Throwable {
        UserTransactionManager userTransactionManager = new UserTransactionManager();
        userTransactionManager.setForceShutdown(false);
        return userTransactionManager;
    }

    @Bean(name = "transactionManager")
    @DependsOn({ "userTransaction", "atomikosTransactionManager" })
    public PlatformTransactionManager transactionManager() throws Throwable {
        UserTransaction userTransaction = userTransaction();
        TransactionManager atomikosTransactionManager = atomikosTransactionManager();
        return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
    }
}

3、controller

        控制层

import com.gloyel.twodbgn.Service.UserServiceImpl;
import com.gloyel.twodbgn.Service.UserService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService=new UserServiceImpl();

    @RequestMapping(value = "/userList")
    public String addPre() {
        String msg = "";
        try {
            userService.updateUser("and FID = 7");
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
        return msg;
    }
}

4、ioc

        配置数据库

import java.sql.SQLException;

import javax.sql.DataSource;

import com.atomikos.icatch.jta.UserTransactionManager;
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.Qualifier;
import org.springframework.context.annotation.*;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.microsoft.sqlserver.jdbc.SQLServerXADataSource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;


@Configuration
@MapperScan(basePackages="com.gloyel.twodbgn.mapper.local",sqlSessionTemplateRef="localSqlSessionTemplate")
public class TestMybatisConfig1 {

    @Primary
    @Bean(name="localDataSource")
    @DependsOn("transactionManager")
    public DataSource testDataSource() throws SQLException {
        SQLServerXADataSource sqlServerXADataSource = new SQLServerXADataSource();
        sqlServerXADataSource.setURL("jdbc:sqlserver://localhost:1433;databaseName=mytest;encrypt=true;trustServerCertificate=true");
        sqlServerXADataSource.setUser("username");
        sqlServerXADataSource.setPassword("password");

        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(sqlServerXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("localDataSource");

        atomikosDataSourceBean.setMinPoolSize(3);
        atomikosDataSourceBean.setMaxPoolSize(25);

        return atomikosDataSourceBean;
    }
    @Primary
    @Bean(name="localSqlSessionFactory")
    @DependsOn("localDataSource")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("localDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }
    @Primary
    @Bean(name="localSqlSessionTemplate")
    @DependsOn("localSqlSessionFactory")
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("localSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

/***************************************************************************************/

import com.atomikos.icatch.jta.UserTransactionManager;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.microsoft.sqlserver.jdbc.SQLServerXADataSource;
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.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Lazy;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import javax.transaction.NotSupportedException;
import javax.transaction.SystemException;
import java.sql.SQLException;

@Configuration
@MapperScan(basePackages="com.gloyel.twodbgn.mapper.cloud",sqlSessionTemplateRef="cloudSqlSessionTemplate")
public class TestMybatisConfig2 {

    @Bean(name="cloudDataSource")
    @DependsOn("transactionManager")
    public DataSource testDataSource() throws SQLException {
        SQLServerXADataSource sqlServerXADataSource = new SQLServerXADataSource();
        sqlServerXADataSource.setURL("jdbc:sqlserver://ip:端口;databaseName=mytest;encrypt=true;trustServerCertificate=true");
        sqlServerXADataSource.setUser("username");
        sqlServerXADataSource.setPassword("password");

        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        atomikosDataSourceBean.setXaDataSource(sqlServerXADataSource);
        atomikosDataSourceBean.setUniqueResourceName("cloudDataSource");

        atomikosDataSourceBean.setMinPoolSize(3);
        atomikosDataSourceBean.setMaxPoolSize(25);

        return atomikosDataSourceBean;
    }
    @Bean(name="cloudSqlSessionFactory")
    @DependsOn("cloudDataSource")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("cloudDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }
    @Bean(name="cloudSqlSessionTemplate")
    @DependsOn("cloudSqlSessionFactory")
    public SqlSessionTemplate testSqlSessionTemplate(@Qualifier("cloudSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

5、mapper

@Mapper
public interface UserMapper_C {
    @Select({"select * from User_loginData where 1=1 ${where}"})
    List<User> selectUser(@Param("where")String where);

    @Update({"update Dc_User set FPassWord = 'ccc' where 1=1 ${where}"})
    int updateUser(@Param("where")String where);

}


@Mapper
public interface UserMapper_L {
    @Select({"select * from User_loginData where 1=1 ${where}"})
    List<User> selectUser(@Param("where")String where);

    @Update({"update Dc_User set FPassWord = 'lll' where 1=1 ${where}"})
    int updateUser(@Param("where")String where);

}

6、service

public interface UserService {
    List<User> selectUser(String where);
    int updateUser(String where);
}
@Service("userService")
public class UserServiceImpl implements UserService {
    @Resource
    private UserMapper_L userMapper_l;
    @Resource
    private UserMapper_C userMapper_c;

    @Override
    public List<User> selectUser(String where) {
        return userMapper_c.selectUser(where);
    }

    @Override
    @Transactional
    public int updateUser(String where) {
        int a = userMapper_c.updateUser(where);
//        int c = 1/0;
        int b = userMapper_l.updateUser(where);
        System.out.println("a="+a+" -- b="+b);
        return a+b;
    }
}

五、调试运行

1、运行正常时:

//UserMapper_C 
@Update({"update Dc_User set FPassWord = 'ccc' where 1=1 ${where}"})
int updateUser(@Param("where")String where);

//UserMapper_L
@Update({"update Dc_User set FPassWord = 'lll' where 1=1 ${where}"})
int updateUser(@Param("where")String where);


@Override
    @Transactional
    public int updateUser(String where) {
        int a = userMapper_c.updateUser(where);
//        int c = 1/0;
        int b = userMapper_l.updateUser(where);
        System.out.println("a="+a+" -- b="+b);
        return a+b;
    }

控制台:

云数据库:

本地数据库:

2、运行异常时:

        观察出现异常时,更新语句是否回滚。

//UserMapper_C 
@Update({"update Dc_User set FPassWord = 'ccc000' where 1=1 ${where}"})
int updateUser(@Param("where")String where);

//UserMapper_L
@Update({"update Dc_User set FPassWord = 'lll000' where 1=1 ${where}"})
int updateUser(@Param("where")String where);


@Override
    @Transactional
    public int updateUser(String where) {
        int a = userMapper_c.updateUser(where);
        int c = 1/0;
        int b = userMapper_l.updateUser(where);
        System.out.println("a="+a+" -- b="+b);
        return a+b;
    }

控制台:

云数据库:

本地数据库:

至此,多数据源分布式事务管理项目测试成功!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2045246.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

整理 酷炫 Flutter 优质 布局、交互 开源App

xtimer-flutter-app Flutter 计时器应用 项目地址&#xff1a;https://github.com/pedromassango/xtimer-flutter-app 项目Demo&#xff1a;https://download.csdn.net/download/qq_36040764/89631382

<数据集>骑行头盔识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;5026张 标注数量(xml文件个数)&#xff1a;5026 标注数量(txt文件个数)&#xff1a;5026 标注类别数&#xff1a;3 标注类别名称&#xff1a;[helmet, without_helmet, two_wheeler] 序号类别名称图片数框数1helm…

CCleaner Pro v6.26.11169 中文绿色便携版免安装 下载 Windows缓存清理 磁盘清理 注册表清理 大文件查找

可以清理系统和软件缓存&#xff0c;清理注册表&#xff0c;磁盘分析进行大文件查找 下载地址(资源制作整理不易&#xff0c;下载使用需付费&#xff0c;不能接受请勿浪费时间下载) 链接&#xff1a;https://pan.baidu.com/s/1XLi-J1h-G5xx8QBwXeFMcg?pwd2fen 提取码&#xf…

美股涨跌互现,半导体与科技股受关注

美股市场表现 昨夜&#xff0c;美股三大股指表现不一。道琼斯工业平均指数下跌0.36%&#xff0c;纳斯达克综合指数上涨0.21%&#xff0c;标准普尔500指数持平。科技股表现突出&#xff0c;其中英伟达股价上涨超过4%。债市方面&#xff0c;美国十年期国债收益率下跌至3.905%&am…

大语言模型(LLM)的SIO解构

引言: 大语言模型&#xff08;LLM&#xff09;作为现代人工智能领域的重要突破&#xff0c;已经在自然语言处理&#xff08;NLP&#xff09;中展现出了卓越的能力。这些模型通过深度学习算法和大规模数据训练&#xff0c;在多个语言任务中表现出接近人类的理解与生成能力&#…

七、2 ADC数模转换器有关函数介绍(Keil5)

函数介绍 &#xff08;1&#xff09;ADCCLK的配置函数&#xff08;在rcc.h中&#xff09; &#xff08;2&#xff09;ADC的库函数&#xff08;在adc.h中&#xff09;

将 Amazon SageMaker 机器学习模型与 QuickSight 集成。

增强使用 Amazon SageMaker 和 QuickSight 构建的机器学习模型的概述。 您是否曾想过如何以更简单的方式将 ML 预测添加到您的 BI 平台并分享给业务客户&#xff1f;别担心&#xff01;AWS ML Insights 的 SageMaker 增强型 QuickSight 之一可以满足您的需求&#xff01; 假设…

vue3 antdv3 Select 每行数据的最右侧增加一个x删除按钮,鼠标移上去显示,移走消失,并且点击可以删除当前行的数据。

1、先上个图&#xff0c;这个是实现的效果&#xff1a; 鼠标移动E所在的行&#xff0c;则E的最右侧显示一个x删除按钮&#xff0c;点击x按钮&#xff0c;可以删除当前的数据&#xff0c; 点击增加按钮&#xff0c;可以新增。 2、实现的代码&#xff1a; <Selectv-model:v…

栈与队列 - 用队列实现栈

225. 用队列实现栈 方法一&#xff1a;使用两个队列实现 var MyStack function() {this.queue1 [];this.queue2 []; };/** * param {number} x* return {void}*/ MyStack.prototype.push function(x) {this.queue1.push(x); };/*** return {number}*/ MyStack.prototype.p…

.net实战(VB):连接网页数据

先上效果图 在上程序 Public Sub SendPostRequest(aa As String)MessageBox.Show(sheet.Name)Dim token As StringDim xmlhttp As ObjectDim xmlhttp1 As ObjectDim url As StringDim jsonParams As StringDim response As Stringxmlhttp CreateObject("MSXML2.XMLHTTP&…

【Qt开发】多线程QThread(通过QObject::moveToThread)和QMutex互斥锁的配置和基本函数

【Qt开发】多线程QThread&#xff08;通过QObject::moveToThread&#xff09;和QMutex互斥锁的配置和基本函数 多线程 Qt官方给了两种方法连运行多线程函数 一种是直接用QThread的run()方法 还有一种就是继承QObject 用moveToThread方法去放到QThread里执行 在官方文档中 推…

6.MySQL的增删改查

目录 Create 单行插入数据 全列插入 多行数据指定列插入 插入否则更新 主键冲突 唯一键冲突 &#xff08;☆&#xff09; 替换数据 Retrieve Select列 全列查询 指定列查询 查询字段为表达式 where条件 NULL 的查询 NULL 和 NULL 的比较&#xff0c; 和 <>…

野蛮生长后,AI大模型还需要重复“造轮子”?

AI大模型带来的智能革命&#xff0c;媲美工业革命和电力革命&#xff0c;深刻改变人类社会的生产生活方式&#xff0c;是开启智能时代的那一台“蒸汽机”。 从文生文到文生图&#xff0c;再到文生视频&#xff0c;以ChatGPT、Sora等为代表的大模型引领了全球人工智能技术与产业…

《使用深度学习三分类模型预测胸部CT中的肺腺癌侵袭性》| 文献速递-基于深度学习的乳房、前列腺疾病诊断系统

Title 题目 Predicting Invasiveness of Lung Adenocarcinoma at Chest CT with Deep Learning Ternary Classification Models 《使用深度学习三分类模型预测胸部CT中的肺腺癌侵袭性》 Background 背景 Preoperative discrimination of preinvasive, minimally invasive,…

开关电源之结构分析

如有技术问题及技术需求请加作者微信! 开关电源之结构分析 1、开关电源的结构 常用开关电源,主要是为电子设备提供直流电源供电。电子设备所需要的直流电压,范围一般都在几伏到十几伏,而交流市电电源供给的电压为220V(110V),频率为50Hz(60Hz)。开关电源的作用就是把一…

Surface Studio 曾经耀眼的骚气光芒至今依然

Surface Studio 曾经耀眼的骚气光芒至今依然 Surface Studio当时上市价格25988RMB&#xff0c;现在小黄鱼大概在3000的样子可以买到屏幕没有老化的那种。看着那块巨大的5K触摸屏&#xff0c;简直让人垂涎三尺欲罢不能。 一、卓越配置&#xff0c;尽显强大性能 Surface Studio…

freeRTOS之任务调度

本节课的内容是重中之重&#xff0c;对复习操作系统的任务调度也很有帮助。

API接口自定义字段返回,最终解决方案,再也不用写 vo、dto 转换逻辑了

西湖的晚霞真美啊 前言 此套方案太过完美&#xff0c;唯一缺点就是&#xff0c;字段返回基于反射操作&#xff0c;损失一点点性能 效果 API接口指定使用自定义增强注解MoreSerializeField&#xff0c;标注只返回 departName 字段 前端只会接收到 departName 字段 实现原理 …

Python爬虫——Selenium方法爬取LOL页面

文章目录 Selenium介绍用Selenium方法爬取LOL每个英雄的图片及名字 Selenium介绍 Selenium 是一个用于自动化Web应用程序测试的工具&#xff0c;但它同样可以被用来进行网页数据的抓取&#xff08;爬虫&#xff09;。Selenium 通过模拟用户在浏览器中的操作&#xff08;如点击…

进程间通信---管道通信、命名管道、匿名管道详解

什么是通信&#xff1f; 为什么要通信&#xff1f; 如何做到通信&#xff1f; 管道是什么&#xff1f; 管道用来干什么&#xff1f; 管道如何实现通信&#xff1f; 匿名管道是什么&#xff1f; 如何实现&#xff1f; 命名管道是什么&#xff1f; 如何实现&#xff1f; 什么是文…