简介:本文通过创建订单,库存,账户三个微服务项目,当订单请求发起后,如果账户微服务超时,验证订单,库存,账户对应的数据库数据是否会发生回滚操作。三个微服务源码地址,已经经过验证。
1.环境准备
1.1 nacos服务客户端,用于服务注册。
1.2 seata服务客户端
1.2.1 seata服务客户端文件修改后
修改 D:\seata-server-1.1.0\seata\conf 目录下file.conf文件,数据库信息,如图两处
然后修改registry.conf文件,指明用哪种注册工具,如图
1.3 数据库准备
如图分别创建了四个数据库,脚本我准备好放这里了
2.代码开始(这里订单微服务重点码下,其余两个微服务类似,请参照源码)
2.1 码之前看下项目结构
2.2 订单微服务开始
第一步:
创建module seata-order-service2001
第二步:
pom文件:seata的引入特殊,要排除自带的,引入和自己的对应的版本
<?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>cloud2020</artifactId> <groupId>com.budi.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>seata-order-service2001</artifactId> <dependencies> <!--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> <exclusions> <exclusion> <artifactId>seata-all</artifactId> <groupId>io.seata</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>io.seata</groupId> <artifactId>seata-all</artifactId> <version>1.1.0</version> </dependency> <!--open feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--mysql--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</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-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies> </project> |
第三步:
yam文件:里面的 tx-service-group 值要与file.conf里面的vgroupMapping.my_test_tx_group = "default"一致,即
my_test_tx_group
server: port: 2001 spring: application: name: seata-order-service cloud: alibaba: seata: tx-service-group: my_test_tx_group #自定义事务组名称需要与seata-server中一致 nacos: discovery: server-addr: localhost:8848 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/seata_order username: root password: 123456 feign: hystrix: enabled: false logging: level: io: seata: info mybatis: mapperLocations: classpath:mapper/*.xml |
同时把file.conf 和registry.conf两个文件放到与yam同级目录,如图
第四步:
创建domain文件,即实体类文件,其中CommonResult用于返回前台使用
Order实体类:
package com.budi.springcloud.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private long id;
private long userId;
private long productId;
private Integer count;
private BigDecimal money;
private Integer status; //订单状态 0:创建中 1:已完成
}
CommonResult
package com.budi.springcloud.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommResult<T> {
private Integer code;
private String mesage;
private T data;
public CommResult(Integer code,String message)
{
this(code,message,null);
}
}
第五步:
创建dao文件,即OrderDao 注解用@Mapper
package com.budi.springcloud.dao;
import com.budi.springcloud.domain.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface OrderDao {
//1新建订单
void create(Order order);
//2修改订单状态 由0改为1
void update(@Param("userId")Long userId,@Param("status")Integer status);
}
与该dao文件对应的mapper 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.budi.springcloud.dao.OrderDao"> <resultMap id="BaseResultMap" type="com.budi.springcloud.domain.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="count" property="count" jdbcType="INTEGER"/> <result column="money" property="money" jdbcType="DECIMAL"/> <result column="status" property="status" jdbcType="INTEGER"/> </resultMap> <insert id="create"> insert into t_order (id,user_id,product_id,count,money,status) values (null,#{userId},#{productId},#{count},#{money},0); </insert> <update id="update"> update t_order set status=1 where user_id=#{userId} and status=#{status}; </update> </mapper> |
第六步:
创建service文件及其实现类
OrderService:
package com.budi.springcloud.service;
import com.budi.springcloud.domain.Order;
import org.springframework.stereotype.Service;
public interface OrderService {
void create(Order order);
}
OrderServiceImpl
package com.budi.springcloud.service.impl;
import com.budi.springcloud.dao.OrderDao;
import com.budi.springcloud.domain.Order;
import com.budi.springcloud.service.AccountService;
import com.budi.springcloud.service.OrderService;
import com.budi.springcloud.service.StorageService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
@Slf4j
public class OrderServiceImpl implements OrderService
{
@Resource
private OrderDao orderDao;
@Resource
private AccountService accountService;
@Resource
private StorageService storageService;
@Override
@GlobalTransactional(name = "budi-zyq",rollbackFor = Exception.class)
public void create(Order order) {
log.info("--------->开始创建订单");
orderDao.create(order);
log.info("--------->开始调用库存接口,做扣减Count");
storageService.decrease(order.getProductId(),order.getCount());
log.info("--------->开始调用库存接口,扣减end");
log.info("--------->开始调用账户接口,做扣减Money");
accountService.decrease(order.getUserId(),order.getMoney());
log.info("--------->开始调用库存接口,扣减end");
//修改订单的状态 从0到1 1代表已经完成
log.info("--------->修改订单状态开始");
orderDao.update(order.getUserId(),0);
log.info("--------->修改订单状态结束");
}
}
由于下订单还需要操作库存库和账户库,要调用库存微服务和账户微服务,所以需要创建对应的StorageService和AccountService,微服务调用使用openFeign,微服务名和调用方法要都保持一致
StorageService
package com.budi.springcloud.service;
import com.budi.springcloud.domain.CommResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "seata-storage-service")
public interface StorageService {
@PostMapping("/storage/decrease")
CommResult decrease(@RequestParam("productId")Long productId,@RequestParam("count")Integer count);
}
AccountService
package com.budi.springcloud.service;
import com.budi.springcloud.domain.CommResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
@FeignClient(value = "seata-account-service")
public interface AccountService {
@PostMapping("/account/decrease")
CommResult decrease(@RequestParam("userId")Long userId, @RequestParam("money") BigDecimal money);
}
第七步
创建controller类
OrderControler
package com.budi.springcloud.controller;
import com.budi.springcloud.domain.CommResult;
import com.budi.springcloud.domain.Order;
import com.budi.springcloud.service.OrderService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class OrderController {
@Resource
private OrderService orderService;
@GetMapping("/order/create")
public CommResult create(Order order)
{
orderService.create(order);
return new CommResult(200,"订单创建成功");
}
}
第八步:
数据库我们自定义代理类,所以需要创建两个配置文件
MyBatisConfig
package com.budi.springcloud.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.budi.springcloud.dao")
public class MyBatisConfig {
}
DataSourceProxyConfig
package com.budi.springcloud.config;
import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
public class DataSourceProxyConfig {
@Value("${mybatis.mapperLocations}")
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();
}
}
第九步:
启动类 SeataOrderMainApp2001 别忘记取消数据源的自动创建
package com.budi.springcloud;
import com.budi.springcloud.config.DataSourceProxyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)//取消数据源的自动创建
public class SeataOrderMainApp2001 {
public static void main(String[] args) {
SpringApplication.run(SeataOrderMainApp2001.class,args);
}
}
第十步:测试(其余两个微服务参考源码)
访问:http://localhost:2001/order/create?userId=1&productId=1&count=10&money=100
正常访问,三个数据库都发生了相应了修改
非正常:我们人为的在账户业务处理类设置超时,看是否出现回滚(订单服务实现类别忘加注解
@GlobalTransactional(name = "budi-zyq",rollbackFor = Exception.class)
)
经过测试,回滚发生,即三个库未发生变化