Spring Cloud:Greenwich.RELEASE
Spring Cloud Alibaba:2.1.0.RELEASE
Spring boot: 2.1.3.RELEASE
Mybatis-Plus: 3.4.2
Nacos:2.2.0 官网 https://nacos.io/zh-cn/index.html
seata:0.9.0 官网 http://seata.io/zh-cn/index.html
Swagger:2.9.2
安装Nacos 下载下载地址: https://github.com/alibaba/nacos/releases
下载zip格式的安装包,然后进行解压缩操作
启动#切换目录 cd nacos/bin #命令启动 startup.cmd -m standalone访问nacos
打开浏览器输入 http://localhost:8848/nacos ,即可访问服务, 默认密码是 nacos/nacos
注意刚进入事是没有这些配置的(我是搭建完后截的图,这些配置后边会提到)
搭建微服务项目项目结构:
一共两个微服务,order通过product-service-client 提供的feign 接口调用 product 中的服务
公司项目一般也是这种结构被调用的服务提供一个客户端项目依赖,调用方依赖这个客户端项目实现服务调用。
父项目pom.xmlorder-service 微服务pom.xml4.0.0 com.xuweichao springcloudalibaba-seatapom 1.0-SNAPSHOT order-service product-service springcloudalibaba-seata http://www.example.com org.springframework.boot spring-boot-starter-parent2.1.3.RELEASE 1.8 UTF-8 UTF-8 Greenwich.RELEASE 2.1.0.RELEASE cn.hutool hutool-all5.7.7 org.projectlombok lombok1.18.2 io.springfox springfox-swagger22.9.2 io.springfox springfox-swagger-ui2.9.2 org.springframework.cloud spring-cloud-dependencies${spring-cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies${spring-cloud-alibaba.version} pom import
product-service-client pom.xmlspringcloudalibaba-seata com.xuweichao 1.0-SNAPSHOT 4.0.0 order-servicecom.xuweichao product-service-client1.0-SNAPSHOT org.springframework.boot spring-boot-starter-webmysql mysql-connector-java5.1.6 com.baomidou mybatis-plus-boot-starter3.4.2 com.baomidou mybatis-plus-extension3.3.0 compile com.alibaba.cloud spring-cloud-starter-alibaba-nacos-configcom.alibaba.cloud spring-cloud-alibaba-nacos-discoverycom.alibaba.cloud spring-cloud-starter-alibaba-seataorg.apache.maven.plugins maven-compiler-plugin1.8 1.8 src/main/java ***.xml
product-service com.xuweichao 1.0-SNAPSHOT 4.0.0 com.xuweichao product-service-clientjar com.baomidou mybatis-plus-annotation3.3.0 compile com.baomidou mybatis-plus-extension3.3.0 compile org.springframework.cloud spring-cloud-starter-openfeign
数据库建表:订单表 产品表
CREATE TABLE `order_base` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_name` varchar(10) DEFAULT NULL, `p_id` bigint(20) DEFAULT NULL, `p_name` varchar(50) DEFAULT NULL, `p_price` double(10,2) DEFAULT NULL, `number` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 CREATE TABLE `product_base` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(100) DEFAULT NULL, `price` double(10,2) DEFAULT NULL, `stock` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4
使用 代码生成工具生成数据库试题对象和 service
order-service 微服务 添加配置 application.yml
spring:
application:
name: order-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
password: xxx
url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/database-name?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
server:
port: 18080
swagger:
enable: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
product-service 微服务 添加配置 application.yml
spring:
application:
name: product-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
password: xxx
url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/database-name?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
server:
port: 18080
swagger:
enable: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
product-service-server 添加接口:
@Slf4j
@Api(value = "Productbase 相关接口", tags = "Productbase 相关接口")
@RestController
@RequestMapping("product")
public class ProductbaseController implements ProductServiceClient {
@Autowired
public IProductbaseService productbaseService;
@Override
@ApiOperation(value = "详情")
@GetMapping("detail/{id}")
public Productbase getProductbase(@PathVariable Long id) {
log.info("获取的参数:===>>" + id);
return productbaseService.getById(id);
}
@Override
@ApiOperation(value = "修改库存")
@GetMapping("{id}")
public Boolean productUpdateStock(@PathVariable Long id) {
log.info("获取的参数:===>>" + id);
Productbase productbase = productbaseService.getById(id);
productbase.setStock(productbase.getStock()-1);
return productbaseService.updateById(productbase);
}
}
在product-service-client 添加 Feign 服务接口
@Component
@FeignClient(name = "product-service",path = "/product/")
public interface ProductServiceClient {
@GetMapping("detail/{id}")
Productbase getProductbase(@PathVariable("id") Long id);
@GetMapping("/{id}")
Boolean productUpdateStock(@PathVariable("id") Long id);
}
order-service 中创建接口
@Slf4j
@Api(value = "Orderbase 相关接口", tags = "Orderbase 相关接口")
@RestController
@RequestMapping("order")
public class OrderbaseController {
@Resource
public IOrderbaseService orderbaseService;
@ApiOperation(value = "添加", notes = "添加")
@GetMapping("{pid}")
public Boolean orderbaseSave(@PathVariable Long pid) {
log.info("获取的参数:===>>" + pid);
return orderbaseService.createOrder(pid);
}
}
OrderServiceImpl:
@Service public class OrderbaseServiceImpl extends ServiceImplimplements IOrderbaseService { @Autowired private ProductServiceClient productServiceClient; @Override public Boolean createOrder(Long pid) { Productbase productbase = productServiceClient.getProductbase(pid); Orderbase orderbase=new Orderbase(); orderbase.setNumber(2); orderbase.setPId(productbase.getId()); orderbase.setPName(productbase.getName()); orderbase.setPPrice(productbase.getPrice()); orderbase.setUserName("超超"); this.save(orderbase); productServiceClient.productUpdateStock(orderbase.getPId()); return Boolean.TRUE; } }
这里是模拟的创建订单和减库存的操作
安装Seata 下载下载地址: https://github.com/seata/seata/releases/v0.9.0/
进入conf 目录修改配置文件register.conf,我们这里是用nacos 作为注册和配置中心的,所以精简一下配置
registry {
type = "nacos"
nacos {
serverAddr = "localhost"
namespace = "public"
cluster = "default"
}
}
config {
type = "nacos"
nacos {
serverAddr = "localhost"
namespace = "public"
cluster = "default"
}
}
初始Seata配置
进入 seata-0.9.0serversrcmainresources 目录 找到 nacos-config.txt 文件
第一个箭头哪里代表事务组
这里的语法为: service.vgroup_mapping.${your - service - gruop}=default ,中间的 ${your - service - gruop} 为自己定义的服务组名称, 这里需要我们在程序的配置文件中配置。
store 那里改成自己的数据源配置,并建表
-- the table to store GlobalSession data DROP TABLE IF EXISTS `global_table`; CREATE TABLE `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_gmt_modified_status` (`gmt_modified`, `status`), KEY `idx_transaction_id` (`transaction_id`) ); -- the table to store BranchSession data DROP TABLE IF EXISTS `branch_table`; CREATE TABLE `branch_table` ( `branch_id` BIGINT NOT NULL, `xid` VARCHAr(128) NOT NULL, `transaction_id` BIGINT , `resource_group_id` VARCHAr(32), `resource_id` VARCHAr(256) , `lock_key` VARCHAr(128) , `branch_type` VARCHAr(8) , `status` TINYINT, `client_id` VARCHAr(64), `application_data` VARCHAr(2000), `gmt_create` DATETIME, `gmt_modified` DATETIME, PRIMARY KEY (`branch_id`), KEY `idx_xid` (`xid`) ); -- the table to store lock data DROP TABLE IF EXISTS `lock_table`; CREATE TABLE `lock_table` ( `row_key` VARCHAr(128) NOT NULL, `xid` VARCHAr(96), `transaction_id` LONG , `branch_id` LONG, `resource_id` VARCHAr(256) , `table_name` VARCHAr(32) , `pk` VARCHAr(36) , `gmt_create` DATETIME , `gmt_modified` DATETIME, PRIMARY KEY(`row_key`) );
创建undo_log 表用于事务回滚
CREATE TABLE `undo_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `branch_id` bigint(20) NOT NULL, `xid` varchar(100) NOT NULL, `context` varchar(128) NOT NULL, `rollback_info` longblob NOT NULL, `log_status` int(11) NOT NULL, `log_created` datetime NOT NULL, `log_modified` datetime NOT NULL, `ext` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8# 初始化 seata 的 nacos 配置 # 注意 : 这里要保证 nacos 是已经正常运行的
cd conf nacos-config.sh 127.0.0.1
执行之后 在nacos 中就能看上最上边图中展示的效果了。
启动seata,进入 seata-server-0.9.0seatabin 目录执行
seata-server.bat -p 9000 -m file
将register.conf 文件分别拷贝到 order-service 和product-service-server 微服务 的resources 目录下
添加服务注册发现和 Seata事务组
分别在两个微服务resources目录下创建 bootstrap.yml
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
namespace: public
group: SEATA_GROUP
discovery:
server-addr: 127.0.0.1:8848
alibaba:
seata:
tx-service-group: product_service_group
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
namespace: public
group: SEATA_GROUP
discovery:
server-addr: 127.0.0.1:8848
alibaba:
seata:
tx-service-group: order_service_group
在 ProductApplication 和OrderApplication 中添加注解 @EnableDiscoveryClient 用户服务发现
并在OrderApplication 添加 @EnableFeignClients(basePackages = "com.xxx") 开启feign服务调用。
修改creatOrder()方法添加注解 @GlobalTransactional
@GlobalTransactional
@Override
public Boolean createOrder(Long pid) {
Productbase productbase = productServiceClient.getProductbase(pid);
Orderbase orderbase=new Orderbase();
orderbase.setNumber(2);
orderbase.setPId(productbase.getId());
orderbase.setPName(productbase.getName());
orderbase.setPPrice(productbase.getPrice());
orderbase.setUserName("超超");
this.save(orderbase);
productServiceClient.productUpdateStock(orderbase.getPId());
return Boolean.TRUE;
}
在服务调用方order-service中添加数据源代理配置
Seata 是通过代理数据源实现事务分支的,所以需要配置 io.seata.rm.datasource.DataSourceProxy 的 Bean ,且是 @Primary 默认的数据源,否则事务不会回滚,无法实现分布式事务@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Primary
@Bean
public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}
product-base 中加入两条数据
然后分别启动两个微服务,在nacos界面服务列表可以看到 服务都注册成功了
浏览器或者swagger执行
http://localhost:18080/order/1
可以看到执行成功
订单表添加成功,库存也减了。然后任务增加一个异常测试一下事务的回滚。
执行后可以看到订单和库存都没有成功。



