Seata 分布式事务-应用实例

Seata 分布式事务-应用实例

需求分析/图解

  1. 需求:完成下订单功能,由三个微服务模块协同完成, 涉及到多数据库, 多张表

image.png
分析 黑色线是执行顺序线 红色线是想Seata Server注册 最后紫色线是决定是否提交和回滚

项目目录

主题包结构都是一样的但是类名字每个项目是不一样的这里列举的10012端口微服务的

image.png

创建数据库和表

-- 订单微服务的数据库
CREATE DATABASE order_micro_service
USE order_micro_serviceCREATE TABLE `order`(
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT DEFAULT NULL ,
product_id BIGINT DEFAULT NULL ,
nums INT DEFAULT NULL ,
money INT DEFAULT NULL,
`status` INT DEFAULT NULL COMMENT '0:创建中; 1:已完结'
);
SELECT * FROM `order`-- 库存微服务的数据库`storage``order`
CREATE DATABASE storage_micro_service
USE storage_micro_serviceCREATE TABLE `storage`(
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
product_id BIGINT DEFAULT NULL ,
amount INT DEFAULT NULL COMMENT '库存量'
);-- 初始化库存表
INSERT INTO `storage` VALUES(NULL, 1, 10);
SELECT * FROM `storage`-- 账号微服务的数据库
CREATE DATABASE account_micro_service
USE account_micro_serviceCREATE TABLE `account`(
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
user_id BIGINT DEFAULT NULL ,
money INT DEFAULT NULL COMMENT '账户金额'
);-- 初始化账户表
INSERT INTO `account` VALUES(NULL, 666, 10000);

分别为3 库创建对应的回滚日志表, 说明回滚日志表在seata 的\conf\db_undo_log.sql

use order_micro_service
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;use storage_micro_service
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;use account_micro_service
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;

开发seata_storage_micro_service-10010 微服务

参考以前的方式,创建seata_storage_micro_service-10010 微服务模块

修改pom.xml

添加相关的jar 依赖

    <!--引入相关的依赖--><dependencies><!--引入opefeign starter 即场景启动器 因为要和其他微服务通信--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--引入 seata starter --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-seata</artifactId><exclusions><!--排除自带的seata-all, 引入自己的版本, 否则会出现冲突--><exclusion><groupId>io.seata</groupId><artifactId>seata-all</artifactId></exclusion></exclusions></dependency><!--引入指定版本的seata-all--><dependency><groupId>io.seata</groupId><artifactId>seata-all</artifactId><version>0.9.0</version></dependency><!--引入nacos-starter nacos的场景启动器starter --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-alibaba-nacos-discovery</artifactId></dependency><!--引入web-starter 说明我们使用版本仲裁(从父项目继承了版本)--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><!--引入mybatis-starter 整合到springboot--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!--引入druid-starter--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><!--这里需要我们指定版本, 因为父项目没有--><version>1.1.17</version></dependency><!--引入mysql依赖,使用版本仲裁--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--spring-boot-start-jdbc引入--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--引入test-starter--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!--引入公共模块--><dependency><groupId>com.wyxedu.springcloud</groupId><artifactId>e_commerce_center-common-api</artifactId><version>${project.version}</version></dependency></dependencies>

创建application.yml

进行相关的配置

server:port: 10010
spring:application:name: seata-storage-micro-servicecloud:alibaba:seata:#指定事务组名,需要和seata-server中的对应 /conf/file.conftx-service-group: wyx_order_tx_groupnacos:discovery:server-addr: localhost:8848 #指定nacos server地址datasource:driver-class-name: com.mysql.jdbc.Driver#注意数据库要改对应的数据库url: jdbc:mysql://localhost:3306/storage_micro_serviceusername: rootpassword: 自己的密码
#配置seata日志输出
logging:level:io:seata: infomybatis:mapperLocations: classpath:mapper/*.xml

创建conf文件

resources目录下

  • 创建file.conf, 进行相关的配置, 说明:该文件从seata 的\conf\file.conf 拷贝,进行修改即可 注意因为我们前面已经修改了所以这里可以不需要修改之际拷贝即可

  • 创建registry.conf, 进行相关的配置, 说明:该文件从seata 的\conf\registry.conf 拷贝,进行修改即可 注意因为我们前面已经修改了所以这里可以不需要修改之际拷贝即

创建/entity/Storage

/entity/Storage.java

/*** Storage 实体类对应 storage表*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Storage {private Long id;private Long productId;private Integer amount;
}

创建StorageDao

/dao/StorageDao.java接口

@Mapper
public interface StorageDao {//扣减库存信息void reduce(@Param("productId") Long productId, @Param("nums") Integer nums);
}

注意这里使用; @Param注解指定一下为好 防止不识别

创建StorageMapper

resources/mapper/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.wyxedu.springcloud.dao.StorageDao"><resultMap id="BaseResultMap" type="com.springcloud.entity.Storage"><id column="id" property="id" jdbcType="BIGINT"/><result column="product_id" property="productId" jdbcType="BIGINT"/><result column="amount" property="amount" jdbcType="INTEGER"/></resultMap><!-- 减少库存 --><update id="reduce">UPDATEstorageSETamount = amount - #{nums}WHEREproduct_id = #{productId}</update></mapper>

创建StorageService

service/StorageService.java接口

public interface StorageService {// 扣减库存void reduce(Long productId, Integer nums);
}

创建StorageServiceImpl

service/impl/StorageServiceImpl.java

@Slf4j
@Service
public class StorageServiceImpl implements StorageService {@Resourceprivate StorageDao storageDao;@Overridepublic void reduce(Long productId, Integer nums) {log.info("==========seata_storage_micro_service-10010 扣减库存 start==========");storageDao.reduce(productId, nums);log.info("==========seata_storage_micro_service-10010 扣减库存 end==========");}
}

创建StorageController

controller/StorageController.java

@RestController
public class StorageController {@Resourceprivate StorageService storageService;//扣减库存@PostMapping("/storage/reduce")public Result reduce(@RequestParam("productId") Long productId,@RequestParam("nums") Integer nums) {storageService.reduce(productId, nums);return Result.success("扣减库存成功ok", null);}}

创建MyBatisConfig

config/MyBatisConfig

/*** 常规配置 Mybatis 和 dao关联*/
@Configuration
@MapperScan({"com.springcloud.dao"})
public class MyBatisConfig {
}

创建DataSourceProxyConfig

config/DataSourceProxyConfig常规配置(拿来使用即可)

注意DataSourceProxy 是引入的 io.seata.rm.datasource不要引入错了


/*** 说明* 1. 这里很重要: 配置数据源的代理是seata 也就是使用seata代理数据源* 2. DataSourceProxy 是引入的 io.seata.rm.datasource*/
@Configuration
public class DataSourceProxyConfig {@Value("${mybatis.mapperLocations}")private String mapperLocations;//配置druidDataSource@Bean@ConfigurationProperties(prefix = "spring.datasource")public DataSource druidDataSource() {return new DruidDataSource();}//配置DataSourceProxy- 使用seata代理数据源@Beanpublic DataSourceProxy dataSourceProxy(DataSource dataSource) {return new DataSourceProxy(dataSource);}//配置SqlSessionFactory-常规写法@Beanpublic SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy)throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean =new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSourceProxy);sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());return sqlSessionFactoryBean.getObject();}}

创建主启动类

SeataStorageMicroServiceApplication10010.java

//注意: 需要取消数据源的自动配置
//而是使用seata 代理数据源, DataSourceProxy
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataStorageMicroServiceApplication10010 {public static void main(String[] args) {SpringApplication.run(SeataStorageMicroServiceApplication10010.class,args);}
}

测试seata_storage_micro_service-10010 微服务

  1. 启动Nacos Server 8848

  2. 双击Seata 的\bin\seata-server.bat , 启动Seata Server

  3. 启动seata_storage_micro_service-10010

  4. 登录Nacos Server , 查看10010 微服务是否注册成功

    4. 1. 登录Nacos Server, 查看10010 是否注册成功

image.png

开发seata_account_micro_service-10012 微服务

  1. 参考以前的方式,创建seata_account_micro_service-10012 微服务模块

  2. 修改pom.xml, 添加相关的jar 依赖 和1010端口微服务一模一样

创建application.yml

进行相关的配置

server:port: 10012
spring:application:name: seata-account-micro-servicecloud:alibaba:seata:#指定事务组名,需要和seata-server中的对应 /conf/file.conftx-service-group: wyxedu_order_tx_groupnacos:discovery:server-addr: localhost:8848 #指定nacos server地址datasource:driver-class-name: com.mysql.jdbc.Driver#注意数据库要改对应的数据库url: jdbc:mysql://localhost:3306/account_micro_serviceusername: rootpassword: 自己的密码
#配置seata日志输出
logging:level:io:seata: infomybatis:mapperLocations: classpath:mapper/*.xml

创建conf文件

resources目录下

  • 创建file.conf, 进行相关的配置, 说明:该文件从seata 的\conf\file.conf 拷贝,进行修改即可 注意因为我们前面已经修改了所以这里可以不需要修改之际拷贝即可

  • 创建registry.conf, 进行相关的配置, 说明:该文件从seata 的\conf\registry.conf 拷贝,进行修改即可 注意因为我们前面已经修改了所以这里可以不需要修改之际拷贝即可

创建Account

com/springcloud/entity/Account.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {private Long id;private Long userId;private Integer money;}

创建AccountDao

com/springcloud/dao/AccountDao.java接口

@Mapper
public interface AccountDao {void reduce(@Param("userId") Long userId, @Param("money") Integer money);
}

创建AccountMapper

resources/mapper/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.wyx.springcloud.dao.AccountDao"><resultMap id="BaseResultMap" type="com.wyxedu.springcloud.entity.Account"><id column="id" property="id" jdbcType="BIGINT"/><result column="user_id" property="userId" jdbcType="BIGINT"/><result column="money" property="money" jdbcType="INTEGER"/></resultMap><!-- 扣减金额用户account表的money --><update id="reduce">UPDATE accountSETmoney = money - #{money}WHEREuser_id = #{userId};</update></mapper>

创建AccountService

com/springcloud/service/AccountService.java接口

public interface AccountService {//扣减用户的moneyvoid reduce(Long userId, Integer money);
}

AccountServiceImpl

com/springcloud/service/impl/AccountServiceImpl.java

@Service
@Slf4j
public class AccountServiceImpl implements AccountService {@ResourceAccountDao accountDao;@Overridepublic void reduce(Long userId, Integer money) {log.info("========seata_account_micro_service-10012 扣减账户余额 start ======");accountDao.reduce(userId, money);log.info("========seata_account_micro_service-10012 扣减账户余额 end ======");}
}

创建AccountController

com/springcloud/controller/AccountController.java

@RestController
public class AccountController {@ResourceAccountService accountService;/*** 扣减账户余额*/@PostMapping("/account/reduce")public Result reduce(@RequestParam("userId") Long userId, @RequestParam("money") Integer money){accountService.reduce(userId,money);return Result.success("200", "扣减账户余额OK");}
}

创建MyBatisConfig

//常规配置 Mybatis 和 dao关联
@Configuration
@MapperScan({"com.wyxedu.springcloud.dao"})
public class MyBatisConfig {
}

创建DataSourceProxyConfig

config/DataSourceProxyConfig常规配置(拿来使用即可)

注意DataSourceProxy 是引入的 io.seata.rm.datasource不要引入错了

/*** 说明* 1. 这里很重要: 配置数据源的代理是seata 也就是使用seata代理数据源* 2. DataSourceProxy 是引入的 io.seata.rm.datasource*/
@Configuration
public class DataSourceProxyConfig {@Value("${mybatis.mapperLocations}")private String mapperLocations;//配置druidDataSource@Bean@ConfigurationProperties(prefix = "spring.datasource")public DataSource druidDataSource() {return new DruidDataSource();}//配置DataSourceProxy- 使用seata代理数据源@Beanpublic DataSourceProxy dataSourceProxy(DataSource dataSource) {return new DataSourceProxy(dataSource);}//配置SqlSessionFactory-常规写法@Beanpublic SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy)throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean =new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSourceProxy);sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());return sqlSessionFactoryBean.getObject();}}

创建主启动类

springcloud/SeataAccountMicroServiceApplication10012

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableFeignClients
@EnableDiscoveryClient
public class SeataAccountMicroServiceApplication10012 {public static void main(String[] args) {SpringApplication.run(SeataAccountMicroServiceApplication10012.class,args);}
}

测试seata_storage_micro_service-10012 微服务

  1. 启动Nacos Server 8848

  2. 双击Seata 的\bin\seata-server.bat , 启动Seata Server

  3. 启动seata_storage_micro_service-10012

  4. 登录Nacos Server , 查看10010 微服务是否注册成功

    4. 1. 登录Nacos Server, 查看10012 是否注册成功

image.png

开发seata-order-micro-service-10008 微服务

  1. 参考以前的方式,创建seata-order-micro-service-10008 微服务模块

修改pom.xml

添加相关的jar 依赖

和10012 微服务一模一样

创建application.yml

进行相关的配置

server:port: 10008
spring:application:name: seata-order-micro-servicecloud:alibaba:seata:#指定事务组名,需要和seata-server中的对应 /conf/file.conftx-service-group: wyxedu_order_tx_groupnacos:discovery:server-addr: localhost:8848 #指定nacos server地址datasource:driver-class-name: com.mysql.jdbc.Driver#注意数据库要改对应的数据库url: jdbc:mysql://localhost:3306/order_micro_serviceusername: rootpassword: 自己的密码
#配置seata日志输出
logging:level:io:seata: infomybatis:mapperLocations: classpath:mapper/*.xml

创建conf文件

resources目录下

  • 创建file.conf, 进行相关的配置, 说明:该文件从seata 的\conf\file.conf 拷贝,进行修改即可 注意因为我们前面已经修改了所以这里可以不需要修改之际拷贝即可

  • 创建registry.conf, 进行相关的配置, 说明:该文件从seata 的\conf\registry.conf 拷贝,进行修改即可 注意因为我们前面已经修改了所以这里可以不需要修改之际拷贝即可

创建Order

com/springcloud/entity/Order.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {private Long id;private Long userId;private Long productId;private Integer nums;private Integer money;private Integer status;}

创建OrderDao

com/springcloud/dao/OrderDao.java接口

@Mapper
public interface OrderDao {//新建订单void save(Order order);//修改订单状态void update(@Param("userId") Long userId, @Param("status") Integer status);}

创建OrderMapper

resources/mapper/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.wyx.springcloud.dao.OrderDao"><resultMap id="BaseResultMap" type="com.wyxedu.springcloud.entity.Order"><id column="id" property="id" jdbcType="BIGINT"/><result column="user_id" property="userId" jdbcType="BIGINT"/><result column="product_id" property="productId" jdbcType="BIGINT"/><result column="nums" property="nums" jdbcType="INTEGER"/><result column="money" property="money" jdbcType="INTEGER"/><result column="status" property="status" jdbcType="INTEGER"/></resultMap><!--配置实现save方法 添加订单 --><insert id="save">insert into `order` (id,user_id,product_id,nums,money,status)values (null,#{userId},#{productId},#{nums},#{money},0);</insert><!--配置实现update- 修改订单状态 这里写的比较简单,实际工作中根据业务需求编写即可--><update id="update">update `order` set status = 1where user_id=#{userId} and status = #{status};</update>
</mapper>

创建OrderService

com/springcloud/service/OrderService.java接口

public interface OrderService {void save(Order order);
}

创建AccountService

创建com/springcloud/service/AccountService.java接口

@FeignClient(value = "seata-account-micro-service")
public interface AccountService {/*** 解读* 1. 远程调用方式是 post* 2. 远程调用的url 为 http://seata_account_micro_service/account/reduce* 3. seata_account_micro_service是nacos注册中心服务名* 4. openfeign是通过接口方式调用服务*//*** 扣减账户余额*/@PostMapping("/account/reduce")public Result reduce(@RequestParam("userId") Long userId,@RequestParam("money") Integer money);
}

创建StorageService

创建com/springcloud/service/StorageService.java接口

@FeignClient(value = "seata-storage-micro-service")
public interface StorageService {/*** 解读* 1. 远程调用方式是 post* 2. 远程调用的url 为 http://seata_storage_micro_service/storage/reduce* 3. seata_storage_micro_service是nacos注册中心服务名* 4. openfeign是通过接口方式调用服务*///扣减库存@PostMapping("/storage/reduce")public Result reduce(@RequestParam("productId") Long productId,@RequestParam("nums") Integer nums);
}

创建OrderServiceImpl

com/springcloud/service/impl/OrderServiceImpl.java


@Service
@Slf4j
public class OrderServiceImpl implements OrderService {@Resourceprivate OrderDao orderDao;@Resourceprivate StorageService storageService;@Resourceprivate AccountService accountService;/*** 创建订单->调用库存服务扣减库存->* 调用账户服务扣减账户余额->修改订单状态*/@Overridepublic void save(Order order) {log.info("=========开始新建订单start ==========");//新建订单orderDao.save(order);System.out.println("order=" + order);log.info("=========减库存start ==========");storageService.reduce(order.getProductId(), order.getNums());log.info("=========减库存end ==========");log.info("=========减账户金额start ==========");accountService.reduce(order.getUserId(), order.getMoney());log.info("=========减账户金额end ==========");log.info("=========修改订单状态start ==========");orderDao.update(order.getUserId(), 0);log.info("=========修改订单状态end ==========");log.info("=========下订单end==========");}
}

创建OrderController

com/springcloud/controller/OrderController.java

@RestController
public class OrderController {@Resourceprivate OrderService orderService;/*** 提醒 * 1. 我们的前端如果是以json格式来发送添加信息Order, 那么我们需要使用@RequestBody*     才能将数据封装到对应的bean, 同时保证http的请求头的 content-type是对应* 2. 如果前端是以表单形式提交了,则不需要使用@RequestBody, 才会进行对象参数封装, 同时保证*      http的请求头的 content-type是对应*/@GetMapping("/order/save")public Result save(Order order) {orderService.save(order);return Result.success("订单创建成功", null);}
}

创建MyBatisConfig

//常规配置 Mybatis 和 dao关联
@Configuration
@MapperScan({"com.wyxedu.springcloud.dao"})
public class MyBatisConfig {
}

创建DataSourceProxyConfig

config/DataSourceProxyConfig常规配置(拿来使用即可)

注意DataSourceProxy 是引入的 io.seata.rm.datasource不要引入错了

/*** 说明* 1. 这里很重要: 配置数据源的代理是seata 也就是使用seata代理数据源* 2. DataSourceProxy 是引入的 io.seata.rm.datasource*/
@Configuration
public class DataSourceProxyConfig {@Value("${mybatis.mapperLocations}")private String mapperLocations;//配置druidDataSource@Bean@ConfigurationProperties(prefix = "spring.datasource")public DataSource druidDataSource() {return new DruidDataSource();}//配置DataSourceProxy- 使用seata代理数据源@Beanpublic DataSourceProxy dataSourceProxy(DataSource dataSource) {return new DataSourceProxy(dataSource);}//配置SqlSessionFactory-常规写法@Beanpublic SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy)throws Exception {SqlSessionFactoryBean sqlSessionFactoryBean =new SqlSessionFactoryBean();sqlSessionFactoryBean.setDataSource(dataSourceProxy);sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));sqlSessionFactoryBean.setTransactionFactory(new SpringManagedTransactionFactory());return sqlSessionFactoryBean.getObject();}}

创建主启动类

springcloud/SeataAccountMicroServiceApplication10008

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableDiscoveryClient
@EnableFeignClients
public class SeataOrderMicroServiceApplication10008 {public static void main(String[] args) {SpringApplication.run(SeataOrderMicroServiceApplication10008.class,args);}
}

测试seata_storage_micro_service-10008 微服务

  1. 启动Nacos Server 8848
  2. 双击Seata 的\bin\seata-server.bat , 启动Seata Server
  3. 启动seata_storage_micro_service-10008
  4. 登录Nacos Server , 查看10010 微服务是否注册成功

4. 1. 登录Nacos Server, 查看10008 是否注册成功

image.png

集成测试(1) 三个微服务协同完成-正常下单

  1. 启动Nacos Server 8848

  2. 双击Seata 的\bin\seata-server.bat , 启动Seata Server

  3. 启动seata-order-micro-service-10010 /10012/10008 三个微服务

  4. 浏览器: http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100
    1.

  5. 查看数据库/表的情况是否正常, 结论:如果没有异常出现,正常下单,数据库三张表数据一致性是OK 的

image.png

注意事项和细节

  1. MySQL 出现too many connections(1040)错误
解决方法在my.ini 设置
max\_connections=1000
  1. 如果出现: service id not legal hostname报错Service id not legal hostname 的原因是服务名称不能带有下划线,可以使用中划线,springcloud 无法识别下划线,把下划线改成中划线就好

集成测试(2) 三个微服务协同完成-模拟异常

  1. 启动Nacos Server 8848
  2. 双击Seata 的\bin\seata-server.bat , 启动Seata Server
  3. 启动seata-order-micro-service-10010 /10012/10008 三个微服务
  4. 浏览器: http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100

修改seata_account_micro_service-10012 的com/springcloud/controller/AccountController.java, 模拟异常出现

@RestController
public class AccountController {@ResourceAccountService accountService;/*** 扣减账户余额*/@PostMapping("/account/reduce")public Result reduce(@RequestParam("userId") Long userId, @RequestParam("money") Integer money){// 模拟异常,超时//openfeign 接口调用默认超时时间为1stry {TimeUnit.SECONDS.sleep(12);} catch (InterruptedException e) {e.printStackTrace();}accountService.reduce(userId,money);return Result.success("200", "扣减账户余额OK");}
}
  1. 浏览器:http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100

image.png

  1. 查看数据库/表的情况是否正常, 结论:这时数据库/表,出现数据不一致现象, 订单是未支付,但是库存减少了,账号钱也扣了(提示: 等休眠时间完成后,再查看account 表,会看到数据不一致.)

集成测试(3)使用@GlobalTransactional

三个微服务协同完成-使用@GlobalTransactional完成分布式事务控制(出现异常,也能保证数据一致性)

  1. 启动Nacos Server 8848

  2. 双击Seata 的\bin\seata-server.bat , 启动Seata Server

  3. 启动seata-order-micro-service-10010 /10012/10008 三个微服务

  4. 浏览器: http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100

  5. 修改seata_account_micro_service-10012 的com/springcloud/controller/AccountController.java, 模拟异常出现

@RestController
public class AccountController {@ResourceAccountService accountService;/*** 扣减账户余额*/@PostMapping("/account/reduce")public Result result(@RequestParam("userId") Long userId, @RequestParam("money")Integer money){//模拟异常, 超时,或者int i = 9 / 0;//openfeign 接口调用默认超时时间为1s//说明1. 也可以使用其它方式模拟异常, 但在Debug 看Seata 分布式事务机制不方便, 不好看效果, 所以这里我们使用超时异常//说明2. 因为是超时异常, 所以在Debug 分析Seata 机制时, 可能会发现某张表被锁几条记录, 因为seata 会做最终一致性操作(即尝试再提交上次超时的事务).try {TimeUnit.SECONDS.sleep(12);} catch (InterruptedException e) {e.printStackTrace();}accountService.reduce(userId,money);return Result.success("200", "扣减账户余额OK");}
}
  1. 修改seata-order-micro-service-10008 的com/springcloud/service/impl/OrderServiceImpl.java
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {@Resourceprivate OrderDao orderDao;@Resourceprivate StorageService storageService;@Resourceprivate AccountService accountService;@Override/*** 解读* 1. @GlobalTransactional : 分布式全局事务控制  io.seata.spring.annotation包* 2. name = "wyxedu-save-order" 名称,程序员自己指定,保证唯一即可* 3. rollbackFor = Exception.class 指定发送什么异常就回滚, 这里我们指定的是Exception.class*    即 只要发生了异常就回滚*/@GlobalTransactional(name = "wyxedu-save-order", rollbackFor = Exception.class)public void save(Order order) {//后面我们如果需要打印日志log.info("====创建订单 start=====");log.info("====本地生成订单 start===");orderDao.save(order);//调用本地方法生成订单orderlog.info("====本地生成订单 end===");log.info("====扣减库存 start===");//远程调用storage微服务扣减库存storageService.reduce(order.getProductId(), order.getNums());log.info("====扣减库存 end===");log.info("====扣减用户余额 start===");//远程调用account微服务扣减用户moneyaccountService.reduce(order.getUserId(), order.getMoney());log.info("====扣减用户余额 end===");log.info("====本地修改订单状态 start===");//调用本地方法修改订单状态0->1orderDao.update(order.getUserId(), 0);log.info("====本地修改订单状态 end===");log.info("====创建订单 end=====");}
}
  1. 重启seata-order-micro-service-10008

浏览器:http://localhost:10008/order/save?userId=666&productId=1&nums=1&money=100

image.png
在数据库就可以看到数据回滚了为什么可以呢我们下面详细说

注意事项和细节

如果数据库/表使用到关键字,需要使用反引号

举例说明:

比如mapper/OrderMapper.xml , 这里的order 就要使用``, 否则会报错

<insert id="save">insert into `order` (id,user_id,product_id,nums,money,status)values (null,#{userId},#{productId},#{nums},#{money},0);
</insert>
<update id="update">update `order` set status = 1where user_id=#{userId} and status = #{status};
</update>

openfeign 在远程调用api 接口时, 默认超时时间为1s

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/253989.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

chatgpt赋能python:Python中的字符提取:从基础到高级

Python中的字符提取&#xff1a;从基础到高级 在使用Python进行文本处理和数据挖掘时&#xff0c;我们经常需要从字符串中提取特定的字符或子串。本文将介绍Python中的常用字符串提取方法&#xff0c;包括基础的字符串操作、正则表达式和第三方库等高级方法。 基础字符串操作…

chatgpt赋能python:Python中用什么表示空值?

Python中用什么表示空值&#xff1f; 在Python编程中&#xff0c;我们经常会遇到处理空值的场景。空值通常表示缺失的或未定义的值&#xff0c;这在数据处理和分析中尤其常见。那么&#xff0c;在Python中&#xff0c;究竟用什么来表示空值呢&#xff1f; None 在Python中&a…

ENU、EPSG坐标系科普(三维重建)

ENU和EPSG实际上代表了两个不同的概念&#xff0c;这两者并不是直接对比的。 1. ENU坐标系&#xff1a;ENU坐标系是一种本地切面坐标系&#xff0c;用于表示与地理位置相关的空间数据。在ENU坐标系中&#xff0c;E代表东&#xff08;East&#xff09;&#xff0c;N代表北&…

ps中给图层新建文件夹

快捷键&#xff1a;CtrlG 或者点击菜单中–图层–新建–组

3.photoshop 图层的创建与管理

xmind: https://github.com/wangtao-luse/static

高速缓存(cache)的原理: 了解计算机架构与性能优化

计基之存储器层次结构 Author&#xff1a; Once Day Date&#xff1a; 2023年5月9日 长路漫漫&#xff0c;而今才刚刚启程&#xff01; 本内容收集整理于《深入理解计算机系统》一书。 参看文档: 捋一捋Cache - 知乎 (zhihu.com)iCache和dCache一致性 - 知乎 (zhihu.com)C…

chatgpt赋能python:Python中拷贝的介绍

Python 中拷贝的介绍 在 Python 中&#xff0c;拷贝是一个十分常见而且必要的操作。拷贝可以在许多情况下被使用&#xff0c;例如在创建测试数据、编写一个新的算法时&#xff0c;或者是在处理多维数据结构的程序中。由于 Python 中的对象是动态类型的&#xff0c;因此在拷贝时…

色情版“微信”背后的秘密

作者&#xff1a;暗影安全实验室 来源&#xff1a;https://www.anquanke.com/post/id/219729 背景&#xff1a;近日&#xff0c;恒安嘉新暗影安全实验室平台监测到一款名为“乐宝”的仿冒应用&#xff0c;安全研究人员第一时间对该应用进行了研究分析&#xff0c;发现该应用表面…

微信的秘密-python可视化微信好友信息

记得2016年第一次开通微信的时候&#xff0c;我以及周围的大多数人还是重度的QQ用户&#xff0c;当时只是跟风开通了一下&#xff0c;也没觉得会改变什么。没想到才两年过去&#xff0c;我已经忘记了QQ的存在&#xff0c;每天起来第一件事就是查看微信&#xff0c;睡觉前也必然…

微信号的秘密...

“听说&#xff0c;微信可以改微信号了&#xff01;” 不知道谁扯了一嗓子&#xff0c;让办公室变成了欢乐的海洋 张姐流下了激动的泪水&#xff1a;“太好了&#xff01;姐的年龄终于不用暴露在微信号了&#xff01;” “一朝自黑&#xff0c;朝朝自黑” 的王总变得更加权威 .…

好友国自然基金获批!畅饮之余透露给我这个秘密

之前自己也是羡慕科研新星冉冉升起&#xff0c; 后来沉下心来学习其发展之路。 只有找到自己的创新点&#xff0c; 持续在该领域发SCI论文&#xff0c; 才能把工作基础转化为基金资助。 其关键&#xff0c; 是高效利用优质资源&#xff0c; 途径就是靠谱的优质公众号。 下面是好…

用 python 分析了微信上所有的微信好友,发现了一个秘密...

最近研究了一下itchat和matplotlib&#xff0c;目前实现了对微信好友头像、性别、区域、个性签名的采集及展示。 本文就来详细介绍一下这个库的用法和一些核心逻辑实现。 1.微信登录 三行代码实现登录&#xff0c;为了避免我们频繁扫描二维码登录&#xff0c;这里我们加入ho…

超简单的方法找出QQ共同好友

先说两句废话&#xff0c;今天和小伙伴在看空间的小秘密&#xff0c;惊讶的发现有个共同的小秘密&#xff0c;就想把他找出来hhh&#xff0c;然后就有了这一步 1. 获取数据 先打开网页版的QQ邮箱&#xff0c;并且进入写信界面&#xff0c;然后&#xff01;&#xff01;&#…

我用 Python 爬取微信好友,最后发现一个大秘密

前言 你身处的环境是什么样&#xff0c;你就会成为什么样的人。现在人们日常生活基本上离不开微信&#xff0c;但微信不单单是一个即时通讯软件&#xff0c;微信更像是虚拟的现实世界。你所处的朋友圈是怎么样&#xff0c;慢慢你的思想也会变的怎么样。最近在学习 itchat&#…

用 Python 分析了所有微信好友,发现了一个秘密...

点击上方“何俊林”&#xff0c;马上关注&#xff0c;每天早上8:50准时推送 真爱&#xff0c;请置顶或星标 Illustrations by Evgenij Kungur 文/ Python攻城狮 最近研究了一下itchat和matplotlib&#xff0c;目前实现了对微信好友头像、性别、区域、个性签名的采集及展示。 本…

chatgpt赋能python:Python中画笔放下:掌握Python图形编程

Python 中画笔放下&#xff1a;掌握 Python 图形编程 Python 是一种高级编程语言&#xff0c;广泛应用于数据处理、人工智能、Web 应用程序等领域。除了这些应用外&#xff0c;Python 还可以用于图形编程&#xff0c;包括绘制 2D 和 3D 图形、创建游戏和交互式应用程序等。在 …

使用Python处理PDF文件的简介与实践

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

影驰 H610M 光影 评测

影驰 H610M 光影 针对英特尔 12 代酷睿处理器设计&#xff0c;采用了 H610 芯片组&#xff0c;造型简洁&#xff0c;面向入门级用户推出。影驰 H610M 光影组装电脑怎么搭配更合适这些点很重要http://www.adiannao.cn/du 主板采用 M-ATX 版型&#xff0c;供电部分没有覆盖散热片…

影驰 H610M 光影主板 评测

影驰宣布 H610M 光影主板首发价格为 599 元。这款主板针对英特尔 12 代酷睿处理器设计&#xff0c;采用了 H610 芯片组&#xff0c;造型简洁&#xff0c;面向入门级用户推出。 主板采用 M-ATX 版型&#xff0c;供电部分没有覆盖散热片。产品提供两个内存插槽&#xff0c;支持 D…

影驰gtx960显卡怎么样_影驰gtx960 2g_影驰gtx960 2g跑分

刚到手的二手GTX960影驰虎将跑分 割版的GTX960 2G显卡&#xff0c;游戏性能只是和GTX960/1050相当。如果你只是用来玩网游戏和一般的单机游戏的话&#xff0c;倒是够用&#xff0c; 否则的话&#xff0c;建议购买二手的4G版的GTX960显卡&#xff0c;这样就可以在1080P下玩大多…