文章目录
- 概念
- LCN模式
- 创建父工程parent
- 创建子工程TxManager: 管理事务
- 创建子工程: Eureka Server 注册中心
- 创建子工程: book: 被远程调用方
- 创建子工程: student: 远程调用方
- TCC模式
- 在lcn的基础上创建子工程: redistest
- 在student 调用 redistest
概念
TX-LCN由两大模块组成,TxClient、TxManager。
TxClient作为模块的依赖框架,提供了TX-LCN的标准支持,事务发起方和参与方都属于TxClient。TxManager作为分布式事务的控制方,控制整个事务。
1.创建事务组: 是指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标识GroupId的过程。
2.加入事务组: 添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息通知给TxManager的操作。
3.通知事务组: 是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager,TxManager将根据事务最终状态和事务组的信息来通知相应的参与模块提交或回滚事务,并返回结果给事务发起方。
TX-LCN事务模式: Tx-LCN 5.0开始支持三种事务模式,分别是:LCN、TCC、TXC模式,每种模式在实际使用时有着自己对应的注解。
LCN:@LcnTransaction
TCC:@TccTransaction
TXC:@TxcTransaction
LCN模式
LCN模式是通过代理JDBC中Connection的方式实现对本地事务的操作,然后在由TxManager统一协调控制事务。当本地事务提交回滚或者关闭连接时将会执行假操作,该代理的连接将由LCN连接池管理。
LCN模式适合能用JDBC连接的所有支持事务的数据库
测试思路:
开发两个微服务,注册在Eureka注册中心:
- 微服务1:完成一个单表访问操作。新增和查询。一个学生可以有多个书籍
- 微服务2:完成一个单表访问操作。新增和查询。一个书籍属于一个学生。
添加学生同时添加书籍,两者同时完成或同时不完成
LcnTransaction - 当前方法使用TX-LCN框架的LCN模式,管理分布式事务。
所有的事务参与方,自动加入事务组。
如果事务参与方,使用的事务管理模式不一致,可以增加其他注解。
@LcnTransaction属性propagation可取值
DTXPropagation.REQUIRED:默认值,表示如果当前没有事务组创建事务组,如果有事务组,加入事务组。多用在事务发起方。
DTXPropagation.SUPPORTS:如果当前没有事务组以本地事务运行,如果当前有事务组加入事务组。多用在事务参与方法。
创建父工程parent
导入依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
创建子工程TxManager: 管理事务
1.导入依赖:
<dependencies>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
2.properties文件配置,注意不是yml
注意: 其他项目加入事务,注册到这个项目中的端口号是,这个项目的tomcat端口号加100 (默认)或者自己手动指定tx-lcn.manager.port=7971
spring.application.name=TransactionManager
server.port=7970
# 操作事务组记录的连接在数据库中
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=fsfs
# 分布式锁实现在redis中
spring.redis.host=localhost
# TM服务器IP修改, TC访问TM时,使用的IP地址。必须精确匹配。默认127.0.0.1。代表TC和TM必须在同一个主机中。
tx-lcn.manager.host=127.0.0.1
# TM服务器日志系统配置,默认关闭日志系统。需要独立配置日志的存储数据库连接
tx-lcn.logger.enabled=false
# 修改登录密码,默认: codingapi
tx-lcn.manager.admin-key=fsfs
# TM事务管理端口。默认是server.port + 100计算得到。可以手动指定
# tx-lcn.manager.port=7971
# 配置TM服务器日志系统数据库连接
#tx-lcn.logger.driver-class-name=com.mysql.cj.jdbc.Driver
#tx-lcn.logger.jdbc-url=jdbc:mysql://localhost:3306/tx-manager?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
#tx-lcn.logger.username=root
#tx-lcn.logger.password=fsfs
3.导入SQL
在依赖的资源txlcn-tm中,包含TxManager需要的数据库SQL脚本文件tx-manager.sql,可以查找并执行此脚本为TxManager创建需要的数据库表格。
脚本如下:
CREATE DATABASE IF NOT EXISTS `tx-manager` DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
USE `tx-manager`;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_tx_exception
-- ----------------------------
DROP TABLE IF EXISTS `t_tx_exception`;
CREATE TABLE `t_tx_exception` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`group_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`unit_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`mod_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`transaction_state` tinyint(4) NULL DEFAULT NULL,
`registrar` tinyint(4) NULL DEFAULT NULL,
`ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 待处理 1已处理',
`remark` varchar(10240) NULL DEFAULT NULL COMMENT '备注',
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 967 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
4.启动类加注解@EnableTransactionManagerServer
@SpringBootApplication
@EnableTransactionManagerServer
public class ManagerApplicationStart {
public static void main(String[] args) {
SpringApplication.run(ManagerApplicationStart.class,args);
}
}
5.启动并访问管理平台
通过浏览器访问 http://localhost:7970 。在界面中输入登录密码:bjsxt(默认密码是codingapi)。
创建子工程: Eureka Server 注册中心
创建Eureka Server 注册中心
创建子工程: book: 被远程调用方
1.导入依赖:
<dependencies>
向eureka注册
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
实体类所在模块
<dependency>
<groupId>com.example</groupId>
<artifactId>model</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
连接数据库
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
分步式事务相关
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
分步式事务相关
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
2.yml配置
server:
port: 9999
spring:
application:
name: book
datasource:
url: jdbc:mysql://localhost:3306/test?charcterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: fsfs
eureka:
client:
service-url:
defaultZone: http://localhost:8081/eureka/
# 配置TM服务器地址。格式是 ip:port。 ip和端口查看TM WEB控制台中的IP和端口。
tx-lcn:
client:
manager-address: 127.0.0.1:8070
3.被调用的代码:
controller
@RestController
public class BookController {
@Autowired
private BookService bookService;
@RequestMapping("/insert")
public void insert(){
System.out.println("abc");
bookService.insert(new Book(1,"abc",17));
}
}
4.启动类加注解@EnableDistributedTransaction
@MapperScan("com.example.mapper")
@SpringBootApplication
@EnableDistributedTransaction
public class BookApplicationStart {
public static void main(String[] args) {
SpringApplication.run(BookApplicationStart.class,args);
}
}
创建子工程: student: 远程调用方
1.导入依赖:
<dependencies>
实体所在模块
<dependency>
<groupId>com.example</groupId>
<artifactId>model</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
分步式事务相关
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
分步式事务相关
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
2.yml配置文件
server:
port: 8888
spring:
application:
name: student
datasource:
url: jdbc:mysql://localhost:3306/test?charcterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: fsfs
eureka:
client:
service-url:
defaultZone: http://localhost:8081/eureka/
# 配置TM服务器地址。格式是 ip:port。 ip和端口查看TM WEB控制台中的IP和端口。
tx-lcn:
client:
manager-address: 127.0.0.1:8070
3.远程调用
@Service
public class StuentServiceImpl implements StudentService {
@Resource
private StudentMapper studentMapper;
@Autowired
private BookOpenFeign bookOpenFeign;
@Override
@Transactional
@LcnTransaction
public void insert(Student zs) {
//int a = 10/0;本地事务可以回滚
studentMapper.insert(zs);
//int a = 10/0;本地事务可以回滚
bookOpenFeign.insert();//此处发生异常,则book的本地事务可以回滚book,异常抛到student本地事务可以回滚
int a = 10/0;//student的本地事务可以回滚,但book无论如何都回滚不了,所以要使用分布式事务
}
}
4.启动类加注解
@MapperScan("com.example.mapper")
@SpringBootApplication
@EnableFeignClients(basePackages = "com.example.service")
@EnableDistributedTransaction
public class StudentApplicatonStart {
public static void main(String[] args) {
SpringApplication.run(StudentApplicatonStart.class,args);
}
}
5.执行
发现两个表中都没有添加数据
TCC模式
TCC事务不依赖资源管理器对XA的支持,而是通过对由业务系统提供的对业务逻辑的调度来实现分布式事务。
主要由三步操作:
Try:尝试执行业务(添加了@TccTransaction注解的方法)
Confirm:确认执行业务(确认方法作用编写提交事务的代码。当分布式事务执行成功时执行的方法,此方法需要程序员自己提供,自己维护。对于Mongo或Redis这种不支持事务的数据库都不去写此方法。)
Cancel:取消执行业务(需要程序员自己编写,自己维护,编写回滚事务的逻辑)。
- 该模式对代码的侵入性高,要求每个业务需要写二个以上步骤的操作。
- 该模式对有无本地事务控制都可以支持,使用面广。
- 数据一致性控制几乎完全由开发者控制,对业务开发难度要求高。
总结:Tcc模式应用于所有不支持XA事务的软件。例如:Redis,Elasticsearch等。
在lcn的基础上创建子工程: redistest
1.导入依赖
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>model</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.example</groupId>
<artifactId>model</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
2.yml配置:
server:
port: 6666
spring:
application:
name: redistest
datasource:
url: jdbc:mysql://localhost:3306/test?charcterEncoding=utf8&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: fsfs
redis:
host: localhost
eureka:
client:
service-url:
defaultZone: http://localhost:8081/eureka/
tx-lcn:
client:
manager-address: 127.0.0.1:8070
3.序列化器配置
@Configuration
public class PhoneManagerConfiguration {
/**
* redis连接工厂由spring-boot-starter-data-redis自动创建。
* 根据配置文件,连接服务器。
* @param factory
* @return
*/
protected RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> redisTemplate =
new RedisTemplate<>();
// 配置,访问Redis服务器时,如何序列化键值对数据对象。
// key使用字符串序列化方式。就是字符串原值。
redisTemplate.setKeySerializer(new StringRedisSerializer());
// value使用JSON序列化方式。就是把Java对象,转换成JSON字符串并保存。
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// hash数据的key序列化方案。字符串原值
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// hash数据的value序列化方案,JSON格式字符串
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
// 设置Redis连接工厂
redisTemplate.setConnectionFactory( factory );
return redisTemplate;
}
}
4.业务层配置
分布式事务参与方法,需要使用注解@TccTransaction注解修饰
建议:为提示开发者和后期的维护人员,当前方法有事务管理,
在方法上增加事务管理注解@Transactional。
使用TCC事务管理模式,做事务控制。
要求定义confirm和cancel方法。
confirm方法定义要求。实现事务提交逻辑。
方法名: 是 confirm + try方法名称首字母转大写
访问修饰符、参数表、返回值、抛出异常,和try方法一致
cancel方法定义要求。实现事务回滚逻辑
方法名: 是 cancel + try方法名称首字母转大写
访问修饰符、参数表、返回值、抛出异常,和try方法一致
@Service
public class RedisTestServiceImpl {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Transactional
@TccTransaction(confirmMethod = "confirmShow",cancelMethod = "cancelShow")
public void show(){
redisTemplate.opsForValue().set("a","1");
}
public void confirmShow(){
System.out.println("confirmShow");
}
public void cancelShow(){
System.out.println("cancelShow");
}
}
5.启动类配置加注解@EnableDistributedTransaction
@SpringBootApplication
@EnableDistributedTransaction
public class RedisApplicationStart {
public static void main(String[] args) {
SpringApplication.run(RedisApplicationStart.class,args);
}
}
在student 调用 redistest
远程调用
@Service
public class StuentServiceImpl implements StudentService {
@Resource
private StudentMapper studentMapper;
@Autowired
private BookOpenFeign bookOpenFeign;
@Autowired
private RedisttestOpenFeign redisttestOpenFeign ;
@Override
@Transactional
@LcnTransaction
public void insert(Student zs) {
//int a = 10/0;本地事务可以回滚
studentMapper.insert(zs);
//int a = 10/0;本地事务可以回滚
bookOpenFeign.insert();//此处发生异常,则book的本地事务可以回滚book,异常抛到student本地事务可以回滚
redisttestOpenFeign .insert();//此处发生异常,使用@TccTransaction(confirmMethod = "confirmShow",cancelMethod = "cancelShow")
int a = 10/0;//student的本地事务可以回滚,但book无论如何都回滚不了,所以要使用分布式事务,非jdbc,使用@TccTransaction(confirmMethod = "confirmShow",cancelMethod = "cancelShow")
}
}
执行该方法:
控制台打印: cancelShow