1、下载Seata服务端
下载Seata服务端Releases · seata/seata · GitHub
2、Seata配置Nacos
修改conf/application.yml
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: seata152
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: SEATA_GROUP
namespace: seata152
cluster: default
username: nacos
password: nacos
# store: #这个配置作用不大,因为上面在引入的nacos配置的时候,又会再引入一遍数据库的配置
# support: file 、 db 、 redis
# mode: db
# 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
3、Nacos配置
Nacos创建seata152命令空间
在创建seataServer.properties配置文件(application.yml配置文件中的data-id:),Group为SEATA_GROUP
配置内容pseata/config.txt at develop · seata/seata · GitHub
将配置中的数据库修改成自己的数据库连接,账号,密码。
store.mode=db
store.lock.mode=db
store.session.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/seata152?useUnicode=true&rewriteBatchedStatements=true
store.db.user=root
store.db.password=root123
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
service.vgroupMapping.account_tx_group=default
service.vgroupMapping.storage_tx_group=default
service.vgroupMapping.order_tx_group=default
4、创建seata数据库
mysql:https://github.com/seata/seata/tree/v1.5.2/script/server/db/mysql.sql
oracle:https://github.com/seata/seata/tree/v1.5.2/script/server/db/oracle.sql
postgresql:https://github.com/seata/seata/tree/v1.5.2/script/server/db/postgresql.sql
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid_and_branch_id` (`xid` , `branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
配置完后启动Seata。执行bin/seata-server.bat的文件。查看nacos可以看到seata已经注册到nacos上面了。
5、创建seata-account-service服务。
创建152_db_account数据库,执行下面的SQL创建表。
CREATE TABLE `t_account` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT 'id',
`user_id` bigint DEFAULT NULL COMMENT '用户id',
`total` decimal(10,0) DEFAULT NULL COMMENT '总额度',
`used` decimal(10,0) DEFAULT NULL COMMENT '已用余额',
`residue` decimal(10,0) DEFAULT '0' COMMENT '剩余可用额度',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
INSERT INTO `t_account` VALUES ('1', '1', '1000', '0', '1000');
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 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 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AT transaction mode undo table';
109-springcloud-alibaba-seate152的pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>com.zmm</groupId>
<artifactId>109-springcloud-alibaba-seate152</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>seata-order-service-8501</module>
<module>seata-account-service-8502</module>
<module>seata-storage-service-8500</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<!--Spring Cloud Alibaba 的版本信息-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.9.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--Spring Cloud 的版本信息-->
<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>
</project>
添加seata-account-service服务的pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>109-springcloud-alibaba-seate152</artifactId>
<groupId>com.zmm</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-account-com.zmm.service-8502</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos-discovery-->
<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>
<exclusions>
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
创建bootstrap.yml文件
# Tomcat
server:
port: 8502
# Spring
spring:
application:
# 应用名称
name: seata-account-service
profiles:
# 环境配置
active: dev
main:
allow-circular-references: true
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
# 注册组 要与 seata 相同
group: SEATA_GROUP
server-addr: 127.0.0.1:8848
namespace: seata152
config:
# 注册组 要与 seata 相同
group: SEATA_GROUP
server-addr: 127.0.0.1:8848
namespace: seata152
file-extension: yml
# 共享配置
#shared-configs:
# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
name: defaultDataSource
url: jdbc:mysql://127.0.0.1:3306/152_db_account?serverTimezone=UTC
username: root
password: root123
#dynamic:
# seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭
seata:
enabled: true
application-id: ${spring.application.name}
# 自定义服务群组,该值必须与 Nacos 配置中的 service.vgroupMapping.{my-service-group}=default 中的 {my-service-group}相同
tx-service-group: account_tx_group
service:
vgroup-mapping:
# 自定义服务群组,该值必须与 Nacos 配置中的 service.vgroupMapping.{my-service-group}=default 中的 {my-service-group}相同
account_tx_group: default
grouplist:
default: 127.0.0.1:8091
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata152
username: nacos
password: nacos
data-id: seataServer.properties
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata152
username: nacos
password: nacos
###################################### MyBatis 配置 ######################################
mybatis:
# 指定 mapper.xml 的位置
mapper-locations: classpath:mapper/*.xml
#扫描实体类的位置,在此处指明扫描实体类的包,在 mapper.xml 中就可以不写实体类的全路径名
type-aliases-package: com.zmm.entity
configuration:
#默认开启驼峰命名法,可以不用设置该属性
map-underscore-to-camel-case: true
management:
endpoints:
web:
exposure:
include: "*" # * 在yaml 文件属于关键字,所以需要加引号
创建AccountMapper
@Mapper
public interface AccountMapper {
Account selectByUserId(Long userId);
int decrease(Long userId, BigDecimal money);
}
创建AccountMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zmm.mapper.AccountMapper">
<resultMap id="BaseResultMap" type="com.zmm.entity.Account">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="user_id" jdbcType="BIGINT" property="userId"/>
<result column="total" jdbcType="DECIMAL" property="total"/>
<result column="used" jdbcType="DECIMAL" property="used"/>
<result column="residue" jdbcType="DECIMAL" property="residue"/>
</resultMap>
<sql id="Base_Column_List">
id
, user_id, total, used, residue
</sql>
<select id="selectByUserId" resultType="com.zmm.entity.Account">
select
<include refid="Base_Column_List"/>
from t_account
where user_id = #{userId,jdbcType=BIGINT}
</select>
<update id="decrease">
UPDATE t_account
SET residue = residue - #{money},
used = used + #{money}
WHERE user_id = #{userId};
</update>
</mapper>
创建Account
@Data
public class Account {
private Long id;
private Long userId;
private BigDecimal total;
private BigDecimal used;
private BigDecimal residue;
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CommonResult<T>{
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message) {
this(code, message, null);
}
}
创建AccountService
public interface AccountService {
int decrease(Long userId, BigDecimal money) ;
}
@Service
@Slf4j
public class AccountServiceImpl implements AccountService{
@Resource
AccountMapper accountMapper;
@GlobalTransactional(rollbackFor = Exception.class)
@Override
public int decrease(Long userId, BigDecimal money) {
log.info("Seata全局事务id=================>{}", RootContext.getXID());
log.info("------->account-com.zmm.service 开始查询账户余额");
Account account = accountMapper.selectByUserId(userId);
log.info("------->account-com.zmm.service 账户余额查询完成," + account);
if (account != null && account.getResidue().intValue() >= money.intValue()) {
log.info("------->account-com.zmm.service 开始从账户余额中扣钱!");
int decrease = accountMapper.decrease(userId, money);
log.info("------->account-com.zmm.service 从账户余额中扣钱完成");
return decrease;
} else {
log.info("账户余额不足,开始回滚!");
throw new RuntimeException("账户余额不足!");
}
}
}
创建DataSourceConfig
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.name}")
private String name;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Bean
@Primary
public DataSource dataSource(){
DruidDataSource druidDataSource=new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setName(name);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
DataSourceProxy dataSourceProxy=new DataSourceProxy(druidDataSource);
return dataSourceProxy;
}
}
创建AccountController
@RestController
@Slf4j
public class AccountController {
@Resource
AccountServiceImpl accountService;
@PostMapping(value = "/account/decrease")
CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money){
int decrease=accountService.decrease(userId,money);
return new CommonResult(200, "扣款成功",decrease);
}
}
创建Seata152Application8502
@SpringBootApplication
@EnableDiscoveryClient
//@EnableFeignClients
@RefreshScope
public class Seata152Application8502 {
public static void main(String[] args) {
SpringApplication.run(Seata152Application8502.class,args);
}
}
6、创建seata-order-service服务。
创建152_db_order数据库,执行下面的SQL创建表。
CREATE TABLE `t_order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint DEFAULT NULL COMMENT '用户id',
`product_id` bigint DEFAULT NULL COMMENT '产品id',
`count` int DEFAULT NULL COMMENT '数量',
`money` decimal(11,0) DEFAULT NULL COMMENT '金额',
`status` int DEFAULT NULL COMMENT '订单状态:0:未完成;1:已完结',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8mb3;
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 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 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AT transaction mode undo table';
添加pom.xml依赖。
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>109-springcloud-alibaba-seate152</artifactId>
<groupId>com.zmm</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-order-com.zmm.service-8501</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
创建bootstrap.yml文件
# Tomcat
server:
port: 8501
# Spring
spring:
application:
# 应用名称
name: seata-order-service
profiles:
# 环境配置
active: dev
main:
allow-circular-references: true
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
# 注册组 要与 seata 相同
group: SEATA_GROUP
server-addr: 127.0.0.1:8848
namespace: seata152
config:
# 注册组 要与 seata 相同
group: SEATA_GROUP
server-addr: 127.0.0.1:8848
namespace: seata152
file-extension: yml
# 共享配置
#shared-configs:
# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
name: defaultDataSource
url: jdbc:mysql://127.0.0.1:3306/152_db_order?serverTimezone=UTC
username: root
password: root123
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: order_tx_group
service:
vgroup-mapping:
order_tx_group: default
grouplist:
default: 127.0.0.1:8091
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata152
username: nacos
password: nacos
data-id: seataServer.properties
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata152
username: nacos
password: nacos
###################################### MyBatis 配置 ######################################
mybatis:
# 指定 mapper.xml 的位置
mapper-locations: classpath:mapper/*.xml
#扫描实体类的位置,在此处指明扫描实体类的包,在 mapper.xml 中就可以不写实体类的全路径名
type-aliases-package: com.zmm.entity
configuration:
#默认开启驼峰命名法,可以不用设置该属性
map-underscore-to-camel-case: true
management:
endpoints:
web:
exposure:
include: "*" # * 在yaml 文件属于关键字,所以需要加引号
创建CommonResult
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CommonResult<T>{
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message) {
this(code, message, null);
}
}
@Data
public class Order {
private Long id;
private Long userId;
private Long productId;
private Integer count;
private BigDecimal money;
private Integer status;
}
创建OrderMapper
@Mapper
public interface OrderMapper {
int create(Order order);
//2 修改订单状态,从零改为1
void update(@Param("userId") Long userId, @Param("status") Integer status);
}
创建OrderMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zmm.mapper.OrderMapper">
<resultMap id="BaseResultMap" type="com.zmm.entity.Order">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="user_id" jdbcType="BIGINT" property="userId"/>
<result column="product_id" jdbcType="BIGINT" property="productId"/>
<result column="count" jdbcType="INTEGER" property="count"/>
<result column="money" jdbcType="DECIMAL" property="money"/>
<result column="status" jdbcType="INTEGER" property="status"/>
</resultMap>
<sql id="Base_Column_List">
id
, user_id, product_id, count, money, status
</sql>
<insert id="create" parameterType="com.zmm.entity.Order">
insert into t_order (user_id, product_id,
count, money, status)
values (#{userId,jdbcType=BIGINT}, #{productId,jdbcType=BIGINT},
#{count,jdbcType=INTEGER}, #{money,jdbcType=DECIMAL}, #{status,jdbcType=INTEGER})
</insert>
<update id="update">
update t_order
set status = 1
where user_id = #{userId}
and status = #{status};
</update>
</mapper>
创建OrderService
public interface OrderService {
/**
* 创建订单数据
* @param order
*/
CommonResult create(Order order);
}
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Resource
private OrderMapper orderMapper;
@Resource
private StorageService storageService;
@Resource
private AccountService accountService;
/**
* 创建订单->调用库存服务扣减库存->调用账户服务扣减账户余额->修改订单状态
* 简单说:下订单->扣库存->减余额->改订单状态
*/
@GlobalTransactional(rollbackFor = Exception.class)
@Override
public CommonResult create(Order order) {
log.info("Seata全局事务id=================>{}", RootContext.getXID());
log.info("----->开始新建订单");
//1 新建订单
order.setUserId(new Long(1));
order.setStatus(0);
orderMapper.create(order);
//2 扣减库存
log.info("----->订单服务开始调用库存服务,开始扣减库存");
storageService.decrease(order.getProductId(), order.getCount());
log.info("----->订单微服务开始调用库存,扣减库存结束");
//3 扣减账户
log.info("----->订单服务开始调用账户服务,开始从账户扣减商品金额");
accountService.decrease(order.getUserId(), order.getMoney());
log.info("----->订单微服务开始调用账户,账户扣减商品金额结束");
//4 修改订单状态,从零到1,1代表已经完成
log.info("----->修改订单状态开始");
orderMapper.update(order.getUserId(), 0);
log.info("----->修改订单状态结束");
log.info("----->下订单结束了------->");
return new CommonResult(200, "订单创建成功");
}
}
创建AccountService 远程调用
@FeignClient(value = "seata-account-service")
public interface AccountService {
@PostMapping(value = "/account/decrease")
CommonResult decrease(@RequestParam("userId") Long userId, @RequestParam("money") BigDecimal money);
}
创建StorageService 远程调用
@FeignClient(value = "seata-storage-service")
public interface StorageService {
@PostMapping(value = "/storage/decrease")
CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count);
}
创建OrderController
@RestController
public class OrderController {
@Autowired
private OrderServiceImpl orderService;
@GetMapping("/order/create/{productId}/{count}/{money}")
public CommonResult create(@PathVariable("productId") Integer productId, @PathVariable("count") Integer count
, @PathVariable("money") BigDecimal money) {
Order order = new Order();
order.setProductId(Integer.valueOf(productId).longValue());
order.setCount(count);
order.setMoney(money);
return orderService.create(order);
}
}
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.name}")
private String name;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Bean
@Primary
public DataSource dataSource(){
DruidDataSource druidDataSource=new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setName(name);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
DataSourceProxy dataSourceProxy=new DataSourceProxy(druidDataSource);
return dataSourceProxy;
}
}
创建Seata152Application8501 启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@RefreshScope
public class Seata152Application8501 {
public static void main(String[] args) {
SpringApplication.run(Seata152Application8501.class,args);
}
}
7、创建seata-storage-service服务。
创建152_db_storage数据库,执行下面的SQL创建表。
CREATE TABLE `t_storage` (
`id` bigint NOT NULL AUTO_INCREMENT,
`product_id` bigint DEFAULT NULL COMMENT '产品id',
`total` int DEFAULT NULL COMMENT '总库存',
`used` int DEFAULT NULL COMMENT '已用库存',
`residue` int DEFAULT NULL COMMENT '剩余库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3;
INSERT INTO `130_db_storage`.`t_storage` (`id`, `product_id`, `total`, `used`, `residue`) VALUES (1, 1, 100, 0, 100);
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 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 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AT transaction mode undo table';
添加pom.xml依赖。
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>109-springcloud-alibaba-seate152</artifactId>
<groupId>com.zmm</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-storage-com.zmm.service-8500</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos-discovery-->
<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>
<exclusions>
<exclusion>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
创建bootstrap.yml文件
# Tomcat
server:
port: 8500
# Spring
spring:
application:
# 应用名称
name: seata-storage-service
profiles:
# 环境配置
active: dev
main:
allow-circular-references: true
allow-bean-definition-overriding: true
cloud:
nacos:
discovery:
# 注册组 要与 seata 相同
group: SEATA_GROUP
server-addr: 127.0.0.1:8848
namespace: seata152
config:
# 注册组 要与 seata 相同
group: SEATA_GROUP
server-addr: 127.0.0.1:8848
namespace: seata152
file-extension: yml
# 共享配置
#shared-configs:
# - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
name: defaultDataSource
url: jdbc:mysql://127.0.0.1:3306/152_db_storage?serverTimezone=UTC
username: root
password: root123
#dynamic:
# seata: true # 开启seata代理,开启后默认每个数据源都代理,如果某个不需要代理可单独关闭
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: storage_tx_group
service:
vgroup-mapping:
storage_tx_group: default
grouplist:
default: 127.0.0.1:8091
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata152
username: nacos
password: nacos
data-id: seataServer.properties
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace: seata152
username: nacos
password: nacos
###################################### MyBatis 配置 ######################################
mybatis:
# 指定 mapper.xml 的位置
mapper-locations: classpath:mapper/*.xml
#扫描实体类的位置,在此处指明扫描实体类的包,在 mapper.xml 中就可以不写实体类的全路径名
type-aliases-package: com.zmm.entity
configuration:
#默认开启驼峰命名法,可以不用设置该属性
map-underscore-to-camel-case: true
management:
endpoints:
web:
exposure:
include: "*" # * 在yaml 文件属于关键字,所以需要加引号
创建实体
@Data
public class Storage {
private Long id;
private Long productId;
private Integer total;
private Integer used;
private Integer residue;
}
@AllArgsConstructor
@NoArgsConstructor
@Data
public class CommonResult<T>{
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message) {
this(code, message, null);
}
}
创建StorageMapper
@Mapper
public interface StorageMapper {
Storage selectByProductId(Long productId);
int decrease(Storage record);
}
创建StorageMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zmm.mapper.StorageMapper">
<resultMap id="BaseResultMap" type="com.zmm.entity.Storage">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="product_id" jdbcType="BIGINT" property="productId"/>
<result column="total" jdbcType="INTEGER" property="total"/>
<result column="used" jdbcType="INTEGER" property="used"/>
<result column="residue" jdbcType="INTEGER" property="residue"/>
</resultMap>
<sql id="Base_Column_List">
id
, product_id, total, used, residue
</sql>
<update id="decrease" parameterType="com.zmm.entity.Storage">
update t_storage
<set>
<if test="total != null">
total = #{total,jdbcType=INTEGER},
</if>
<if test="used != null">
used = #{used,jdbcType=INTEGER},
</if>
<if test="residue != null">
residue = #{residue,jdbcType=INTEGER},
</if>
</set>
where product_id = #{productId,jdbcType=BIGINT}
</update>
<select id="selectByProductId" parameterType="java.lang.Long" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from t_storage
where product_id = #{productId,jdbcType=BIGINT}
</select>
</mapper>
创建StorageService
public interface StorageService {
int decrease(Long productId, Integer count) ;
}
@Service
@Slf4j
public class StorageServiceImpl implements StorageService{
@Resource
StorageMapper storageMapper;
@GlobalTransactional(rollbackFor = Exception.class)
@Override
public int decrease(Long productId, Integer count) {
log.info("Seata全局事务id=================>{}", RootContext.getXID());
log.info("------->storage-service中扣减库存开始");
log.info("------->storage-com.zmm.service 开始查询商品是否存在");
Storage storage = storageMapper.selectByProductId(productId);
if (storage != null && storage.getResidue().intValue() >= count.intValue()) {
Storage storage2 = new Storage();
storage2.setProductId(productId);
storage.setUsed(storage.getUsed() + count);
storage.setResidue(storage.getTotal().intValue() - storage.getUsed());
int decrease = storageMapper.decrease(storage);
log.info("------->storage-com.zmm.service 扣减库存成功");
return decrease;
} else {
log.info("------->storage-com.zmm.service 库存不足,开始回滚!");
throw new RuntimeException("库存不足,扣减库存失败!");
}
}
}
创建StorageController
@RestController
@Slf4j
public class StorageController {
@Resource
private StorageServiceImpl storageService;
@Value("${server.port}")
private String serverPort;
@PostMapping(value = "/storage/decrease")
CommonResult decrease(@RequestParam("productId") Long productId, @RequestParam("count") Integer count) {
int decrease = storageService.decrease(productId, count);
CommonResult result;
if (decrease > 0) {
result = new CommonResult(200, "from mysql,serverPort: " + serverPort, decrease);
} else {
result = new CommonResult(505, "from mysql,serverPort: " + serverPort, "库存扣减失败");
}
return result;
}
}
创建DataSourceConfig
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.name}")
private String name;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Bean
@Primary
public DataSource dataSource(){
DruidDataSource druidDataSource=new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setName(name);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
DataSourceProxy dataSourceProxy=new DataSourceProxy(druidDataSource);
return dataSourceProxy;
}
}
创建Seata152Application8500 启动类
@SpringBootApplication
@EnableDiscoveryClient
//@EnableFeignClients
@RefreshScope
public class Seata152Application8500 {
public static void main(String[] args) {
SpringApplication.run(Seata152Application8500.class,args);
}
}
8、测试
创建完三个服务后分别启动来。
用浏览器调用http://127.0.0.1:8501/order/create/1/2/50
在分别看三个数据库数据。
可以看到订单已经完成了。数据也都记录下来。
在调用 http://127.0.0.1:8501/order/create/1/2/5000
可以看到后端服务已经报错。在看下三个数据库数据
报错后没有参数脏数据。说明分布式事务已经生效了。
9、集成Seata1.5.2中的问题。
分布式事务:Seata框架AT模式及TCC模式执行流程剖析 - 朝雨忆轻尘 - 博客园
集成Seata1.5.2后,调用报错方法发现事务没有生效。在排查的过程中发现undo_log表没有存入数据。只需要手动注入DataSourceProxy数据源就可以了。