原创

Spring Boot集成atomikos快速入门Demo

1.什么是atomikos

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

2.环境搭建

第一个mysql数据库

docker run --name docker-mysql -e MYSQL_ROOT_PASSWORD=123456 -p 3333:3306 -d mysql

第二个mysql数据库

docker run --name docker-mysql-2 -e MYSQL_ROOT_PASSWORD=123456 -p 3334:3306 -d mysql

初始化数据

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 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连接不同的数据库
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
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
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

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

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 应用

插入第一个数据测试

http://localhost:8088/datasource1?userId=9&username=datasource1&age=2

插入第二个数据库

http://localhost:8088/datasource2?userId=9&username=datasource2&age=2

同时插入2个数据库

http://localhost:8088/insertDb1AndDb2?userId=1&username=tom5&age=2

异常回滚测试

http://localhost:8088/insertDb1AndDb2?userId=2&username=tom5&age=0  //touch a error

5.参考

 
正文到此结束
Loading...