1.前提
已经完成了seata服务端启动的环境部署,并已经启动seata服务端
参考:https://www.yuque.com/u27809381/ca4o9w/avy27g
2.案例
如下,假设当前存在一个存在一个订单确认的功能
该功能的对外暴露接口存在于订单服务,接口的功能包括3步
- 订单状态改为已确认
- 物品扣减库存
- 账户扣除花费
3.创建物品服务
建库建表
-- 创建物品数据库
CREATE DATABASE `item`;
-- 使用物品数据库
USE `item`;
-- 创建物品表
DROP TABLE IF EXISTS `t_item`;
CREATE TABLE `t_item` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`count` INT DEFAULT NULL COMMENT '库存',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 创建回滚日志表
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `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';
-- 插入测试数据
INSERT INTO t_item (id,COUNT)
VALUES
(1,5),
(2,15),
(3,20),
(4,25);
项目引入依赖
<!--web服务依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Nacos 服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入 seata 依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--第三方工具类库-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.18</version>
</dependency>
<!--代码生成器依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<!-- ...................多数据源连接数据库相关依赖 start................... -->
<!--动态数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<!--Mybatis连接依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!--mybatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- ...................多数据源连接数据库相关依赖 end................... -->
<!--Lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--swagger依赖-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.8.0.RELEASE</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
编写bootstrap.yml文件
#端点配置
management:
endpoints:
web:
exposure:
include: '*'
#spring配置
spring:
#服务名称
application:
name: item
cloud:
#注册中心配置
nacos:
discovery:
server-addr: 8.142.132.135:8848 #nacos地址
#事务分组配置
alibaba:
seata:
tx-service-group: service-item-group
编写application.yml文件
#服务器配置
server:
port: 8082
spring:
#配置数据库数据源
datasource:
dynamic:
type: com.alibaba.druid.pool.DruidDataSource
primary: item
#配置连接池
druid:
initialSize: 10
minIdle: 1
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x' FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: false
maxOpenPreparedStatements: -1
filters: stat,log4j,wall
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
useGlobalDataSourceStat: true
#配置数据源
datasource:
item:
url: jdbc:mysql://10.128.41.42:3306/item?characterEncoding=UTF-8&autoReconnect=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull&useUnicode=true&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=true&rewriteBatchedStatements=true
username: root
password: sMviwqONsqW&Ag$5
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
#seata配置
seata:
#配置中心配置
config:
type: nacos
nacos:
server-addr: 8.142.132.135:8848
group: "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
#注册中心配置
registry:
type: nacos
nacos:
application: seata-server
server-addr: 8.142.132.135:8848
group: "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
启动类添加注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ItemApplication {
public static void main(String[] args) {
SpringApplication.run(ItemApplication.class, args);
}
}
编写扣减库存接口
mapper
import com.example.item.entity.TItemPo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
@Mapper
public interface TItemMapper extends BaseMapper<TItemPo> {
}
service
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.item.entity.TItemPo;
/**
* <p>
* 服务类
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
public interface TItemService extends IService<TItemPo> {
/**
* 扣减库存
*
* @param id 主键ID
* @return 扣减后对象
*/
JSONObject delCount(Integer id);
}
service.impl
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.item.entity.TItemPo;
import com.example.item.mapper.TItemMapper;
import com.example.item.service.TItemService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* <p>
* 服务实现类
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class TItemServiceImpl extends ServiceImpl<TItemMapper, TItemPo> implements TItemService {
@Override
public JSONObject delCount(Integer id) {
//1.id判定
if (id <= 0) {
throw new RuntimeException("id异常");
}
//2.查询库存
TItemPo itemPo = getById(id);
if (ObjectUtil.isNull(itemPo)) {
throw new RuntimeException("未找到资源");
}
//3.修改库存,默认-10,测试使用
itemPo.setCount(itemPo.getCount() - 10);
if (itemPo.getCount() < 0) {
throw new RuntimeException("库存数不足");
}
//4.数据库更新
boolean isSuccess = updateById(itemPo);
if (!isSuccess) {
throw new RuntimeException("数据库更新失败");
}
//5.封装返回结果
JSONObject result = new JSONObject(true);
result.set("status", true);
result.set("data", itemPo);
//6.返回
return result;
}
}
entity
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
*
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_item")
@ApiModel(value="TItemPo对象", description="")
public class TItemPo extends Model<TItemPo> {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "库存")
private Integer count;
@Override
public Serializable pkVal() {
return this.id;
}
}
controller
import cn.hutool.json.JSONObject;
import com.example.item.service.TItemService;
import lombok.RequiredArgsConstructor;
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;
/**
* <p>
* 前端控制器
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
@RestController
@RequestMapping("/api/v1/items")
@RequiredArgsConstructor
public class TItemController {
private final TItemService itemService;
@PutMapping("{id}")
public JSONObject delCount(@PathVariable Integer id) {
return itemService.delCount(id);
}
}
4. 创建账户服务
建库建表
-- 创建账户数据库
CREATE DATABASE `account`;
-- 使用账户数据库
USE `account`;
-- 创建账户表
DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`count` INT DEFAULT NULL COMMENT '余额',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 创建回滚日志表
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `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';
-- 插入测试数据
INSERT INTO t_account (id,COUNT)
VALUES
(1,100),
(2,150),
(3,200),
(4,250);
项目引入依赖
<!--web服务依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Nacos 服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入 seata 依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--第三方工具类库-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.18</version>
</dependency>
<!--代码生成器依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
<!-- ...................多数据源连接数据库相关依赖 start................... -->
<!--动态数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<!--Mybatis连接依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!--mybatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- ...................多数据源连接数据库相关依赖 end................... -->
<!--Lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--swagger依赖-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.8.0.RELEASE</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
编写bootstrap.yml文件
#端点配置
management:
endpoints:
web:
exposure:
include: '*'
#spring配置
spring:
#服务名称
application:
name: account
cloud:
#注册中心配置
nacos:
discovery:
server-addr: 8.142.132.135:8848 #nacos地址
#事务分组配置
alibaba:
seata:
tx-service-group: service-account-group
编写application.yml文件
#服务器配置
server:
port: 8083
spring:
#配置数据库数据源
datasource:
dynamic:
type: com.alibaba.druid.pool.DruidDataSource
primary: account
#配置连接池
druid:
initialSize: 10
minIdle: 1
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x' FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: false
maxOpenPreparedStatements: -1
filters: stat,log4j,wall
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
useGlobalDataSourceStat: true
#配置数据源
datasource:
account:
url: jdbc:mysql://10.128.41.42:3306/account?characterEncoding=UTF-8&autoReconnect=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull&useUnicode=true&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=true&rewriteBatchedStatements=true
username: root
password: sMviwqONsqW&Ag$5
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
#seata配置
seata:
#配置中心配置
config:
type: nacos
nacos:
server-addr: 8.142.132.135:8848
group: "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
#注册中心配置
registry:
type: nacos
nacos:
application: seata-server
server-addr: 8.142.132.135:8848
group: "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
启动类添加注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class AccountApplication {
public static void main(String[] args) {
SpringApplication.run(AccountApplication.class, args);
}
}
编写扣减账户余额接口
mapper
import com.example.account.entity.TAccountPo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
@Mapper
public interface TAccountMapper extends BaseMapper<TAccountPo> {
}
service
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.account.entity.TAccountPo;
/**
* <p>
* 服务类
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
public interface TAccountService extends IService<TAccountPo> {
/**
* 扣减余额
*
* @param id 主键ID
* @return 扣减后对象
*/
JSONObject delCount(Integer id);
}
service.impl
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.account.entity.TAccountPo;
import com.example.account.mapper.TAccountMapper;
import com.example.account.service.TAccountService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* <p>
* 服务实现类
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class TAccountServiceImpl extends ServiceImpl<TAccountMapper, TAccountPo> implements TAccountService {
@Override
public JSONObject delCount(Integer id) {
//1.id判定
if (id <= 0) {
throw new RuntimeException("id异常");
}
//2.查询库存
TAccountPo accountPo = getById(id);
if (ObjectUtil.isNull(accountPo)) {
throw new RuntimeException("未找到资源");
}
//3.修改余额,默认-100,测试使用
accountPo.setCount(accountPo.getCount() - 100);
if (accountPo.getCount() < 0) {
throw new RuntimeException("余额不足");
}
//4.数据库更新
boolean isSuccess = updateById(accountPo);
if (!isSuccess) {
throw new RuntimeException("数据库更新失败");
}
//5.封装返回结果
JSONObject result = new JSONObject(true);
result.set("status", true);
result.set("data", accountPo);
//6.返回
return result;
}
}
entity
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
/**
* <p>
*
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_account")
@ApiModel(value = "TAccountPo对象", description = "")
public class TAccountPo extends Model<TAccountPo> {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "余额")
private Integer count;
@Override
public Serializable pkVal() {
return this.id;
}
}
controller
import cn.hutool.json.JSONObject;
import com.example.account.service.TAccountService;
import lombok.RequiredArgsConstructor;
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;
/**
* <p>
* 前端控制器
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
@RestController
@RequestMapping("/api/v1/accounts")
@RequiredArgsConstructor
public class TAccountController {
private final TAccountService accountService;
@PutMapping("{id}")
public JSONObject delCount(@PathVariable Integer id) {
return accountService.delCount(id);
}
}
5.创建订单服务
建库建表
-- 创建订单数据库
CREATE DATABASE `order`;
-- 使用订单数据库
USE `order`;
-- 创建订单表
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`status` INT DEFAULT NULL COMMENT '订单状态:0:未完成;1:已完结',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 创建回滚日志表
DROP TABLE IF EXISTS `undo_log`;
CREATE TABLE `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';
-- 插入测试数据
INSERT INTO t_order (id,STATUS)
VALUES
(1,0),
(2,0),
(3,0),
(4,0);
项目引入依赖
<!--web服务依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--Nacos 服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入 seata 依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!-- openfeign依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- openfeign优化请求连接池依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
<!--第三方工具类库-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.18</version>
</dependency>
<!-- ...................多数据源连接数据库相关依赖 start................... -->
<!--动态数据源-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<!--Mybatis连接依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<!--mybatis-plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- ...................多数据源连接数据库相关依赖 end................... -->
<!--Lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--swagger依赖-->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.8.0.RELEASE</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
编写bootstramp.yml文件
#端点配置
management:
endpoints:
web:
exposure:
include: '*'
#spring配置
spring:
#服务名称
application:
name: order
cloud:
#注册中心配置
nacos:
discovery:
server-addr: 8.142.132.135:8848 #nacos地址
#事务分组配置
alibaba:
seata:
tx-service-group: service-order-group
编写application.yml文件
#服务器配置
server:
port: 8081
spring:
#配置数据库数据源
datasource:
dynamic:
type: com.alibaba.druid.pool.DruidDataSource
primary: order
#配置连接池
druid:
initialSize: 10
minIdle: 1
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x' FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: false
maxOpenPreparedStatements: -1
filters: stat,log4j,wall
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
useGlobalDataSourceStat: true
#配置数据源
datasource:
order:
url: jdbc:mysql://10.128.41.42:3306/order?characterEncoding=UTF-8&autoReconnect=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=convertToNull&useUnicode=true&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=true&rewriteBatchedStatements=true
username: root
password: sMviwqONsqW&Ag$5
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
#seata配置
seata:
#配置中心配置
config:
type: nacos
nacos:
server-addr: 8.142.132.135:8848
group: "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
#注册中心配置
registry:
type: nacos
nacos:
application: seata-server
server-addr: 8.142.132.135:8848
group: "SEATA_GROUP"
namespace: ""
username: "nacos"
password: "nacos"
#ribbon配置
ribbon:
ReadTimeout: 6000
ConnectionTimeout: 6000
#feign配置
feign:
client:
httpclient:
enabled: true
compression:
request:
enabled: true
mime-types: text/xml,application/xml, application/json
min-request-size: 1024
response:
enabled: true
启动类添加注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
编写订单确认接口
mapper
import com.example.order.entity.TOrderPo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
@Mapper
public interface TOrderMapper extends BaseMapper<TOrderPo> {
}
service
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.order.entity.TOrderPo;
/**
* <p>
* 服务类
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
public interface TOrderService extends IService<TOrderPo> {
/**
* 修改订单状态
*
* @param id 订单ID
* @return 修改后的订单
*/
JSONObject updateOrder(Integer id);
}
service.impl
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.order.entity.TOrderPo;
import com.example.order.mapper.TOrderMapper;
import com.example.order.service.TOrderService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* <p>
* 服务实现类
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class TOrderServiceImpl extends ServiceImpl<TOrderMapper, TOrderPo> implements TOrderService {
@Override
public JSONObject updateOrder(Integer id) {
//1.id判定
if (id <= 0) {
throw new RuntimeException("id异常");
}
//2.查询账户
TOrderPo orderPo = getById(id);
if (ObjectUtil.isNull(orderPo)) {
throw new RuntimeException("未找到资源");
}
//3.修改订单状态
orderPo.setStatus(1);
//4.数据库更新
boolean isSuccess = updateById(orderPo);
if (!isSuccess) {
throw new RuntimeException("数据库更新失败");
}
//5.封装返回结果
JSONObject result = new JSONObject(true);
result.set("status", true);
result.set("data", orderPo);
//6.返回
return result;
}
}
entity
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.baomidou.mybatisplus.annotation.TableId;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* <p>
*
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("t_order")
@ApiModel(value="TOrderPo对象", description="")
public class TOrderPo extends Model<TOrderPo> {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "订单状态:0:未完成;1:已完结")
private Integer status;
@Override
public Serializable pkVal() {
return this.id;
}
}
ItemFeign
import cn.hutool.json.JSONObject;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
/**
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @date 2022-06-27 18:24:50
* @describe:
*/
@Component
@FeignClient(value = "item")
public interface ItemFeign {
@PutMapping("/api/v1/items/{id}")
JSONObject delCount(@PathVariable Integer id);
}
AccountFeign
import cn.hutool.json.JSONObject;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
/**
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @date 2022-06-27 18:24:50
* @describe:
*/
@Component
@FeignClient(value = "account")
public interface AccountFeign {
@PutMapping("/api/v1/accounts/{id}")
JSONObject delCount(@PathVariable Integer id);
}
controller
import cn.hutool.json.JSONObject;
import cn.hutool.log.StaticLog;
import com.example.order.feign.AccountFeign;
import com.example.order.feign.ItemFeign;
import com.example.order.service.TOrderService;
import lombok.RequiredArgsConstructor;
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;
/**
* <p>
* 前端控制器
* </p>
*
* @author 冯铁城 [fengtiecheng@cyou-inc.com]
* @since 2022-06-27
*/
@RestController
@RequestMapping("/api/v1/orders")
@RequiredArgsConstructor
public class TOrderController {
private final TOrderService orderService;
private final ItemFeign itemFeign;
private final AccountFeign accountFeign;
@PutMapping("{id}")
public JSONObject updateOrder(@PathVariable Integer id) {
//1.修改订单状态
JSONObject result = orderService.updateOrder(id);
//2.调用物品服务扣减库存
JSONObject itemResult = itemFeign.delCount(id);
StaticLog.info("物品服务扣减库存响应:[{}]", itemResult);
//3.调用账户服务扣减余额
JSONObject accountResult = accountFeign.delCount(id);
StaticLog.info("账户服务扣减余额响应:[{}]", accountResult);
//4.返回
return result;
}
}
6.未引入seata触发修改订单状态接口
因为item服务每次会扣除10库存,而id=1的库存=5,当库存不足时,item服务会抛出异常,因此调用item服务的order服务也会异常
如图:响应异常
异常原因出现在item服务
但是此时查看order数据库,id为1的订单状态,发现状态已经变更了,此时就出现了数据不一致问题
7.@GlobalTransactional注解引入
注解说明
seata提供了@GlobalTransactional标签来控制全局事务的开启、管理以及控制
@GlobalTransactional注解最常用的属性
- name:声明全局事务的名称
- rollbackFor:出现什么异常时全局回滚
@GlobalTransactional注解的作用域
- 在方法上使用时,全局事务的范围就是该方法以及它所涉及的所有服务。
- 在类上使用时,全局事务的范围就是这个类中的所有方法以及这些方法涉及的服务。
order服务改造
```java import cn.hutool.json.JSONObject; import cn.hutool.log.StaticLog; import com.example.order.feign.AccountFeign; import com.example.order.feign.ItemFeign; import com.example.order.service.TOrderService; import io.seata.spring.annotation.GlobalTransactional; import lombok.RequiredArgsConstructor; 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 冯铁城 [fengtiecheng@cyou-inc.com]
@since 2022-06-27 */ @RestController @RequestMapping(“/api/v1/orders”) @RequiredArgsConstructor public class TOrderController {
private final TOrderService orderService;
private final ItemFeign itemFeign;
private final AccountFeign accountFeign;
@PutMapping(“{id}”) @GlobalTransactional(name = “order-update-order”, rollbackFor = Exception.class) public JSONObject updateOrder(@PathVariable Integer id) {
//1.修改订单状态
JSONObject result = orderService.updateOrder(id);
//2.调用物品服务扣减库存
JSONObject itemResult = itemFeign.delCount(id);
StaticLog.info("物品服务扣减库存响应:[{}]", itemResult);
//3.调用账户服务扣减余额
JSONObject accountResult = accountFeign.delCount(id);
StaticLog.info("账户服务扣减余额响应:[{}]", accountResult);
//4.返回
return result;
改造后具体逻辑步骤