现在有一个下微服务
如果库存服务异常回滚,不会影响订单和账户服务因为他们不知道库存服务出问题了。我们需要解决这个问题,因此有了分布式事务。
在分布式系统下,一个业务跨越了多个服务或数据源,每个服务都是一个分支事务,要保证所有的分支事务最终状态一致,这样的事务就是分布式事务。
分布式系统有三个指标
此外,分布式系统无法同时满足这三个指标。
是对CAP的一中解决思路,包含三个思想:
解决分布式事务,必须让各个子系统能够感知到彼此的事务状态,才能保证状态一致,因此需要一个事务协调这来协调每一个事务的参与者(子系统事务)
Seata官网
官方文档
依据上书模型,Seata提供了四种不同的分布式事务解决方案:
Seata-安装地址
进入config中,将application.example.yml自己需要的部分复制粘贴并填写即可。
笔者此处用的是nacos
server:
port: 7091
spring:
application:
name: seata-server
logging:
config: classpath:logback-spring.xml
file:
path: ${user.home}/logs/seata
extend:
logstash-appender:
destination: 127.0.0.1:4560
kafka-appender:
bootstrap-servers: 127.0.0.1:9092
topic: logback_to_logstash
console:
user:
username: seata
password: seata
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: "nacos"
nacos:
server-addr: 127.0.0.1:8848
namespace:
group: "SEATA_GROUP"
username: "nacos"
password: "nacos"
data-id: seataServer.properties
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: "nacos"
nacos:
application: "seata-server"
server-addr: 127.0.0.1:8848
group: "DEFAULT_GROUP"
namespace: ""
cluster: "CQ"
username: "nacos"
password: "nacos"
store:
# support: file 、 db 、 redis
mode: file
# server:
# service-port: 8091 #If not configured, the default is '${server.port} + 1000'
security:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login
根据上面文件中自己Config位置的dataID 和 group在nacos中创建对应的配置。
# 数据存储方式
# 数据存储方式
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
#mysql8 store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=123456
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxwait=5000
# 事务、日志等配置
server.recovery.committingRetryPeriod=1000
server.recovery.asynCommittingRetryPeriod=1000
server.recovery.rollbackingRetryPeriod=1000
server.recovery.timeoutRetryPeriod=1000
server.maxCommitRetryTimeout=-1
server.maxRollbackRetryTimeout=-1
server.rollbackRetryTimeoutUnlockEnable=false
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
# 客户端与服务端传输方式
transport.serialization=seata
transport.compressor=none
#关闭metrics功能,提高性能
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
在数据库中(上面配置的自己的数据库)导入seata自带的表结构,作为全局事务局部事务的存储位置
随后在bin目录运行程序。
建立如图数据库格式
account_tbl
order_tbl
我们接下来在主类导入如下内容,之后就不用再在子类中配置了
<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">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.5version>
parent>
<groupId>org.examplegroupId>
<artifactId>seata-demoartifactId>
<packaging>pompackaging>
<version>1.0-SNAPSHOTversion>
<modules>
<module>account-servicemodule>
<module>order-servicemodule>
<module>storage-servicemodule>
modules>
<properties>
<maven.compiler.source>11maven.compiler.source>
<maven.compiler.target>11maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<spring-cloud.version>2021.0.3spring-cloud.version>
<spring-cloud-alibaba.version>2.2.5.RELEASEspring-cloud-alibaba.version>
<mybatis.version>3.5.2mybatis.version>
<mybatis.generator.version>3.5.3mybatis.generator.version>
<swagger.version>1.6.6swagger.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring-cloud-alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-bootstrapartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-loadbalancerartifactId>
dependency>
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>${mybatis.version}version>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>${mybatis.generator.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-freemarkerartifactId>
dependency>
<dependency>
<groupId>io.swaggergroupId>
<artifactId>swagger-annotationsartifactId>
<version>${swagger.version}version>
dependency>
dependencies>
project>
解析数据库并生成相应代码
package com.storage;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Collections;
public class generator {
public static void main(String[] args){
FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=UTF-8&&useSSL=false", "root", "123456")
.globalConfig(builder -> {
builder.author("yjx23332") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D:\\tool\\seata-demo\\storage-service\\src\\main\\java"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.storage") // 设置父包名
.moduleName("") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, "D:\\tool\\seata-demo\\storage-service\\src\\main\\resources")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("storage_tbl") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
同理全部解析完成后,继续如下操作。
完成每一个子项的配置
server:
port: 8082
spring:
application:
name: order-service
cloud:
nacos:
server-addr: 127.0.0.1:8848
discovery:
cluster-name: CQ
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&characterEncoding=UTF-8&&useSSL=false
feign:
httpclient:
max-connections-per-route: 50
connection-timeout: 2000
并且完成主运行类的创建
package com.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.order.mapper")
@EnableFeignClients
@EnableTransactionManagement
public class MainApplication {
public static void main(String[] args){
SpringApplication.run(MainApplication.class,args);
}
}
Order服务
package com.order.controller;
import com.order.entity.OrderTbl;
import com.order.service.IOrderTblService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
* 前端控制器
*
*
* @author yjx23332
* @since 2022-08-24
*/
@RestController
@RequestMapping("/orderTbl")
public class OrderTblController {
@Autowired
private IOrderTblService iOrderTblService;
@PostMapping
public ResponseEntity<Integer> createOrder(OrderTbl order){
int orderId = iOrderTblService.create(order);
return ResponseEntity.status(HttpStatus.CREATED).body(orderId);
}
}
package com.order.service.impl;
import com.order.entity.OrderTbl;
import com.order.mapper.OrderTblMapper;
import com.order.service.AccountClient;
import com.order.service.IOrderTblService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.order.service.StorageClient;
import feign.FeignException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
*
* 服务实现类
*
*
* @author yjx23332
* @since 2022-08-24
*/
@Service
@Slf4j
public class OrderTblServiceImpl extends ServiceImpl<OrderTblMapper, OrderTbl> implements IOrderTblService {
@Autowired
AccountClient accountClient;
@Autowired
StorageClient storageClient;
@Override
@Transactional
public int create(@RequestBody OrderTbl order) {
getBaseMapper().insert(order);
try {
accountClient.deduct(order.getUserId(),order.getMoney());
storageClient.deduct(order.getCommodityCode(),order.getCount());
}catch (FeignException e){
log.error("下单失败,原因:{}",e.contentUTF8());
throw new RuntimeException(e.contentUTF8(),e);
}
return order.getId();
}
}
package com.order.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@FeignClient("account-service")
@Service
public interface AccountClient {
@PutMapping("/accountTbl/{userId}/{money}")
void deduct(@PathVariable("userId")Integer userId, @PathVariable("money")Integer money);
}
package com.order.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
@FeignClient("storage-service")
@Service
public interface StorageClient {
@PutMapping("/storageTbl/{code}/{count}")
void deduct(@PathVariable("code")String code,@PathVariable("count") Integer count);
}
Storage
package com.storage.controller;
import com.storage.service.IStorageTblService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
* 前端控制器
*
*
* @author yjx23332
* @since 2022-08-24
*/
@RestController
@RequestMapping("/storageTbl")
public class StorageTblController {
@Autowired
IStorageTblService iStorageTblService;
@PutMapping("/{code}/{count}")
public ResponseEntity<Void> deduct(@PathVariable("code") String code, @PathVariable("count") Integer count){
iStorageTblService.deduct(code,count);
return ResponseEntity.noContent().build();
}
}
package com.storage.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.storage.entity.StorageTbl;
import com.storage.mapper.StorageTblMapper;
import com.storage.service.IStorageTblService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
*
* 服务实现类
*
*
* @author yjx23332
* @since 2022-08-24
*/
@Service
public class StorageTblServiceImpl extends ServiceImpl<StorageTblMapper, StorageTbl> implements IStorageTblService {
@Override
@Transactional
public void deduct(String code, Integer count) {
StorageTbl storageTbl = getBaseMapper().selectOne(new LambdaQueryWrapper<StorageTbl>()
.eq(StorageTbl::getCommodityCode,code));
storageTbl.setCount(storageTbl.getCount() - count);
if(storageTbl.getCount() < 0){
throw new RuntimeException("库存不足!");
}
getBaseMapper().updateById(storageTbl);
}
}
Account
package com.account.controller;
import com.account.service.IAccountTblService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
* 前端控制器
*
*
* @author yjx23332
* @since 2022-08-24
*/
@RestController
@RequestMapping("/accountTbl")
public class AccountTblController {
@Autowired
private IAccountTblService iAccountTblService;
@PutMapping("/{userId}/{money}")
public ResponseEntity<Void> debuct(@PathVariable("userId") Integer userId, @PathVariable("money") Integer money){
iAccountTblService.deduct(userId,money);
return ResponseEntity.noContent().build();
}
}
package com.account.service.impl;
import com.account.entity.AccountTbl;
import com.account.mapper.AccountTblMapper;
import com.account.service.IAccountTblService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
*
* 服务实现类
*
*
* @author yjx23332
* @since 2022-08-24
*/
@Service
public class AccountTblServiceImpl extends ServiceImpl<AccountTblMapper, AccountTbl> implements IAccountTblService {
@Override
@Transactional
public void deduct(Integer userId, Integer money) {
AccountTbl accountTbl = getBaseMapper().selectOne(new LambdaQueryWrapper<AccountTbl>()
.eq(AccountTbl::getUserId,userId));
accountTbl.setMoney(accountTbl.getMoney() - money);
if(accountTbl.getMoney() < 0){
throw new RuntimeException("余额不足!");
}
getBaseMapper().updateById(accountTbl);
}
}
接下来我们放一些数据进去,然后测试一下能不能用。
引入Seata依赖
<properties>
...
<seata.version>1.4.2seata.version>
<alibaba.druid.version>1.2.9alibaba.druid.version>
properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-seataartifactId>
<exclusions>
<exclusion>
<groupId>seata-srping-boot-startergroupId>
<artifactId>io.seataartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.seatagroupId>
<artifactId>seata-spring-boot-starterartifactId>
<version>${seata.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>${alibaba.druid.version}version>
dependency>
dependencies>
为每一个服务配置seata,内容要和之前seata中的信息一致
seata:
registry:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace: ""
group: DEFAULT_GROUP
application: seata-server
username: nacos
password: nacos
#事务组
tx-service-group: seata-demo
service:
# 事务组与cluster的映射关系
vgroup-mapping:
seata-demo: CQ
重新运行,如果启动失败请考虑Seata与OpenFeign兼容性问题,
可以试着注释掉
XA规范是X/Open组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,描述了全局的TM与局部RM之间的接口,几乎所有主流数据库都对XA规范提供了支持、
该标准基于数据库的能力去完成。
阶段一:准备阶段
RM这里是数据库,由TC通知各个数据库执行,执行完毕不提交,而是告知TC
如果都成功了,TC通知提交
如果有失败
针对提交的数据库
开启模式,每一个RM都需要添加
seata:
data-source-proxy-mode: XA
标记全局事务入口
@Override
@GlobalTransactional
public int create(OrderTbl order) {
...
}
AT模式同样是分阶段提交事务模型,不过弥补了XA模型中资源锁定周期过长的缺陷
使用方式同上,直接把XA改为AT。
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
提前把锁释放了,期间其他事务可以写数据,但是出错后回滚,将导致其他事务丢失更新。
全局锁的死锁问题
因为是执行完前先保存快照,执行sql后才获取全局锁,也就是说数据库锁会先被获取。
当一个事务获取了表的全局锁但他释放了数据库锁,随后另一个事务获取了该表的锁执行sql后,想要获取全局锁,却发现无法获取,进入等待。
有全局锁的事务需要回滚,但是当进入该表时,无法获取数据库锁,也进入了等待。
解决方法:
全局锁重试默认30次,每次等待10ms,超时则回滚释放数据库锁
由此可以看出,全局锁粒度更小,因此性能比XA模式好一些。但是,隔离不够彻底。
但是如果发生了会怎么样?即,一个TC管理的一个非TC管理,对同一个表进行操作。
Seata保存了两个快照,一个是更新前快照,一个更新后快照。恢复时他会对比是否期间是否有操作。如果有问题,此时就可以请求人工操作。
以扣费为例,金额会分为冻结金额与可用金额
最终一致。
优点
缺点
数学中幂等就是多次运算结果一致
这里就是,同一个操作不管你操作多少次结果是相同的
我们可以AT、XT、TCC混着用
空回滚:当一个分支的try阶段阻塞,导致一些分支还没有执行try,结果被要求执行cancel回滚,但这个cancel是空的,因此它们不能回滚。
业务悬挂:当阻塞的分支畅通了后继续执行,但已经事务已经回滚结束了,那么它就永远不可能confirm或者cancel。应当阻止空回滚后的try操作。
我们需要实现如下表
state
package com.account.service;
import io.seata.rm.tcc.api.BusinessActionContext;
import io.seata.rm.tcc.api.BusinessActionContextParameter;
import io.seata.rm.tcc.api.LocalTCC;
import io.seata.rm.tcc.api.TwoPhaseBusinessAction;
@LocalTCC
public interface TCCService {
@TwoPhaseBusinessAction(name = "prepare",commitMethod = "confirm",rollbackMethod = "cancel")
void prepare(@BusinessActionContextParameter(paramName = "userId")Integer userId,
@BusinessActionContextParameter(paramName = "money")Integer money);
boolean confirm(BusinessActionContext context);
boolean cancel(BusinessActionContext context);
}
package com.account.service.impl;
import com.account.entity.AccountFreezeTbl;
import com.account.service.IAccountFreezeTblService;
import com.account.service.IAccountTblService;
import com.account.service.TCCService;
import io.seata.core.context.RootContext;
import io.seata.rm.tcc.api.BusinessActionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TCCServiceImpl implements TCCService {
@Autowired
private IAccountTblService iAccountTblService;
@Autowired
private IAccountFreezeTblService iAccountFreezeTblService;
@Override
@Transactional
public void prepare(Integer userId, Integer money) {
//获取上下文中的XID
String xid = RootContext.getXID();
AccountFreezeTbl freezeTbl = iAccountFreezeTblService.getById(xid);
//业务悬空判断
if(freezeTbl != null){
return ;
}
//扣钱
iAccountTblService.deduct(userId,money);
freezeTbl = new AccountFreezeTbl();
freezeTbl.setUserId(userId);
freezeTbl.setFreezeMoney(money);
freezeTbl.setState(0);
freezeTbl.setXid(xid);
//存储冻结金额
iAccountFreezeTblService.save(freezeTbl);
}
@Override
public boolean confirm(BusinessActionContext context) {
String xid = RootContext.getXID();
int count = iAccountFreezeTblService.getBaseMapper().deleteById(xid);
return count == 1;
}
@Override
public boolean cancel(BusinessActionContext context) {
String xid = context.getXid();
AccountFreezeTbl accountFreezeTbl = iAccountFreezeTblService.getById(xid);
//空回滚判断
if(accountFreezeTbl == null){
Integer userId = Integer.parseInt(context.getActionContext("userId").toString());
accountFreezeTbl = new AccountFreezeTbl();
accountFreezeTbl.setUserId(userId);
accountFreezeTbl.setFreezeMoney(0);
accountFreezeTbl.setState(2);
accountFreezeTbl.setXid(xid);
int count = iAccountFreezeTblService.getBaseMapper().insert(accountFreezeTbl);
return count == 1;
}
//幂等判断
if(accountFreezeTbl.getState() == 2){
return true;
}
// 恢复余额
iAccountTblService.refund(accountFreezeTbl.getUserId(),accountFreezeTbl.getFreezeMoney());
accountFreezeTbl.setFreezeMoney(0);
accountFreezeTbl.setState(2);
int count = iAccountFreezeTblService.getBaseMapper().updateById(accountFreezeTbl);
return count == 1;
}
}
package com.account.service.impl;
import com.account.entity.AccountTbl;
import com.account.mapper.AccountTblMapper;
import com.account.service.IAccountTblService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
*
* 服务实现类
*
*
* @author yjx23332
* @since 2022-08-24
*/
@Service
public class AccountTblServiceImpl extends ServiceImpl<AccountTblMapper, AccountTbl> implements IAccountTblService {
@Override
@Transactional(rollbackFor = RuntimeException.class)
public void deduct(Integer userId, Integer money) {
AccountTbl accountTbl = getBaseMapper().selectOne(new LambdaQueryWrapper<AccountTbl>()
.eq(AccountTbl::getUserId,userId));
accountTbl.setMoney(accountTbl.getMoney() - money);
if(accountTbl.getMoney() < 0){
throw new RuntimeException("余额不足!");
}
getBaseMapper().updateById(accountTbl);
}
@Override
@Transactional
public void refund(Integer userId, Integer freezeMoney) {
AccountTbl accountTbl = getBaseMapper().selectOne(new LambdaQueryWrapper<AccountTbl>()
.eq(AccountTbl::getUserId,userId));
accountTbl.setMoney(accountTbl.getMoney() + freezeMoney);
getBaseMapper().updateById(accountTbl);
}
}
package com.account.controller;
import com.account.service.IAccountTblService;
import com.account.service.TCCService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
* 前端控制器
*
*
* @author yjx23332
* @since 2022-08-24
*/
@RestController
@RequestMapping("/accountTbl")
@Slf4j
public class AccountTblController {
@Autowired
private TCCService tccService;
@PutMapping("/{userId}/{money}")
public ResponseEntity<Void> debuct(@PathVariable("userId") Integer userId, @PathVariable("money") Integer money){
tccService.prepare(userId,money);
return ResponseEntity.noContent().build();
}
}
没有隔离性,最终一致。
优势
缺点
[1]黑马程序员Java微服务
[2]Seata官网