springboot:2.1.3.RELEASE;
springcloud:Greenwich.RELEASE
alibaba-seata:2.1.0.RELEASE
父模块+子模块
子模块4.0.0 cn.itcast.dtx pom 1.0-SNAPSHOT dtx-seata-demo dtx-seata-demo-bank1 dtx-seata-demo-bank2 2.1.0.RELEASE org.springframework.boot spring-boot-dependencies 2.1.3.RELEASE pom import org.springframework.cloud spring-cloud-dependencies Greenwich.RELEASE pom import com.alibaba.cloud spring-cloud-alibaba-dependencies 2.1.0.RELEASE pom import io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2 org.projectlombok lombok 1.18.0 javax.servlet javax.servlet-api 3.1.0 provided javax.interceptor javax.interceptor-api 1.2 mysql mysql-connector-java 5.1.47 org.mybatis.spring.boot mybatis-spring-boot-starter 2.0.0 com.alibaba druid-spring-boot-starter 1.2.3 commons-lang commons-lang 2.6
配置dtx-seata-demo cn.itcast.dtx 1.0-SNAPSHOT 4.0.0 dtx-seata-demo-bank1 com.alibaba.cloud spring-cloud-alibaba-seata org.springframework.cloud spring-cloud-starter-openfeign org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter mysql mysql-connector-java org.projectlombok lombok
配置如下图:
application.yml
spring:
application:
name: seata-demo-bank1
profiles:
active: dev
main:
allow-bean-definition-overriding: true
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
server:
servlet:
context-path: /bank1
feign:
hystrix:
enabled: true
compression:
request:
enabled: true # 配置请求GZIP压缩
mime-types: ["text/xml","application/xml","application/json"] # 配置压缩支持的MIME TYPE
min-request-size: 2048 # 配置压缩数据大小的下限
response:
enabled: true # 配置响应GZIP压缩
上面的配置指定的了dev配置环境,所以这里使用application-dev.yml命名
application-dev.yml
server:
port: 56081
spring:
##################### DB #####################
datasource:
druid:
url: jdbc:mysql://127.0.0.1:3306/practice?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT user()
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
connection-properties: druid.stat.mergeSql:true;druid.stat.slowSqlMillis:5000
logging:
level:
root: INFO
io:
seata: DEBUG
org:
springframework:
cloud:
alibaba:
seata:
web: DEBUG
seata客户端配置
我们这里使用最简单的file类型来连接seata服务器,所以配置也是要file类型。
registry.confregistry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
# 这里使用file类型连接服务器
type = "file"
# 指定file的 配置文件
file {
name = "file.conf"
}
}
config {
# file、nacos 、apollo、zk
# 指定file类型
type = "file"
file {
name = "file.conf"
}
}
file.conf
transport {
# tcp udt unix-domain-socket
type = "TCP"
#NIO NATIVE
server = "NIO"
#enable heartbeat
heartbeat = true
#thread factory for netty
thread-factory {
boss-thread-prefix = "NettyBoss"
worker-thread-prefix = "NettyServerNIOWorker"
server-executor-thread-prefix = "NettyServerBizHandler"
share-boss-worker = false
client-selector-thread-prefix = "NettyClientSelector"
client-selector-thread-size = 1
client-worker-thread-prefix = "NettyClientWorkerThread"
# netty boss thread size,will not be used for UDT
boss-thread-size = 1
#auto default pin or 8
worker-thread-size = 8
}
}
## transaction log store
store {
## store mode: file、db
mode = "file"
## file store
file {
dir = "sessionStore"
# branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
max-branch-session-size = 16384
# globe session size , if exceeded throws exceptions
max-global-session-size = 512
# file buffer size , if exceeded allocate new buffer
file-write-buffer-cache-size = 16384
# when recover batch read size
session.reload.read_size = 100
# async, sync
flush-disk-mode = async
}
}
service {
#vgroup->rgroup
# 命名规则是固定的:vgroup_mapping.[springcloud服务名]-fescar-service-group
# 源码根据这个命名规则获取应用服务的信息
vgroup_mapping.seata-demo-bank1-fescar-service-group = "default"
#only support single node,指定seata server的地址
default.grouplist = "127.0.0.1:8888"
#degrade current not support
enableDegrade = false
#disable
disable = false
}
client {
async.commit.buffer.limit = 10000
lock {
retry.internal = 10
retry.times = 30
}
}
配置seata代理数据源
配置位置如下:
新增DatabaseConfiguration.java,Seata的RM通过DataSourceProxy才能在业务代码的事务提交时,通过这个切入点,与TC进行通信交互、记录undo_log等。
package cn.itcast.dtx.seatademo.bank1.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
@Configuration
public class DatabaseConfiguration {
private final ApplicationContext applicationContext;
public DatabaseConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid")
public DruidDataSource ds0() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}
@Primary
@Bean
public DataSource dataSource(DruidDataSource ds0) {
DataSourceProxy pds0 = new DataSourceProxy(ds0);
return pds0;
}
}
启动类修改
注意要把springboot自带的数据源带来排除掉,否则出现配置的代理数据源与springboot自带的形成循环依赖。
package cn.itcast.dtx.seatademo.bank1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
// 启动是排除springboot自带的数据源配置类
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableFeignClients(basePackages = {"cn.itcast.dtx.seatademo.bank1.spring"})
public class Bank1Server {
public static void main(String[] args) {
SpringApplication.run(Bank1Server.class, args);
}
}
添加undo_log表
该表用来事务回滚,分支事务提交时记录事务相关信息,在分布式事务异常时回滚,分布式事务结束后会删除undo_log的记录。
在spring配置指定的数据库中创建表,每个需要注册到seata server的业务模块都有创建该表,创建语句如下:
-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
CREATE TABLE `undo_log`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
seata server服务
seata分成服务端和客户端,客户端我们在项目中引入alibaba-seata即可,服务端需要单独部署。
这里只介绍最简单的单独部署,启动模式file。
这里选择版本:seata-server-1.2.0
下载地址:https://github.com/seata/seata/tags
[seata服务端解压路径]/bin/seata-server.bat -p 8888 -m file
8888为服务对外服务端口,这里需要和file.conf中指定的一致;-m file表示以file模式启动。
seata使用示例这里只简单的演示在一个微服务中,一个分布式事物包含两个分支事物。
创建业务表CREATE TABLE `t_order` ( `id` int(11) NOT NULL AUTO_INCREMENT, `order_id` varchar(255) DEFAULT NULL COMMENT '订单', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', `user_name` varchar(255) DEFAULT NULL COMMENT '用户名称', `sex` tinyint(1) DEFAULT '0' COMMENT '性别,0:男,1:女', `age` int(3) DEFAULT '0' COMMENT '年龄', `address` varchar(255) DEFAULT NULL COMMENT '地址', `phone` varchar(11) DEFAULT NULL COMMENT '手机', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;事务测试类
代码结构如下:
package cn.itcast.dtx.seatademo.bank1.controller;
import cn.itcast.dtx.seatademo.bank1.service.TestGlobalTransService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping(value = "/test/")
public class TestGlobalTrans {
@Resource
private TestGlobalTransService service;
@GetMapping("/seataTrans")
public String testSeataTrans() throws Exception {
service.testTrans();
return "success";
}
}
service
package cn.itcast.dtx.seatademo.bank1.service;
import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class TestGlobalTransService {
@Resource
private TestOnlyTransService service;
// 只需要添加@GlobalTransactional注解就说明启动了分布式事务
@GlobalTransactional
public void testTrans() throws Exception {
// 分支事务添加用户信息
service.insertUser();
// 分支事务添加订单信息
service.insertOrder();
// 抛出异常,事务回滚
throw new Exception("test exception");
}
}
负责分支事务的服务类,单独拿出来是因为spring事务代理要求事务方法如果和调用方法放在一个类中,代理不生效,具体原因不在赘述。
package cn.itcast.dtx.seatademo.bank1.service;
import cn.itcast.dtx.seatademo.bank1.dao.UserInfo;
import cn.itcast.dtx.seatademo.bank1.dao.OrderInfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@Service
public class TestOnlyTransService {
@Resource
private OrderInfo orderInfo;
@Resource
private UserInfo userInfo;
@Transactional(rollbackFor = Exception.class)
public void insertOrder() {
orderInfo.insert();
}
@Transactional(rollbackFor = Exception.class)
public void insertUser() {
userInfo.insert();
}
}
dao
package cn.itcast.dtx.seatademo.bank1.dao;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
@Mapper
@Component
public interface OrderInfo {
@Insert("insert into t_order (order_id,create_time) values ('aaaaaaaaa',now())")
int insert();
}
@Mapper
@Component
public interface UserInfo {
@Insert("insert into t_user (user_name,sex,age, create_time) values ('aaaaaaaaa', 0,18,now())")
int insert();
}
测试结果
- 浏览器调用:http://127.0.0.1:56081/bank1/test/seataTrans
- 因为 我们的代码中有异常抛出,所有数据库中没有插入数据,事务回滚了。
2022-07-03 11:31:41.697 INFO 13816 --- [atch_RMROLE_1_8] io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked 2022-07-03 11:31:41.697 DEBUG 13816 --- [atch_RMROLE_1_8] i.s.core.rpc.netty.RmMessageListener : branch rollback result:xid=169.254.144.154:8888:2015847663,branchId=2015847665,branchStatus=PhaseTwo_Rollbacked,result code =Success,getMsg =null 2022-07-03 11:31:41.697 DEBUG 13816 --- [atch_RMROLE_1_8] i.s.core.rpc.netty.AbstractRpcRemoting : send response:xid=169.254.144.154:8888:2015847663,branchId=2015847665,branchStatus=PhaseTwo_Rollbacked,result code =Success,getMsg =null,channel:[id: 0xbf86ca50, L:/127.0.0.1:62389 - R:/127.0.0.1:8888] 2022-07-03 11:31:41.701 DEBUG 13816 --- [lector_RMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting : io.seata.core.rpc.netty.RmRpcClient@6e6f2c4f msgId:3, body:xid=169.254.144.154:8888:2015847663,branchId=2015847664,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/practice,applicationData=null 2022-07-03 11:31:41.701 INFO 13816 --- [atch_RMROLE_2_8] i.s.core.rpc.netty.RmMessageListener : onMessage:xid=169.254.144.154:8888:2015847663,branchId=2015847664,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/practice,applicationData=null 2022-07-03 11:31:41.702 INFO 13816 --- [atch_RMROLE_2_8] io.seata.rm.AbstractRMHandler : Branch Rollbacking: 169.254.144.154:8888:2015847663 2015847664 jdbc:mysql://127.0.0.1:3306/practice 2022-07-03 11:31:41.708 INFO 13816 --- [atch_RMROLE_2_8] i.s.rm.datasource.undo.UndoLogManager : xid 169.254.144.154:8888:2015847663 branch 2015847664, undo_log deleted with GlobalFinished 2022-07-03 11:31:41.709 INFO 13816 --- [atch_RMROLE_2_8] io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked 2022-07-03 11:31:41.709 DEBUG 13816 --- [atch_RMROLE_2_8] i.s.core.rpc.netty.RmMessageListener : branch rollback result:xid=169.254.144.154:8888:2015847663,branchId=2015847664,branchStatus=PhaseTwo_Rollbacked,result code =Success,getMsg =null 2022-07-03 11:31:41.709 DEBUG 13816 --- [atch_RMROLE_2_8] i.s.core.rpc.netty.AbstractRpcRemoting : send response:xid=169.254.144.154:8888:2015847663,branchId=2015847664,branchStatus=PhaseTwo_Rollbacked,result code =Success,getMsg =null,channel:[id: 0xbf86ca50, L:/127.0.0.1:62389 - R:/127.0.0.1:8888] 2022-07-03 11:31:41.714 DEBUG 13816 --- [io-56081-exec-4] io.seata.core.context.RootContext : unbind 169.254.144.154:8888:2015847663 2022-07-03 11:31:41.714 INFO 13816 --- [io-56081-exec-4] i.seata.tm.api.DefaultGlobalTransaction : [169.254.144.154:8888:2015847663] rollback status:Rollbacked 2022-07-03 11:31:41.734 ERROR 13816 --- [io-56081-exec-4] o.a.c.c.C.[.[.[.[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [/bank1] threw exception [Request processing failed; nested exception is java.lang.Exception: test exception] with root cause java.lang.Exception: test exception at cn.itcast.dtx.seatademo.bank1.service.TestGlobalTransService.testTrans(TestGlobalTransService.java:24) ~[classes/:na] at cn.itcast.dtx.seatademo.bank1.service.TestGlobalTransService$$FastClassBySpringCGLIB$$dcfd65aa.invoke() ~[classes/:na] at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) ~[spring-aop-5.1.5.RELEASE.jar:5.1.5.RELEASE]
- 修改代码,删除异常,数据保存成功。
@GlobalTransactional
public void testTrans() throws Exception {
service.insertUser();
service.insertOrder();
// throw new Exception("test exception");
}



