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账号rootmysql密码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;
@Servicepublic 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;
@Servicepublic 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;
@RestControllerpublic 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

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

代码仓库

  • https://github.com/Harries/springboot-demo

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.参考

  • https://github.com/ColoZhu/springbootmanyDatasource

  • http://www.liuhaihua.cn/archives/710435.html

相关推荐

  • 百亿美金开源模型Llama3: 小札最新2万字访谈披露一切 · 全纪录+视频
  • 生成式AI大会沸腾北京!从Sora到具身智能,25位大佬密集输干货
  • [开源]一款彻底开源的前后端分离的低代码开发框架,已集成工作流
  • 微服务全做错了?谷歌提出新方法,成本直接降9倍!
  • Stable Diffusion3 性能比肩 Midjourney v6
  • 为啥我敢说Python是数据分析界的扛把子语言?
  • 时间与玫瑰——关于《工作的心智》一书
  • 前端代码规范 - 编辑器&代码风格
  • 生成式AI如何告别色情、版权困扰?基于权重显著性的机器遗忘算法有奇效
  • 波士顿动力新版人形机器人Atlas问世,纯电驱动
  • 华为Pura70系列发布:麒麟9010+可伸缩镜头,上线秒抢光
  • 2024谷歌研究学者计划名单公布:清华、北大、上交ACM班等校友在列
  • 谁才是最强的?清华给海内外知名大模型做了场综合能力评测
  • 港股IPO招股异常火爆,唯一盈利的「AIGC第一股」是怎么炼成的?
  • 微软炸裂级单图生数字人,Sora同款思路,“比AI刘强东还真”
  • 波士顿动力断臂转型!弃液压转电驱,新机器人再次惊艳世界
  • Sora问世,如何用大模型反诈?
  • 万字梳理中国AIGC产业峰会激辩,大模型应用最全行业参考在此
  • 超级大厂,史上最高比例裁员!! !
  • 增长算法工程师招聘