Spring Boot集成atomikos快速入门Demo

1.什么是atomikos

Atomikos是一个轻量级的分布式事务管理器,实现了Java Transaction API (JTA)规范,可以很方便的和Spring Boot集成,支持微服务场景下跨节点的全局事务。Atomikos公司官方网址为:www.atomikos.com/。其旗下最著名的产品就...

  • TransactionEssentials:开源的免费产品
  • ExtremeTransactions:上商业版,需要收费。

2.环境搭建

第一个mysql数据库

css 复制代码
docker run --name docker-mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3333:3306 -d mysql

第二个mysql数据库

css 复制代码
docker run --name docker-mysql-2 -e MYSQL_ROOT_PASSWORD=123456 -p 3334:3306 -d mysql

初始化数据

sql 复制代码
create database demo;
create table user_info
(
user_id     varchar(64)          not null primary key,
username    varchar(100)         null ,
age         int(3)               null ,
gender      tinyint(1)           null ,
remark      varchar(255)         null ,
create_time datetime             null ,
create_id   varchar(64)          null ,
update_time datetime             null ,
update_id   varchar(64)          null ,
enabled     tinyint(1) default 1 null
);

说明

复制代码
msyql账号root
mysql密码123456

3.项目代码

**

实验目的:实现2个mysql数据的分布式事务管理,要么全部成功,只要有一个失败就会滚。

**

pom..xml

xml 复制代码
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springboot-demo</artifactId>
        <groupId>com.et</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>atomikos</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.0</version><!-- 1.3.0以上的版本没有@MapperScan以及@Select注解 -->
        </dependency>
        <!-- automatic+jta的分布式事务管理 -->
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jta-atomikos -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jta-atomikos</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <!--boot 2.1默认 mysql8的版本; boot 2.0默认mysql5版本-->
            <version>8.0.13</version>
            <!--<version>5.1.46</version>-->
            <!--<scope>runtime</scope>-->
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
            <version>1.18.2</version>
        </dependency>

    </dependencies>
</project>

mapper

创建2个mapper连接不同的数据库

less 复制代码
package com.et.atomikos.mapper1;

import org.apache.catalina.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

public interface UserInfoMapper1 {
    // query
    @Select("SELECT * FROM user_info WHERE username = #{username}")
    User findByName(@Param("username") String username);

    // add
    @Insert("INSERT INTO user_info(user_id,username, age) VALUES(#{userId},#{username}, #{age})")
    int insert(@Param("userId") String userId,@Param("username") String username, @Param("age") Integer age);
}

package com.et.atomikos.mapper2;

import org.apache.catalina.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

public interface UserInfoMapper2 {
    // query
    @Select("SELECT * FROM user_info WHERE username = #{username}")
    User findByName(@Param("username") String username);

    // add
    @Insert("INSERT INTO user_info(user_id,username, age) VALUES(#{userId},#{username}, #{age})")
    int insert(@Param("userId") String userId,@Param("username") String username, @Param("age") Integer age);
}

service

创建2个service,分别用不同mapper

java 复制代码
package com.et.atomikos.mapper1;

import com.et.atomikos.mapper1.UserInfoMapper1;
import com.et.atomikos.mapper2.UserInfoMapper2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ManyService1 {

    @Autowired
    private UserInfoMapper1 userInfoMapper1;
    @Autowired
    private UserInfoMapper2 userInfoMapper2;


    @Transactional
    public int insert(String userId,String username, Integer age) {
        int insert = userInfoMapper1.insert(userId,username, age);
        int i = 1 / age;// if age is zero  ,then a error will be happened.
        return insert;
    }

    @Transactional
    public int insertDb1AndDb2(String userId,String username, Integer age) {
        int insert = userInfoMapper1.insert(userId,username, age);
        int insert2 = userInfoMapper2.insert(userId,username, age);
        int i = 1 / age;// if age is zero  ,then a error will be happened.
        return insert + insert2;
    }


}

package com.et.atomikos.mapper2;

import com.et.atomikos.mapper2.UserInfoMapper2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ManyService2 {

    @Autowired
    private UserInfoMapper2 userInfoMapper2;

    @Transactional
    public int insert(String userId,String username, Integer age) {
        int i = userInfoMapper2.insert(userId,username, age);
        System.out.println("userInfoMapper2.insert end :" + null);
        int a = 1 / 0;//touch a error
        return i;
    }

}

config

初始化数据源1和数据源2

java 复制代码
package com.et.atomikos.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "spring.datasource.test1")
public class DBConfig1 {
   // @Value("${mysql.datasource.test1.jdbcurl}")
   //@Value("${jdbcurl}")
    private String jdbcurl;
    //private String url;
    private String username;
    private String password;
    private int minPoolSize;
    private int maxPoolSize;
    private int maxLifetime;
    private int borrowConnectionTimeout;
    private int loginTimeout;
    private int maintenanceInterval;
    private int maxIdleTime;
    private String testQuery;
}

package com.et.atomikos.config;

import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.mysql.cj.jdbc.MysqlXADataSource;
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 javax.sql.DataSource;
import java.sql.SQLException;

/**
 * @author liuhaihua
 * @version 1.0
 * @ClassName MyBatisConfig1
 * @Description todo
 * @date 2024年04月18日 13:37
 */

@Configuration
@MapperScan(basePackages = "com.et.atomikos.mapper1", sqlSessionTemplateRef = "test1SqlSessionTemplate")
public class MyBatisConfig1 {

    @Bean(name = "test1DataSource")  //test1DataSource
    public DataSource testDataSource(DBConfig1 testConfig) throws SQLException {
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        //mysqlXaDataSource.setUrl(testConfig.getUrl());
        mysqlXaDataSource.setUrl(testConfig.getJdbcurl());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(testConfig.getPassword());
        mysqlXaDataSource.setUser(testConfig.getUsername());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        // 将本地事务注册到创 Atomikos全局事务
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName("test1DataSource");

        xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
        xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
        xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
        xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
        xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
        xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
        xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
        xaDataSource.setTestQuery(testConfig.getTestQuery());
        return xaDataSource;
    }

    @Bean(name = "test1SqlSessionFactory")
    public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        return bean.getObject();
    }

    @Bean(name = "test1SqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(
            @Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

数据源2也是类似配置

controller

kotlin 复制代码
package com.et.atomikos.controller;

import com.et.atomikos.mapper1.ManyService1;
import com.et.atomikos.mapper2.ManyService2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class HelloWorldController {

    @Autowired
    private ManyService1 manyService1;

    @Resource
    private ManyService2 manyService2;
    //http://localhost:8088/datasource1?userId=9&username=datasource1&age=2
    @RequestMapping(value = "datasource1")
    public int datasource1(String userId,String username, Integer age) {
        return manyService1.insert(userId,username, age);
    }
    //http://localhost:8088/datasource2?userId=9&username=datasource2&age=2
    @RequestMapping(value = "datasource2")
    public int datasource2(String userId,String username, Integer age) {
        return manyService2.insert(userId,username, age);
    }

    //http://localhost:8088/insertDb1AndDb2?userId=1&username=tom5&age=2
    //http://localhost:8088/insertDb1AndDb2?userId=2&username=tom5&age=0  //touch a error
    @RequestMapping(value = "insertDb1AndDb2")
    public int insertDb1AndDb2(String userId,String username, Integer age) {
        return manyService1.insertDb1AndDb2(userId,username, age);
    }

}

application.yaml

yaml 复制代码
server:
  port: 8088

spring:
  application:
    name: manyDatasource
  datasource:
    #  spring.datasource.test1
    #    druid:
    test1:
      jdbcurl: jdbc:mysql://localhost:3333/demo?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
      username: root
      password: 123456
      initial-size: 1
      min-idle: 1
      max-active: 20
      test-on-borrow: true
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
      minPoolSize: 3
      maxPoolSize: 25
      maxLifetime: 20000
      borrowConnectionTimeout: 30
      loginTimeout: 30
      maintenanceInterval: 60
      maxIdleTime: 60

    test2:
      jdbcurl: jdbc:mysql://localhost:3334/demo?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource
      minPoolSize: 3
      maxPoolSize: 25
      maxLifetime: 20000
      borrowConnectionTimeout: 30
      loginTimeout: 30
      maintenanceInterval: 60
      maxIdleTime: 60

mybatis:
  mapper-locations: classpath:mapper/*.xml


spring.resources.static-locations: classpath:static/,file:static/

logging:
  level:
    czs: debug
    org.springframework: WARN
    org.spring.springboot.dao: debug

以上只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

4.测试

启动Spring Boot 应用

插入第一个数据测试

bash 复制代码
http://localhost:8088/datasource1?userId=9&username=datasource1&age=2

插入第二个数据库

bash 复制代码
http://localhost:8088/datasource2?userId=9&username=datasource2&age=2

同时插入2个数据库

bash 复制代码
http://localhost:8088/insertDb1AndDb2?userId=1&username=tom5&age=2

异常回滚测试

bash 复制代码
http://localhost:8088/insertDb1AndDb2?userId=2&username=tom5&age=0  //touch a error

5.参考

相关推荐
Jinkxs1 分钟前
Java 部署:Jenkins Pipeline 构建 Java 项目(自动化)
java·spring boot
Jinkxs5 分钟前
Java 部署:滚动更新(K8s RollingUpdate 策略)
java·开发语言·kubernetes
a8a3026 分钟前
Spring Boot 3.3.4 升级导致 Logback 之前回滚策略配置不兼容问题解决
java·spring boot·logback
jfqqqqq12 分钟前
win11下intelliJ idea的shift + F6无效
java·ide·intellij-idea
xu_ws13 分钟前
Spring-ai项目-deepseek-7-Function Calling(智能客服)
java·人工智能·spring
逝水如流年轻往返染尘25 分钟前
JAVA中的抽象类
java·开发语言
hx862271 小时前
Java MySQL 连接
java·mysql·adb
lpfasd1231 小时前
Kubernetes (K8s) 底层早已不再直接使用 Docker 引擎了
java·docker·kubernetes
aq55356001 小时前
SpringBoot有几种获取Request对象的方法
java·spring boot·后端
steel80882 小时前
Spring Boot 整合 log4j2 日志配置教程
spring boot·单元测试·log4j