目录
1:什么是seata
Seata是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
官网:http:seata.io/zh-cn/
2:seata安装
下载
https://seata.io/zh-cn/blog/download.html
Seata-Server安装
解压seata-server.zip,启动seata-server.sh
3:seata的三种存储模式
因为seata需要对全局事务和分支事务进行管理存储,所以有三种存储模式
分别是file模式,DB模式,redis模式
3.1:file模式
file会将事务数据存储到本地文件夹
1:修改conf下的application.yml
2:启动seata-server.sh
到bin目录下看到本地存储的sessionStore文件夹,证明了本地存储
3:访问 http://localhost:7091/#/transaction/list
账户密码都是seata 进入seata控制台
3.2:DB模式
DB会将事务数据存储到自己设置的数据库
1:修改conf下的application.yml
seata:
#配置中心(在db模式下使用nacos集群或者单机)
config:
# support: nacos, consul, apollo, zk, etcd3
type: nacos
nacos:
server-addr: 127.0.0.1:8851,127.0.0.1:8853,127.0.0.1:8855
namespace:
group: SEATA_GROUP
username: nacos
password: nacos
context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key:
#secret-key:
data-id: seataServer.properties
#注册中心(在db模式下使用nacos集群或者单机)
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
preferred-networks: 30.240.*
nacos:
application: seata-server
server-addr: 127.0.0.1:8851,127.0.0.1:8853,127.0.0.1:8855
group: SEATA_GROUP
namespace:
cluster: default
username: nacos
password: nacos
context-path:
##if use MSE Nacos with auth, mutex with username/password attribute
#access-key:
#secret-key:
#存储方式(去application.example.yml文件中复制过来)
store:
# support: file(适用于单机测试) 、 db(数据库保存事务信息) 、 redis(redis保存事务信息,性能略强于db)
mode: db
session:
mode: db
lock:
mode: db
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/Seata?useUnicode=true&characterEncoding=utf-8&useSSL=false
user: root
password: 123456
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 1000
max-wait: 5000
2:然后找到建表语句在mysql的Seata库中建表
在本机的数据库Seata下边执行建表sql会得到4张表
3:然后配置nacos的配置文件
配置文件的group和data-id如上边的yml配置,配置文件的内容位置是
seata/script/config-center/config.txt ,需要对文件进行数据源的配置,然后在nacos中创建配置文件
配置文件内容型如下:
#For details about configuration items, see https://seata.io/zh-cn/docs/user/configurations.html
#Transport configuration, for client and server
transport.type=TCP
transport.server=NIO
transport.heartbeat=true
transport.enableTmClientBatchSendRequest=false
transport.enableRmClientBatchSendRequest=true
transport.enableTcServerBatchSendResponse=false
transport.rpcRmRequestTimeout=30000
transport.rpcTmRequestTimeout=30000
transport.rpcTcRequestTimeout=30000
transport.threadFactory.bossThreadPrefix=NettyBoss
transport.threadFactory.workerThreadPrefix=NettyServerNIOWorker
transport.threadFactory.serverExecutorThreadPrefix=NettyServerBizHandler
transport.threadFactory.shareBossWorker=false
transport.threadFactory.clientSelectorThreadPrefix=NettyClientSelector
transport.threadFactory.clientSelectorThreadSize=1
transport.threadFactory.clientWorkerThreadPrefix=NettyClientWorkerThread
transport.threadFactory.bossThreadSize=1
transport.threadFactory.workerThreadSize=default
transport.shutdown.wait=3
transport.serialization=seata
transport.compressor=none
#Transaction routing rules configuration, only for the client
service.vgroupMapping.default_tx_group=default
#If you use a registry, you can ignore it
service.default.grouplist=127.0.0.1:8091
service.enableDegrade=false
service.disableGlobalTransaction=false
#Transaction rule configuration, only for the client
client.rm.asyncCommitBufferLimit=10000
client.rm.lock.retryInterval=10
client.rm.lock.retryTimes=30
client.rm.lock.retryPolicyBranchRollbackOnConflict=true
client.rm.reportRetryCount=5
client.rm.tableMetaCheckEnable=true
client.rm.tableMetaCheckerInterval=60000
client.rm.sqlParserType=druid
client.rm.reportSuccessEnable=false
client.rm.sagaBranchRegisterEnable=false
client.rm.sagaJsonParser=fastjson
client.rm.tccActionInterceptorOrder=-2147482648
client.tm.commitRetryCount=5
client.tm.rollbackRetryCount=5
client.tm.defaultGlobalTransactionTimeout=60000
client.tm.degradeCheck=false
client.tm.degradeCheckAllowTimes=10
client.tm.degradeCheckPeriod=2000
client.tm.interceptorOrder=-2147482648
client.undo.dataValidation=true
client.undo.logSerialization=jackson
client.undo.onlyCareUpdateColumns=true
server.undo.logSaveDays=7
server.undo.logDeletePeriod=86400000
client.undo.logTable=undo_log
client.undo.compress.enable=true
client.undo.compress.type=zip
client.undo.compress.threshold=64k
#For TCC transaction mode
tcc.fence.logTableName=tcc_fence_log
tcc.fence.cleanPeriod=1h
#Log rule configuration, for client and server
log.exceptionRate=100
#Transaction storage configuration, only for the server. The file, db, and redis configuration values are optional.
store.mode=db
store.lock.mode=db
store.session.mode=db
#Used for password encryption
#store.publicKey=
#If `store.mode,store.lock.mode,store.session.mode` are not equal to `file`, you can remove the configuration block.
#store.file.dir=file_store/data
#store.file.maxBranchSessionSize=16384
#store.file.maxGlobalSessionSize=512
#store.file.fileWriteBufferCacheSize=16384
#store.file.flushDiskMode=async
#store.file.sessionReloadReadSize=100
#These configurations are required if the `store mode` is `db`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `db`, you can remove the configuration block.
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://localhost:3306/Seata?useUnicode=true&characterEncoding=utf-8&useSSL=false
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.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
#These configurations are required if the `store mode` is `redis`. If `store.mode,store.lock.mode,store.session.mode` are not equal to `redis`, you can remove the configuration block.
#store.redis.mode=single
#store.redis.single.host=127.0.0.1
#store.redis.single.port=6379
#store.redis.sentinel.masterName=
#store.redis.sentinel.sentinelHosts=
#store.redis.maxConn=10
#store.redis.minConn=1
#store.redis.maxTotal=100
#store.redis.database=0
#store.redis.password=
#store.redis.queryLimit=100
#Transaction rule configuration, only for the server
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.distributedLockExpireTime=10000
server.xaerNotaRetryTimeout=60000
server.session.branchAsyncQueueSize=5000
server.session.enableBranchAsyncRemove=false
server.enableParallelRequestHandle=false
#Metrics configuration, only for the server
metrics.enabled=false
metrics.registryType=compact
metrics.exporterList=prometheus
metrics.exporterPrometheusPort=9898
4:启动seata-server.sh -m db 以数据库存储方式启动
可以在nacos中看到 seata-server的注册服务
5:访问 http://localhost:7091/#/transaction/list
账户密码都是seata 进入seata控制台
3.2:redis模式(没有写)
redis会将事务数据存储到自己设置的redis,跟上边的相似的配置
4:seata实现分布式事务的4种模式
seata的框架结构如下
TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM (Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
seata实现分布式事务的4种模式
1:AT模式(常用)
2:TCC模式(常用)
3:SAGA模式(不常用,链式调用,适用于不同的老系统,有补偿机制,代码复杂)
4:XA模式(不常用)
在下边的内容会详细介绍这几种模式
5:seata的AT模式
详见官网:Seata AT 模式 | Seata
5.1:AT模式模式实现机制
AT模式是两段式提交的演变
一阶段:业务数据和回滚日志(undo log)在同一个本地事务中需要先拿到本地锁和全局锁提交,只会释放本地锁。
二阶段:commit提交是异步化的,非常快速。rollback是根据一阶段的回滚日志来进行回滚。
无论提交还是回滚都会在这个时候释放全局锁。
AT模式的写隔离和读隔离,这是啥意思呢?详见官网的解释和图片实例,由于解释和图片很多,这里只做简单的解释
写隔离:
两个事务修改同一条数据,需要先竞争获取本地锁(也就是行锁),一个事务一阶段提交的时候需要再获取全局锁,竞争的事务等待,等到人家全局锁二阶段提交后,等待的事务才能再次获取到全局锁,提交自己的事务。保证了就像本地事务一样写操作的安全性也就是写隔离。
读隔离:
我们知道mysql的默认隔离级别的RR(可重复读),能够防止脏读,不可重复读,幻读的情况,但是当我们使用分布式事务seata的时候,他能做到RR的隔离级别吗?答案是不能,seata只能做到在Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)。他会读取到别的事务没有提交的数据,发生脏读。
怎样避免发生脏读,或者更高的安全性?如果应用在特定场景下,必需要求全局的读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。
5.2:AT模式的案例实现
我们首先介绍项目情况,9002通过feign调用9003项目,9002新增一条学生表数据,9003新增一条老师表数据,通过seata的AT模式验证分布式事务
0:在每一个数据库下边创建undo表,用于数据回滚
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';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
1:pom文件配置9002和9003项目的pom文件一样
<!--springboot3.0.3适配的springCloud版本是2022.0.0-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>SCA01_Seata_9003</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 1:springCloud版本和spring-cloud-alibababa版本 很重要 版本必须匹配-->
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.0</spring-cloud.version>
<spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 德鲁伊数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<!-- mybatisPlus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!--spring-cloud-starter-alibaba服务注册-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--spring-cloud-starter-alibaba配置管理-->
<!-- <dependency>-->
<!-- <groupId>com.alibaba.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>-->
<!-- </dependency>-->
<!--sentinel依赖的jar-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--seata依赖-->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
2:9002和9003的application.properties也是基本一样
#0:项目信息
server.port=9003
spring.application.name=SCA01-Seata-9003
##1:nacos集群地址 用来注册服务nacos
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8851,127.0.0.1:8853,127.0.0.1:8855
##定义坐标
spring.cloud.nacos.discovery.group=SEATA_GROUP
#端点监控
management.endpoints.web.exposure.include=*
#2:seata分布式事务配置 default_tx_group=default 来源于db模式的seata配置文件
seata.enabled=true
seata.service.vgroup-mapping.default_tx_group=default
seata.service.disable-global-transaction=false
seata.service.grouplist.default=127.0.0.1:8091
#3:注册sentinel监控地址
spring.cloud.sentinel.transport.dashboard=localhost:8080
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
spring.cloud.sentinel.transport.port=8719
#启动就会加载
spring.cloud.sentinel.eager=true
#4:德鲁伊数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/Seata3?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.druid.initial-size=10
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=10
spring.datasource.druid.max-wait=4000
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
3:9002核心代码
//9002的controller代码用于外部请求
@GetMapping(value = "add")
public List<Student> add(Student student){
return studentService.add(student);
}
//9002的全局事务代码
@GlobalTransactional
@Override
public List<Student> add(Student student) {
//9002项目添加学生表信息
studentMapper.insert(student);
//这里模拟报错,9002已经第一阶段提交
//int a = 9002 / 0;
//feign调用9003项目添加老师表信息
Teacher teacher = new Teacher();
teacher.setName("测试老师22");
teacher.setAddress("洛阳");
feignService9003.add(teacher);
//这里模拟报错,9002和9003都已经第一阶段提交,第二阶段需要回滚 debug断点
//可以在数据库的看到学生表和老师表存在了新增的数据,并且undo日志也存在
//这就是seata的第一阶段提交,读未提交(Read Uncommitted) 其他的事务会发生脏读
int a=9002/0;
return studentMapper.selectList(null);
}
//远程调用nacos上的SCA01_Seata_9003服务
@FeignClient(value = "SCA01-Seata-9003")
public interface FeignService9003 {
@GetMapping(value = "selectById")
Teacher selectById(@RequestParam(value = "id") int id);
@GetMapping(value = "selectAll")
List<Teacher> selectByALL();
@GetMapping(value = "deleteById")
int deleteByID(@RequestParam(value = "id") int id);
@PostMapping(value = "add")
boolean add(@RequestBody Teacher teacher);
}
4:9003核心代码
@PostMapping(value = "add")
public boolean add(@RequestBody Teacher teacher){
boolean save = teacherService.save(teacher);
//9003模拟报错验证全局事务
//int a=9003/0;
return save;
}
5:代码测试,断点验证,断点打到 int a=9002/0;
http://localhost:9002/add?name=学生张三22&address=北京1&score=88
断点接着走会报错,因为int a=9002/0 会报异常,数据能回滚吗? 我们再次查询两个表发现数据都不在了,根据undo日志回滚了。这就验证了分布式事务生效了,你也可以在其他的地方验证,都会生效,这里不过多举例。
读隔离验证:这里有个问题,seata修改数据会发生锁竞争,是安全的。但是修改的数据会在数据库被我们查到,会发生读已提交(其他事务读取到本事务没有提交的数据),我们怎么保证安全呢?
读隔离验证:我们可以在可以在9003代码中加入 for update
//9003的controller
@GetMapping(value = "selectAll")
public List<Teacher> selectByALL() throws InterruptedException {
return teacherService.selectList();
}
//9003的service 加入GlobalTransactional
@GlobalTransactional
@Override
public List<Teacher> selectList() {
return teacherMapper.selectList();
}
//9003的mapper 假如for update
<select id="selectList" resultType="com.example.SCA01_Seata_9003.entity.Teacher">
select * from Teacher for update
</select>
读隔离:再次验证,读已提交(Read committed)
5.3:AT模式的优缺点
两段式提交的变种,第一阶段就会提交本地事务,数据能查询到,第二阶段异步提交、回滚
写隔离安全
读隔离在msyql数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)。必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。
6:seata的TCC模式
详见官网
6.1:TCC模式模式实现机制
TCC也是两阶段提交的演变,整体是 两阶段提交 的模型。
全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:
一阶段: prepare 行为
二阶段: commit 或 rollback 行为
TCC和AT模式对比
AT 模式基于 支持本地 ACID 事务 的 关系型数据库:(不需要额外代码,只需要在配置和注解)
一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。
二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。
相应的,TCC 模式,不依赖于底层数据资源的事务支持:(也就需要是自己写代码)
一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
二阶段 commit 行为:调用 自定义 的 commit 逻辑。
二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
6.2:TCC模式的案例实现
跟前边相似,9022通过feign调用9023
0:删除数据库下的undo表
1:pom文件配置9002和9003项目的pom文件一样
2:9022和9023的application.properties也是上边一样
3:9022核心代码
//9022的controller代码方法,用于外部请求
@GetMapping(value = "add")
@GlobalTransactional //TCC模式注解必须加到外部
public List<Student> add(Student student)
{
System.out.println("进入TCC业务测试");
return studentService.addStudent(student);
}
//service接口代码
@LocalTCC
public interface StudentService extends IService<Student> {
//两阶段的业务操作name就是一个动态的bean
@TwoPhaseBusinessAction(name = "addStudent",
commitMethod = "addStudent_commit",
rollbackMethod = "addStudent_rollback")
List<Student> addStudent(@BusinessActionContextParameter(value ="student") Student student);
//TCC的CommitMethod 手动提交 BusinessActionContext是map key就是student
Boolean addStudent_commit(BusinessActionContext businessActionContext);
//TCC的RollbackMethod 手动回滚
Boolean addStudent_rollback(BusinessActionContext businessActionContext) throws JsonProcessingException;
}
//service接口实现类代码
@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student>
implements StudentService {
@Autowired
StudentMapper studentMapper;
@Autowired
FeignService9023 feignService9023;
//9022的TCC全局事务代码
//@GlobalTransactional 这里不加注解 注解加到外边
@Override
public List<Student> addStudent(Student student) {
//9002项目添加学生表信息
studentMapper.insert(student);
System.out.println("插入学生表信息:" + student);
//这里模拟TCC报错,9022已经第一阶段提交
//int a = 9022 / 0;
//feign调用9003项目添加老师表信息
Teacher teacher = new Teacher();
teacher.setName("测试老师22");
teacher.setAddress("洛阳");
feignService9023.add(teacher);
//这里模拟报错,9022和9023都已经第一阶段提交,第二阶段需要回滚 debug断点
//可以在数据库的看到学生表和老师表存在了新增的数据,并且undo日志也存在
//这就是seata的第一阶段提交,读未提交(Read Uncommitted) 其他的事务会发生脏读
int a = 9022 / 0;
return studentMapper.selectList(null);
}
@Override
public Boolean addStudent_commit(BusinessActionContext businessActionContext) {
System.out.println("=====9022的addStudent的提交方法");
return true;
}
@Override
//回滚需要手动操作回滚
public Boolean addStudent_rollback(BusinessActionContext businessActionContext) throws JsonProcessingException {
System.out.println("=====9022的addStudent的回滚方法");
String studentJson = businessActionContext.getActionContext("student").toString();
ObjectMapper objectMapper = new ObjectMapper();
Student student = objectMapper.readValue(studentJson, Student.class);
Map map = new HashMap();
map.put("name", student.getName());
map.put("address", student.getAddress());
studentMapper.deleteByMap(map);
return true;
}
}
//feign的远程调用nacos上的SCA01_Seata_9023服务
@FeignClient(value = "SCA01-SeataTCC-9023")
public interface FeignService9023 {
@GetMapping(value = "selectById")
Teacher selectById(@RequestParam(value = "id") int id);
@GetMapping(value = "selectAll")
List<Teacher> selectByALL();
@GetMapping(value = "deleteById")
int deleteByID(@RequestParam(value = "id") int id);
@PostMapping(value = "add")
boolean add(@RequestBody Teacher teacher);
}
4:9023核心代码,提供给9022通过feign调用
//9023的controller方法 给feign调用
@PostMapping(value = "add")
public boolean add(@RequestBody Teacher teacher){
teacherService.save1(teacher);
//9003模拟报错验证全局事务
//int a=9003/0;
return true;
}
//9023的TeacherService接口 需要注解@LocalTCC
@LocalTCC
public interface TeacherService extends IService<Teacher> {
List<Teacher> selectList();
//两段式提交
@TwoPhaseBusinessAction(name = "save1",
commitMethod = "save1_commit",
rollbackMethod = "save1_rollback")
int save1(@BusinessActionContextParameter(paramName = "teacher") Teacher teacher);
boolean save1_commit(BusinessActionContext actionContext);
boolean save1_rollback(BusinessActionContext actionContext);
}
//TeacherService接口的实现类 方法
@Service
public class TeacherServiceImpl extends ServiceImpl<TeacherMapper, Teacher>
implements TeacherService {
@Autowired
TeacherMapper teacherMapper;
private int teacher_id;
@Override
public List<Teacher> selectList() {
return teacherMapper.selectList();
}
@Override
//准备方法
public int save1(Teacher teacher) {
int insert = teacherMapper.insert(teacher);
Integer id = teacher.getId();
this.teacher_id=id;
return insert;
}
@Override
//提交方法
public boolean save1_commit(BusinessActionContext actionContext) {
System.out.println("=====9023的addTeacher的提交方法");
return true;
}
@Override
//回滚方法
public boolean save1_rollback(BusinessActionContext actionContext) {
System.out.println("=====9023的addTeacher的回滚方法");
teacherMapper.deleteById(teacher_id);
return true;
}
}
5:debug测试因为 断点在9022的int a = 9022 / 0;
http://localhost:9022/add?name=tcc3&address=纽约
在此停顿观察数据库
然后int a = 9022 / 0; 放行后在查询数据库,发现插入的两条数据会回滚,走了各自的回滚方法
这里验证的TCC模式能够保证 分布式事务的一致性。
在int a = 9022 / 0没有放行的时候,我们接着查询9023的数据,回出现读未提交,
所以想要保证安全,防止脏读?
跟AT模式一样这里的读也需要添加select for update,并且加全局注解能保证安全吗?
答案是不能,因为TCC模式是通过手动代码删除或者修改数据的,不能保证脏读。慎重使用TCC,存在业务限制。
6.3:TCC模式的优缺点
缺点:
1:需要自己手写代码的准备,提交,回滚方法,代码耦合度高
2:跟AT模式一样这里的读也需要添加select for update,并且加全局注解能保证安全吗?
答案是不能,因为TCC模式是通过手动代码删除或者修改自己添加和修改的数据,不能保证脏读。慎重使用TCC,存在业务限制。
优点:
TCC 模式是 Seata 支持的一种由业务方细粒度控制的侵入式分布式事务解决方案,是继 AT 模式后第二种支持的事务模式,最早由蚂蚁金服贡献。其分布式事务模型直接作用于服务层,不依赖底层数据库,可以灵活选择业务资源的锁定粒度,减少资源锁持有时间,可扩展性好,可以说是为独立部署的 SOA 服务而设计的。