- TX-LCN 主要有两个模块,Tx-Client(TC) Tx-Manager(TM). TC作为微服务下的依赖,TM是独立的服务。
- LCN原理如下
- 创建事务组
是指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标示GroupId的过程。 - 加入事务组
添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息通知给TxManager的操作。 - 通知事务组
是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager,TxManager将根据事务最终状态和事务组的信息来通知相应的参与模块提交或回滚事务,并返回结果给事务发起方。
- 首先我们需要二个微服务项目订单服务和会员服务,订单服务通过feign客户端调用会员服务,不清楚的可以参考上一篇文章:一叶知秋:springcloud整合注册中心和feign客户端简单教程。
- 首先整合TM
a 创建MySQL数据库, 名称为: tx-manager,创建数据表
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,
`remark` varchar(4096) NULL DEFAULT NULL,
`ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 未解决 1已解决',
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
b 官网下载codingapi/tx-lcn源码和demo:https://github.com/codingapi/txlcn-demo
用idea的maven插件打包源码包TM项目txlcn-tm,txlcn-demo-tm项目的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>txlcn-demo</artifactId>
<groupId>org.txlcn.demo</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>txlcn-demo-tm</artifactId>
<name>txlcn-demo-tm</name>
<dependencies>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tm</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
c txlcn-demo-tm项目的application.properties文件如下,注意要配置自己的数据库和redis
的地址以及将txlcn-demo-tm注册到eureka中
##################
# 这个是启动本服务的配置文件,其它的application-xxx.properties 是开发者的个性化配置,不用关心。
# 你可以在 https://txlcn.org/zh-cn/docs/setting/manager.html 看到所有的个性化配置
#################
spring.application.name=TransactionManager
server.port=7970
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.137.129:3306/tx-manager?characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
redis.database=0
redis.host=127.0.0.1
redis.port=6379
redis.password=
redis.jedis.pool.max-active=50
redis.jedis.pool.max-wait=-1
redis.jedis.pool.max-idle=50
redis.jedis.pool.min-idle=1
redis.timeout=10000
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.use-generated-keys=true
eureka.client.serviceUrl.defaultZone=http://localhost:8600/eureka/
eureka.instance.prefer-ip-address=true
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
- 订单项目和会员项目需要整合Tx-Client(TC)
- 订单项目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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.liwen</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../parent</relativePath>
</parent>
<groupId>com.liwen</groupId>
<artifactId>order-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>order-service</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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.liwen</groupId>
<artifactId>member-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.29</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
- 订单项目配置文件
spring:
application:
name: member-service
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.137.129:3306/member?useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
server:
port: 9200
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8600/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
#微服务和TM的通讯地址
tx-lcn:
client.manager-address: 127.0.0.1:8070
- 订单项目项目启动类
package com.liwen;
import com.codingapi.txlcn.tc.config.EnableDistributedTransaction;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
//分布式事务直注解
@EnableDistributedTransaction
public class MemberServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MemberServiceApplication.class, args);
}
}
- 订单项目调用会员项目
package com.liwen.service;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.liwen.entity.Member;
import com.liwen.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
public class MemberService implements IMemberService{
@Autowired
private MemberRepository memberRepository;
@LcnTransaction //分布式事务注解
@Transactional
@Override
public String saveMemberMember() {
Member member = new Member();
member.setId(null);
member.setMyName("lwp");
member.setTime(new Date());
Member save = memberRepository.save(member);
int i= 1/0;
System.out.println("会员服务被调用。。。。。");
return "SUCCESS";
}
}
- 会员项目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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.liwen</groupId>
<artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../parent</relativePath>
</parent>
<groupId>com.liwen</groupId>
<artifactId>member-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>member-service</name>
<description>Demo project for Spring Boot</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.liwen</groupId>
<artifactId>member-service-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.29</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
会员项目配置
spring:
application:
name: member-service
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.137.129:3306/member?useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
server:
port: 9200
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8600/eureka/
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
tx-lcn:
client.manager-address: 127.0.0.1:8070
- 会员项目启动类
package com.liwen;
import com.codingapi.txlcn.tc.config.EnableDistributedTransaction;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableDistributedTransaction
public class MemberServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MemberServiceApplication.class, args);
}
}
- 会员项目全局捕获异常
package com.liwen;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public JSONObject exceptionHandler(Exception e) {
log.info("###全局捕获异常###,error:{}", e);
JSONObject result = new JSONObject();
result.put("code", 500);
result.put("msg", "系统错误");
return result;
}
}
- 会员项目提供给订单服务的接口
package com.liwen.service;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.liwen.entity.Member;
import com.liwen.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
public class MemberService implements IMemberService{
@Autowired
private MemberRepository memberRepository;
@LcnTransaction //分布式事务注解
@Transactional
@Override
public String saveMemberMember() {
Member member = new Member();
member.setId(null);
member.setMyName("lwp");
member.setTime(new Date());
Member save = memberRepository.save(member);
int i= 1/0;
System.out.println("会员服务被调用。。。。。");
return "SUCCESS";
}
}
- 下面依次启动注册中心、txlcn-demo-tm、订单项目、会员项目
调用订单服务
订单服务调用会员服务,订单服务是事务发起方,会员服务是事务参与方,会员服务返回500错误,订单服务根据错误码判断,抛出异常,订单服务和会员服务回滚事务。查询数据库订单表和会员表都没有保存数据,与预期一致。