微服务技术系列教程(04) - SpringBoot - 事务管理

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 微服务技术系列教程(04) - SpringBoot - 事务管理

代码已上传到Github,有兴趣的同学可以下载来看看:https://github.com/ylw-github/SpringBoot-Transaction-Demo

1. SpringBoot事务管理

SpringBoot默认集成事务,只主要在方法上加上@Transactional即可

操作比较简单,此处不再详述,可以参考:https://blog.csdn.net/justry_deng/article/details/80828180

2. SpringBoot分布式事务管理

可以使用springboot+jta+atomikos 进行分布式事务管理,下面来详细介绍集成的步骤:

2.1 添加mave依赖
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.0.RELEASE</version>
</parent>
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jta-atomikos</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
  </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</artifactId>
    <version>1.1.1</version>
  </dependency>
  <!-- mysql 依赖 -->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>
  <!-- springboot-web组件 -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
</dependencies>
2.2 配置application.properties
# Mysql 1
mysql.datasource.test1.url = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
mysql.datasource.test1.username = root
mysql.datasource.test1.password = 123456
mysql.datasource.test1.minPoolSize = 3
mysql.datasource.test1.maxPoolSize = 25
mysql.datasource.test1.maxLifetime = 20000
mysql.datasource.test1.borrowConnectionTimeout = 30
mysql.datasource.test1.loginTimeout = 30
mysql.datasource.test1.maintenanceInterval = 60
mysql.datasource.test1.maxIdleTime = 60
# Mysql 2
mysql.datasource.test2.url =jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8
mysql.datasource.test2.username =root
mysql.datasource.test2.password =123456
mysql.datasource.test2.minPoolSize = 3
mysql.datasource.test2.maxPoolSize = 25
mysql.datasource.test2.maxLifetime = 20000
mysql.datasource.test2.borrowConnectionTimeout = 30
mysql.datasource.test2.loginTimeout = 30
mysql.datasource.test2.maintenanceInterval = 60
mysql.datasource.test2.maxIdleTime = 60
2.3 ConfigurationProperties

总共要写两个配置类DBConfig1和DBConfig2:

@ConfigurationProperties(prefix = "mysql.datasource.test1")
public class DBConfig1 {
  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;
  //getter setter...
//DBConfig2///
@ConfigurationProperties(prefix = "mysql.datasource.test2")
public class DBConfig2 {
  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;
  //getter setter...
2.4 配置数据源

配置的时候注意,每个扫描mapper的包位置不一致。

数据源1:

package com.ylw.datasource;
@Configuration
// basePackages 最好分开配置 如果放在同一个文件夹可能会报错
@MapperScan(basePackages = "com.ylw.mapper.test01", sqlSessionTemplateRef = "testSqlSessionTemplate")
public class MyBatisConfig1 {
  // 配置数据源
  @Bean(name = "testDataSource")
  public DataSource testDataSource(DBConfig1 testConfig) throws SQLException {
    MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
    mysqlXaDataSource.setUrl(testConfig.getUrl());
    mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
    mysqlXaDataSource.setPassword(testConfig.getPassword());
    mysqlXaDataSource.setUser(testConfig.getUsername());
    mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
    // 将本地事务注册到创 Atomikos全局事务
    AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
    xaDataSource.setXaDataSource(mysqlXaDataSource);
    xaDataSource.setUniqueResourceName("testDataSource");
    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 = "testSqlSessionFactory")
  public SqlSessionFactory testSqlSessionFactory(@Qualifier("testDataSource") DataSource dataSource)
      throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    return bean.getObject();
  }
  @Bean(name = "testSqlSessionTemplate")
  public SqlSessionTemplate testSqlSessionTemplate(
      @Qualifier("testSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
    return new SqlSessionTemplate(sqlSessionFactory);
  }
}

数据源2:

@Configuration
@MapperScan(basePackages = "com.ylw.mapper.test02", sqlSessionTemplateRef = "test2SqlSessionTemplate")
public class MyBatisConfig2 {
  // 配置数据源
  @Bean(name = "test2DataSource")
  public DataSource testDataSource(DBConfig2 testConfig) throws SQLException {
    MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
    mysqlXaDataSource.setUrl(testConfig.getUrl());
    mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
    mysqlXaDataSource.setPassword(testConfig.getPassword());
    mysqlXaDataSource.setUser(testConfig.getUsername());
    mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
    AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
    xaDataSource.setXaDataSource(mysqlXaDataSource);
    xaDataSource.setUniqueResourceName("test2DataSource");
    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 = "test2SqlSessionFactory")
  public SqlSessionFactory testSqlSessionFactory(@Qualifier("test2DataSource") DataSource dataSource)
      throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    return bean.getObject();
  }
  @Bean(name = "test2SqlSessionTemplate")
  public SqlSessionTemplate testSqlSessionTemplate(
      @Qualifier("test2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
    return new SqlSessionTemplate(sqlSessionFactory);
  }
}
2.5 配置Mapper

注意包结构不一样。

UserMapperTest01:

package com.ylw.mapper.test01;
public interface UserMapperTest01 {
  // 查询语句
  @Select("SELECT * FROM t_user WHERE name = #{name}")
  User findByName(@Param("name") String name);
  // 添加
  @Insert("INSERT INTO t_user(uuid,name, age) VALUES(#{uuid},#{name}, #{age})")
  int insert(@Param("uuid") String uuid,@Param("name") String name, @Param("age") Integer age);
}

UserMapperTest02:

package com.ylw.mapper.test02;
public interface UserMapperTest02 {
  // 查询语句
  @Select("SELECT * FROM t_user WHERE name = #{name}")
  User findByName(@Param("name") String name);
  // 添加
  @Insert("INSERT INTO t_user(uuid,name, age) VALUES(#{uuid},#{name}, #{age})")
  int insert(@Param("uuid") String uuid,@Param("name") String name, @Param("age") Integer age);
}
2.6 业务类

UserServiceTest01:

package com.ylw.service.test01;
@Service
public class UserServiceTest01 {
    @Autowired
    private UserMapperTest01 userMapperTest01;
    @Transactional
    public int insertUser(String name, Integer age) {
        int insertUserResult = userMapperTest01.insert(UUID.randomUUID().toString(), name, age);
        System.out.println("######insertUserResult:{}##########-> " + insertUserResult);
        //int i = 1 / age;
        // 验证事务开启
        return insertUserResult;
    }
}

UserServiceTest02:

package com.ylw.service.test02;
@Service
public class UserServiceTest02 {
    @Autowired
    private UserMapperTest02 userMapperTest02;
    @Autowired
    private UserMapperTest01 userMapperTest01;
    @Transactional
    public int insertUser(String name, Integer age) {
        int insertUserResult = userMapperTest02.insert(UUID.randomUUID().toString(), name, age);
        System.out.println("######insertUserResult:{}########## -> " + insertUserResult);
        // 怎么样验证事务开启成功!~
        //int i = 1 / age;
        return insertUserResult;
    }
    @Transactional()
    public int insertUserTest01AndTest02(String name, Integer age) {
        // 传统分布式事务解决方案 jta+atomikos 注册同一个全局事务中
        // 第一个数据源
        int insertUserResult01 = userMapperTest01.insert(UUID.randomUUID().toString(), name, age);
        // 第二个数据源
        int insertUserResult02 = userMapperTest02.insert(UUID.randomUUID().toString(), name, age);
        //int i = 1 / 0;
        int result = insertUserResult01 + insertUserResult02;
        // test01入库 test02回滚
        return result;
    }
}
2.7 启动类与Controller

启动类,要配置EnableConfigurationProperties:

// 开启读取配置文件
@EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class })
@SpringBootApplication
public class MybatisApp03 {
  public static void main(String[] args) {
    SpringApplication.run(MybatisApp03.class, args);
  }
}

controller:

@RestController
public class MybatisMultilDataSourceController {
  @Autowired
  private UserServiceTest01 userServiceTest01;
  @Autowired
  private UserServiceTest02 userServiceTest02;
  @RequestMapping(value = "/insertUserTest1" ,method = RequestMethod.GET)
  public Integer insertUserTest1(String name, Integer age) {
    return userServiceTest01.insertUser(name, age);
  }
  @RequestMapping(value = "/insertUserTest2",method = RequestMethod.GET)
  public Integer insertUserTest2(String name, Integer age) {
    return userServiceTest02.insertUser(name, age);
  }
  @RequestMapping(value ="/insertUserTest01AndTest02",method = RequestMethod.GET)
  public int insertUserTest01AndTest02(String name, Integer age) {
    return userServiceTest02.insertUserTest01AndTest02(name, age);
  }
}
2.8 测试

在浏览器打开三个页面,地址分别是:

运行后,会发现两个数据库均添加了数据:

总结

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
打赏
0
0
1
0
239
分享
相关文章
Springboot3新特性:开发第一个 GraalVM 本机应用程序(完整教程)
文章介绍如何在Spring Boot 3中利用GraalVM将Java应用程序编译成独立的本机二进制文件,从而提高启动速度、减少内存占用,并实现不依赖JVM运行。
454 1
Springboot3新特性:开发第一个 GraalVM 本机应用程序(完整教程)
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
369 2
|
2月前
|
为何内存不够用?微服务改造启动多个Spring Boot的陷阱与解决方案
本文记录并复盘了生产环境中Spring Boot应用内存占用过高的问题及解决过程。系统上线初期运行正常,但随着业务量上升,多个Spring Boot应用共占用了64G内存中的大部分,导致应用假死。通过jps和jmap工具排查发现,原因是运维人员未设置JVM参数,导致默认配置下每个应用占用近12G内存。最终通过调整JVM参数、优化堆内存大小等措施解决了问题。建议在生产环境中合理设置JVM参数,避免资源浪费和性能问题。
135 3
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
151 1
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
深入探索Spring Cloud与Spring Boot:构建微服务架构的实践经验
212 5
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
本文详细讲解了如何整合Apache Shiro与Spring Boot项目,包括数据库准备、项目配置、实体类、Mapper、Service、Controller的创建和配置,以及Shiro的配置和使用。
952 1
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
SpringBoot微服务打包Docker镜像
SpringBoot微服务打包Docker镜像
127 11
|
4月前
|
简单两步,Spring Boot 写死的定时任务也能动态设置:技术干货分享
【10月更文挑战第4天】在Spring Boot开发中,定时任务通常通过@Scheduled注解来实现,这种方式简单直接,但存在一个显著的限制:任务的执行时间或频率在编译时就已经确定,无法在运行时动态调整。然而,在实际工作中,我们往往需要根据业务需求或外部条件的变化来动态调整定时任务的执行计划。本文将分享一个简单两步的解决方案,让你的Spring Boot应用中的定时任务也能动态设置,从而满足更灵活的业务需求。
318 4
基于人工智能技术的智能导诊系统源码,SpringBoot作为后端服务的框架,提供快速开发,自动配置和生产级特性
当身体不适却不知该挂哪个科室时,智能导诊系统应运而生。患者只需选择不适部位和症状,系统即可迅速推荐正确科室,避免排错队浪费时间。该系统基于SpringBoot、Redis、MyBatis Plus等技术架构,支持多渠道接入,具备自然语言理解和多输入方式,确保高效精准的导诊体验。无论是线上医疗平台还是大型医院,智能导诊系统均能有效优化就诊流程。
126 0
微服务架构演变与架构设计深度解析
【11月更文挑战第14天】在当今的IT行业中,微服务架构已经成为构建大型、复杂系统的重要范式。本文将从微服务架构的背景、业务场景、功能点、底层原理、实战、设计模式等多个方面进行深度解析,并结合京东电商的案例,探讨微服务架构在实际应用中的实施与效果。
225 6
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等