seata微服务的执行流程:
其中
-
TM(Transaction Manager):全局事务管理器,控制全局事务边界,负责全局事务开启、全局提交、全局回滚。
-
RM(Resource Manager):资源管理器,控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。
-
TC(Transaction Coordinator):事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
下边就以最原始典型的account--order---stroge 来展示一下这个seata微服务如何使用
首先使用seata必须要弄一个seata数据库,这里边存储了一些公共的信息。
然后我们需要修改一下seata文件的几个配置文件,分别是
这两个配置文件,里边分别配置了数据库以及服务的一些信息和注册的一些信息:
我们进去修改:
file.conf:
分别有三个地方需要修改:
第一个是service中的一个组名需要修改:这个字段名可以随意更改,但是他是和springcloud交互的一个相当于标记通道之类的东西。
第二个是模型(数据)读取以什么方式来读取,默认是file,我们需要改为db,因为我们要连接数据库嘛。
第三个就是数据库的配置了:
还有一个要改的文件就是registry.conf,听名字也能想出来他是一个注册的配置文件。
需要将注册的方式改为自己用的服务治理框架。在这里我们用nacos。
这两步都做好之后,我们就可以搭建项目的数据库了,数据库结构如下:
其中seata表的话是直接可以在下载的文件夹里找到store的sql文件执行就有的。别的需要自己创建。
Use seata_order;
CREATE TABLE t_order(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`user_id` BIGINT(11) DEFAULT NULL COMMENT '用户id',
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`count` INT(11) DEFAULT NULL COMMENT '数量',
`money` DECIMAL(11,0) DEFAULT NULL COMMENT '金额',
`status` INT(1) DEFAULT NULL COMMENT '订单状态:0:创建中; 1:已完结'
) ENGINE=INNODB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
SELECT * FROM t_order;
Use seata_storage;
CREATE TABLE t_storage(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`product_id` BIGINT(11) DEFAULT NULL COMMENT '产品id',
`'total` INT(11) DEFAULT NULL COMMENT '总库存',
`used` INT(11) DEFAULT NULL COMMENT '已用库存',
`residue` INT(11) DEFAULT NULL COMMENT '剩余库存'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO seata_storage.t_storage(`id`,`product_id`,`total`,`used`,`residue`)
VALUES('1','1','100','0','100');
SELECT * FROM t_storage;
Use seata_account;
CREATE TABLE t_account(
`id` BIGINT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'id',
`user_id` BIGINT(11) 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 '剩余可用额度'
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO seata_account.t_account(`id`,`user_id`,`total`,`used`,`residue`) VALUES('1','1','1000','0','1000')
之后我们就可以写各个微服务了。按照图示创建父子工程:
注意要将父工程pom内的打包方式写为pom;
然后导入夫工程的包管理依赖:如果你用的是maven创建工程的话还要继承springboot包。
<!--引入父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<properties>
<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>
<mysql.version>5.1.47</mysql.version>
<mybatis.spring.boot.version>1.3.2</mybatis.spring.boot.version>
</properties>
<!-- 子模块继承之后,提供作用:锁定版本+子模块不用写groupId和version -->
<dependencyManagement>
<dependencies>
<!-- springboot 2.2.2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring cloud Hoxton.SR1 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring cloud alibaba 2.1.0.RELEASE -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
之后在子工程order里边修改pom文件:注意seata的包版本要跟你自己的一致
<parent>
<groupId>com.jkt</groupId>
<artifactId>springcloud-seata</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- nacos -->
<!-- seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>0.9.0</version>
</dependency>
<!-- seata-->
<!--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.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.18</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配完pom相关的东西,我们就可以写配置文件:application.yaml
server:
port: 8005 #端口号
spring:
application:
name: seata-order-service # 服务名
cloud:
alibaba:
seata:
# 自定义事务组名称需要与seata-server中的对应
tx-service-group: jkt_tx_group
nacos:
discovery:
server-addr: localhost:8848 #将工程服务注册到nacos中
datasource:
# 当前数据源操作类型
type: com.alibaba.druid.pool.DruidDataSource
# mysql驱动类
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 476988
logging:
level:
io:
seata: info # 日志输出级别
mybatis:
mapper-locations: classpath:mapper/*.xml # 定义mybatis的xml文件映射
之后我们要把seata的两个配置文件引入到order微服务中。先看一下order的目录结构把:
这两个配置文件其中我们需要更改一下file.conf里边的服务内容,seata和springboot我理解为是一个服务传递。
seata里边fileconf配置service:vgroup_mapping.my_test_tx_group = "jkt_tx_group"
而springboot的fileconf配置service:
vgroup_mapping.jkt_tx_group = "default"
之后就是一些增删改查配置了:
在这里因为我们的seata需要链接数据库,所以我们不自动引入数据源,而选择自己配置数据源;
需要在config里边加入两个类,这两个类里边需要改一个东西:
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapper-locations}")
private String mapperLocations;
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druidDataSource() {
return new DruidDataSource();
}
@Bean
public DataSourceProxy dataSourceProxy(DataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSourceProxy dataSourceProxy) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSourceProxy);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(resolver.getResources(mapperLocations));
return bean.getObject();
}
}
@Configuration
@MapperScan({"com.jkt.dao"})
public class MyBatisConfig {
}
扫描对应的dao包。
还要在启动类上做一些注解:
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) // 排除自动导入数据源
@EnableFeignClients // 开启Feign远程微服务的注解
@EnableDiscoveryClient// 开启注册中心nacos的注解
public class SeateOrderApplication {
public static void main(String[] args) {
SpringApplication.run(SeateOrderApplication.class, args);
}
}
之后我们需要书写Feign的远程服务调用:
注意注解中的value是对应微服务的名字。
@FeignClient(value = "seata-storage-service")
public interface StorageFeign {
@RequestMapping("/storage/decrease")
public void decrease(@RequestParam("productId")Long productId, @RequestParam("count") Integer count);
}
@FeignClient(value = "seata-account-service")
public interface AccountFeign {
@RequestMapping("account/decrease")
public CommonResult decrease(@RequestParam("userId")Long userId, @RequestParam("money")BigDecimal money);
}
dao:
@Mapper
public interface OrderDao {
//创建订单
void create(Order order);
//修改订单状态
void update(@Param("userId") Long userId, @Param("status") Integer status);
}
entity:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message) {
this(code, message, null);
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order implements Serializable {
private Long id;
private Long userId;
private Long productId;
private Integer count;
private BigDecimal money;
private Integer status; //订单状态:0:未付款,1:付款结束
}
controller
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/order/create")
public CommonResult create(Order order) {
orderService.create(order);
return new CommonResult(200,"订单创建成功!");
}
}
service:
其中GLOBALtRANSACTIONAL是全局事务注解,需要加载对应的方法上。
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private StorageFeign storageFeign;
@Autowired
private AccountFeign accountFeign;
@GlobalTransactional(name = "jkt_order_tx",rollbackFor = Exception.class) // 在应用的方法上增加
@Override
public void create(Order order) {
log.info("-------->开始创建新订单");
orderDao.create(order);
log.info("-------订单微服务开始调用账户,做扣减");
accountFeign.decrease(order.getUserId(),order.getMoney());
log.info("-------订单微服务开始调用账户,做扣减end");
log.info("--------订单微服务开始调用库存,做扣减");
storageFeign.decrease(order.getProductId(),order.getCount());
log.info("-------订单微服务开始调用库存,做扣减end");
log.info("-------修改订单状态");
orderDao.update(order.getUserId(),1);
log.info("-------修改订单状态结束");
log.info("--------下订单结束了,哈哈哈哈");
}
}