1 环境准备
- 修改
/conf/registry.conf文件,把seata的注册中心和配置中心都改为nacos
registry {
# 修改注册中心
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
group = "SEATA_GROUP"
namespace = "f408da5f-2a7e-478f-951a-6a32ce8475f8"
cluster = "default"
username = "nacos"
password = "nacos"
}
......
}
config {
# 修改配置中心
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = "f408da5f-2a7e-478f-951a-6a32ce8475f8"
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
dataId = "seataServer.properties"
}
......
}
- 修改
/conf/file.conf文件,修改seata的存储模式,改为用数据库存储
## transaction log store, only used in seata-server
store {
## store mode: file、db、redis
mode = "db"
## rsa decryption public key
publicKey = ""
## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp)/HikariDataSource(hikari) etc.
datasource = "druid"
## mysql/oracle/postgresql/h2/oceanbase etc.
dbType = "mysql"
## MySQL8的驱动与5不同
driverClassName = "com.mysql.cj.jdbc.Driver"
## MySQL8要加timeZone
url = "jdbc:mysql://127.0.0.1:3306/my_seata?rewriteBatchedStatements=true&serverTimezone=GMT%2B8"
user = "root"
password = "persona5"
minConn = 5
maxConn = 100
globalTable = "global_table"
branchTable = "branch_table"
lockTable = "lock_table"
queryLimit = 100
maxWait = 5000
}
......
}
-
先启动nacos
-
拷贝官方提供的配置信息,并修改以下内容
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.cj.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/my_seata?rewriteBatchedStatements=true&serverTimezone=GMT%2B8
store.db.user=root
store.db.password=persona5
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
- 在nacos对应的命名空间中,新建配置,Data ID为seataServer.properties,Group为SEATA_GROUP(与
registry.conf配置的保持一致),并发布上述修改后的配置信息,注意删除空值配置项 - 启动seata
2 项目准备
- 有以下微服务
| 服务名 | 地址 | 数据库地址 |
|---|---|---|
| order-service | localhost:8801 | 10.168.6.233:3306/db_order |
| stock-service | localhost:8802 | 10.168.6.69:3306/db_stock |
- 各个微服务都视为一个RM,在各个服务对应的数据库中建立以下表
-- 该表用于数据的回滚
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,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
- 都导入以下依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
<scope>runtime</scope>
</dependency>
</dependencies>
- 都配置以下application.yml
server:
port: 8802
spring:
application:
# 具体服务的名称
name: stock-service
# 具体服务的数据源
datasource:
username: root
password: 123456
url: jdbc:mysql://10.168.6.69:3306/db_stock?serverTimezone=GMT%2B8&useSSL=false&useAffectedRows=true&allowPublicKeyRetrieval=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
cloud:
nacos:
discovery:
server-addr: localhost:8848
mybatis:
mapper-locations: classpath:/mapper/*Mapper.xml
type-aliases-package: pers.ljc.seata.stock.model
configuration:
map-underscore-to-camel-case: true
seata:
# 事务组名称,与配置项service.vgroupMapping.xxx中的xxx一致
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
- 业务场景如下:用户下单时调用order-service创建订单,创建订单后远程调用stock-service减少库存
- 各微服务业务代码如下
// order-service的业务代码
@Service
public class OrderServiceImpl implements OrderService {
private final OrderMapper orderMapper;
private final StockClient stockClient;
public OrderServiceImpl(OrderMapper orderMapper, StockClient stockClient) {
this.orderMapper = orderMapper;
this.stockClient = stockClient;
}
@Override
@GlobalTransactional
public Boolean createOrder(Order order) {
// 创建订单
int i = orderMapper.insertSelective(order);
if (i == 0) {
throw new IllegalStateException("创建订单失败");
}
// 远程调用库存服务减少库存
return stockClient.reduceStock(order.getProductId(), order.getCount());
}
}
// stock-service的业务代码
@Service
public class StockServiceImpl implements StockService {
private final StockMapper stockMapper;
public StockServiceImpl(StockMapper stockMapper) {
this.stockMapper = stockMapper;
}
@Override
public Boolean reduceStock(Long productId, Integer count) {
Stock stock = stockMapper.selectByPrimaryKey(productId);
validateReducingStock(stock, count);
stock.setCount(stock.getCount() - count);
// 模拟减库存的业务时间
//try {
// Thread.sleep(1000 * 10);
//} catch (InterruptedException e) {
// e.printStackTrace();
//}
int i = stockMapper.updateByPrimaryKeySelective(stock);
if (i == 0) {
throw new IllegalStateException("更新商品库存失败");
}
return true;
}
/**
* 校验是否能减少商品库存
*/
private void validateReducingStock(Stock stock, Integer count) {
if (Objects.isNull(stock)) {
throw new IllegalStateException("不存在该商品");
}
if (stock.getCount() < count) {
throw new IllegalStateException("该商品库存数量不足");
}
}
}
3 案例演示
3.1 AT模式
添加注解@GlobalTransactional即可实现
@Service
public class OrderServiceImpl implements OrderService {
// 其他代码...
@Override
@GlobalTransactional
public Boolean createOrder(Order order) {
// 创建订单
int i = orderMapper.insertSelective(order);
if (i == 0) {
throw new IllegalStateException("创建订单失败");
}
// 远程调用库存服务减少库存
return stockClient.reduceStock(order.getProductId(), order.getCount());
}
}