简介
LCN分布式事务框架其本身并不创建事务,而是基于对本地事务的协调从而达到事务一致性的效果。
LCN5.0.2有3种模式,分别是LCN模式,TCC模式,TXC模式
LCN模式:
LCN模式是通过代理Connection的方式实现对本地事务的操作,然后在由TxManager统一协调控制事务。当本地事务提交回滚或者关闭连接时将会执行假操作,该代理的连接将由LCN连接池管理。
该模式的特点:
- 该模式对代码的嵌入性为低。
- 该模式仅限于本地存在连接对象且可通过连接对象控制事务的模块。
- 该模式下的事务提交与回滚是由本地事务方控制,对于数据一致性上有较高的保障。
- 该模式缺陷在于代理的连接需要随事务发起方一共释放连接,增加了连接占用的时间。
TCC模式:
TCC事务机制相对于传统事务机制(X/Open XA Two-Phase-Commit),其特征在于它不依赖资源管理器(RM)对XA的支持,而是通过对(由业务系统提供的)业务逻辑的调度来实现分布式事务。主要由三步操作,Try: 尝试执行业务、 Confirm:确认执行业务、 Cancel: 取消执行业务。
该模式的特点:
- 该模式对代码的嵌入性高,要求每个业务需要写三种步骤的操作。
- 该模式对有无本地事务控制都可以支持使用面广。
- 数据一致性控制几乎完全由开发者控制,对业务开发难度要求高。
TXC模式:
TXC模式命名来源于淘宝,实现原理是在执行SQL之前,先查询SQL的影响数据,然后保存执行的SQL快走信息和创建锁。当需要回滚的时候就采用这些记录数据回滚数据库,目前锁实现依赖redis分布式锁控制。
该模式的特点:
- 该模式同样对代码的嵌入性低。
- 该模式仅限于对支持SQL方式的模块支持。
- 该模式由于每次执行SQL之前需要先查询影响数据,因此相比LCN模式消耗资源与时间要多。
- 该模式不会占用数据库的连接资源。
实现原理
1.LCN客户端(发起方和参与方都必须要注册到事务协调者中), 建立一个长连接。(长连接 减宽带 但是消耗内存 连接后不断开)
2.订单服务(发起方)调用库存服务接口(参与方)之前,会向TxManager事务协调者创建一个事务的分组id。
3.订单服务(发起方)调用库存服务接口(参与方)的时候,会在请求头(底层是HTTP协议,LCN底层重写了Feigin客户端)中存放该事务的分组id,给库存服务。
(TxClient的代理连接池实现了Javax.sql.DataSource接口,并重写了close方法,事务模块在提交关闭后,TxClient连接池将执行‘假关闭’操作,等待TxManager协调完成事务后再关闭连接)
4.如果库存服务获取到请求头中有对应的事务分组id,库存服务业务逻辑代码执行完毕的,会采用假关闭,不会提交该事务。
(微服务里面的服务和服务之间 协调在一起 通过注册中心。
分布式是事务中, 解决事务和事务之间的关系 靠的是类似的平台 TxManager 去协调事务)
5.参与方在什么时候提交事务,不能一直不提交。
肯定在发起方 执行成功下。
订单服务(发起方)调用库存服务接口(参与方)之后,如果订单服务(发起方)执行没有问题的下,
订单服务(发起方)使用对应的事务分组id,通知给TxManager事务协调者,让后TxManager事务协调者在根据该事务分组id,通知给所有的参与方提交事务。
PS:长连接 好处减少宽带传输 弊端比较占内存。
使用LCN很简单 加个注解就OK了
入门demo
废话不多说,开整
SQL建表语句(在txlcn-tm模块的resource目录下)
/*
Navicat Premium Data Transfer
Source Server : local
Source Server Type : MySQL
Source Server Version : 100309
Source Host : localhost:3306
Source Schema : tx-manager
Target Server Type : MySQL
Target Server Version : 100309
File Encoding : 65001
Date: 29/12/2018 18:35:59
*/
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;
下载源码并编译
源码下载地址:https://github.com/codingapi/tx-lcn
工程目录
修改txlcn-tm的配置文件application.properties
spring.application.name=TransactionManager
server.port=7970
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=123456
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=update
# TM后台登陆密码,默认值为codingapi
tx-lcn.manager.admin-key=123456
注意:个人修改了数据库的名称,和用户名密码,TM的后台登录密码,其他根据自己的实际情况修改
txlcn-tm的pom修改,放开配置
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<archive>
<manifest>
<mainClass>com.codingapi.txlcn.tm.TMApplication</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
<manifestEntries>
<Class-Path>./</Class-Path>
</manifestEntries>
</archive>
<excludes>
<exclude>config/**</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptors>
<descriptor>src/main/build/package.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
注掉配置
<!--docker 构建-->
<!--<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<imageName>codingapi/txlcn-tm</imageName>
<dockerDirectory>${project.basedir}/src/main/docker</dockerDirectory>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
<imageTags>
<imageTag>5.0.2</imageTag>
</imageTags>
<serverId>docker-hub</serverId>
<registryUrl>https://index.docker.io/v1/</registryUrl>
</configuration>
</plugin>
-->
最后使用maven编译,去掉test,否则编译很慢
启动txlcn-tm模块
启动后打开后台地址http://localhost:7970,初始密码是codingapi,我这里改成了123456
项目配置
本文以下订单扣库存举例。分为2个服务,并注册到eureka上
sql文件
test1库goods表,test2库order_form表
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 50712
Source Host : localhost:3306
Source Schema : test1
Target Server Type : MySQL
Target Server Version : 50712
File Encoding : 65001
Date: 09/01/2020 14:52:01
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for goods
-- ----------------------------
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`goods_id` int(10) NULL DEFAULT NULL,
`stock` int(10) NULL DEFAULT NULL,
`group_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of goods
-- ----------------------------
INSERT INTO `goods` VALUES (1, 31, 100, '');
SET FOREIGN_KEY_CHECKS = 1;
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 50712
Source Host : localhost:3306
Source Schema : test2
Target Server Type : MySQL
Target Server Version : 50712
File Encoding : 65001
Date: 09/01/2020 13:53:07
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for order_form
-- ----------------------------
DROP TABLE IF EXISTS `order_form`;
CREATE TABLE `order_form` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`goods_id` int(20) NULL DEFAULT NULL,
`goods_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
`user_id` int(10) NULL DEFAULT NULL,
`create_time` timestamp(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 46 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
eureka
eureka代码不上了,很简单,上下配置文件
application.yml
server:
port: 6868
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://127.0.0.1:${server.port}/eureka/
server:
enable-self-preservation: false
spring:
application:
name: eureka
order服务
controller
package com.wangtao.controller;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.wangtao.dao.OrderDao;
import com.wangtao.enity.Order;
import com.wangtao.feign.OrderFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
public class OrderController {
@Autowired
private OrderDao orderDao;
@Autowired
private OrderFeign orderFeign;
@Transactional(rollbackFor = Exception.class)
@LcnTransaction
@RequestMapping("/createOrder")
public String createOrder (){
int i = 0;
try {
Order order = new Order();
order.setGoodsName("图片");
order.setGoodsId(31);
order.setUserId(1);
order.setCreateTime(new Date());
orderDao.save(order);
i = orderFeign.updateGoodsStock();
int j=1/0;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("下单失败",e);
}
return "下单成功";
}
}
dao
package com.wangtao.dao;
import com.wangtao.enity.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
@Repository
public interface OrderDao extends JpaRepository<Order, Integer>, JpaSpecificationExecutor<Order> {
}
enity
package com.wangtao.enity;
import lombok.Data;
import javax.persistence.*;
import java.util.Date;
@Data
@Entity
@Table(name = "order_form")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增类型的主键
private int id;
@Column(name = "goods_id")
private int goodsId;
@Column(name = "goods_name")
private String goodsName;
@Column(name="user_id")
private int userId;
@Column(name = "create_time")
private Date createTime;
}
feign
package com.wangtao.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@FeignClient(name = "goods")
@Component
public interface OrderFeign {
@RequestMapping(value = "/goods/updateGoodsStock", method = RequestMethod.POST)
int updateGoodsStock();
}
启动类
package com.wangtao;
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;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
@EnableDistributedTransaction
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
配置文件
server:
port: 9001
spring:
application:
name: order #指定服务名
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test2?characterEncoding=UTF8&useSSL=false
username: root
password: 123456
jpa:
database: MySQL
show-sql: true
cloud:
refresh:
refreshable: none
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka/
instance:
prefer-ip-address: true
tx-lcn:
client:
manager-address: 127.0.0.1:8070
logger:
enabled: true
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>parent</artifactId>
<groupId>com.wangtao</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order</artifactId>
<dependencies>
<!-- 添加Eureka的依赖 -->
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.4</version>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-config</artifactId>-->
<!--</dependency>-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.4</version>
<classifier>jdk15</classifier>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</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>
<!--分布式事务依赖 结束-->
</dependencies>
</project>
goods服务
controller
package com.wangtao.controller;
import com.codingapi.txlcn.tc.annotation.LcnTransaction;
import com.codingapi.txlcn.tc.annotation.TxcTransaction;
import com.wangtao.dao.GoodsDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/goods")
public class GoodsController {
@Autowired
private GoodsDao goodsDao;
@RequestMapping("/updateGoodsStock")
@Transactional(rollbackFor = Exception.class)
@LcnTransaction //分布式事务注解
public int updateGoodsStock(){
int i = goodsDao.updateGoodsStock();
return 1;
}
@RequestMapping("/updateGoodsStockTcc")
@Transactional(rollbackFor = Exception.class)
//@TccTransaction
@TxcTransaction
public int updateGoodsStockTcc(){
int i = goodsDao.updateGoodsStock();
return 1;
}
}
dao
package com.wangtao.dao;
import com.wangtao.entity.Goods;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
@Repository
public interface GoodsDao extends JpaRepository<Goods, Integer>, JpaSpecificationExecutor<Goods> {
@Modifying
@Query(value = "update goods set stock=stock-1 where goods_id=31 and stock>0",nativeQuery = true)
public int updateGoodsStock();
}
entity
package com.wangtao.entity;
import lombok.Data;
import javax.persistence.*;
@Entity
@Data
@Table(name = "goods")
public class Goods {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // 自增类型的主键
private int id;
@Column(name = "goods_id")
private int goodsId;
private int stock;
}
启动类
package com.wangtao;
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 GoodsApplication {
public static void main(String[] args) {
SpringApplication.run(GoodsApplication.class, args);
}
}
配置文件
server:
port: 9002
spring:
application:
name: goods #指定服务名
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test1?characterEncoding=UTF8&useSSL=false
username: root
password: 123456
jpa:
database: MySQL
show-sql: true
cloud:
refresh:
refreshable: none
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:6868/eureka/
instance:
prefer-ip-address: true
tx-lcn:
client:
manager-address: 127.0.0.1:8070
logger:
enabled: true
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>parent</artifactId>
<groupId>com.wangtao</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>goods</artifactId>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- 添加Eureka的依赖 -->
<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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--<dependency>-->
<!--<groupId>org.springframework.cloud</groupId>-->
<!--<artifactId>spring-cloud-starter-config</artifactId>-->
<!--</dependency>-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.0.2</version>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<!-- <version>2.0.4.RELEASE</version>-->
<!-- 启用 -->
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</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>
<!--分布式事务依赖 结束-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
分别启动两个服务并注册到eureka上,并访问http://localhost:9001/createOrder
发现基于lcn模式的数据库表中并没有增加数据,说明分布式事务起作用了
下面演示tcc模式
eureka配置不变
order服务
OrderController增加
@Transactional(rollbackFor = Exception.class)
@RequestMapping("/createTccOrder")
@LcnTransaction
public String createTccOrder (@RequestParam int num){
int i = 0;
try {
Order order = new Order();
order.setGoodsName("图片");
order.setGoodsId(31);
order.setUserId(1);
order.setCreateTime(new Date());
orderDao.save(order);
i = orderFeign.updateGoodsStockTcc();
int j=1/num;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("下单失败",e);
}
return "下单成功";
}
OrderFeign增加
@RequestMapping(value = "/goods/updateGoodsStockTcc", method = RequestMethod.POST)
int updateGoodsStockTcc();
goods服务
GoodsController增加
@RequestMapping("/updateGoodsStockTcc")
@Transactional(rollbackFor = Exception.class)
@TccTransaction(propagation = DTXPropagation.SUPPORTS,cancelMethod = "cancelUpdate",confirmMethod = "confirmUpdate" ,executeClass = GoodsController.class)
public int updateGoodsStockTcc(){
int i = goodsDao.updateGoodsStockTcc(TracingContext.tracing().groupId());
return 1;
}
public void confirmUpdate() {
log.info("成功");
}
@Transactional(rollbackFor = Exception.class)
public void cancelUpdate() {
log.error("失败,正在回退");
goodsDao.cancelUpdate(TracingContext.tracing().groupId());
log.error("回退成功");
}
GoodsDao增加
@Modifying
@Query(value = "update goods set stock=stock-1,group_id=:groupId where goods_id=31 and stock>0",nativeQuery = true)
public int updateGoodsStockTcc(@Param("groupId") String groupId);
@Modifying
@Query(value = "update goods set stock=stock+1 where group_id=:groupId",nativeQuery = true)
void cancelUpdate(@Param("groupId") String groupId);
Goods增加
@Column(name = "group_id")
private String groupId;
启动2个服务注册到eureka上
输入网址正常情况下
order_form表
goods表
恢复原始数据,测试报错情况下
order_form表
goods表
ok,测试完毕,另外一种txc模式大家自行探索吧,溜了。。。