8.Spring Cloud Alibaba教程:整合Seata分布式事务

概述

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。

更多的介绍可以参考官方文档:Seata快速入门
本篇主要是介绍Spring Cloud Alibaba + JPA 整合 Seata 的过程

安装Seata

  • 下载Seata,打开 https://github.com/seata/seata/releases,现在最新的是1.3.0,所以选择seata-server-1.3.0.tar.gz 进行下载,放到/opt
  • 解压Seata到/usr/local目录下
cd /opt
tar -zxvf seata-server-1.3.0.tar.gz -C /usr/local
cd /usr/local/seata/
  • 这边采用file单机模式(db模式相对麻烦一点,以后再另外介绍,从简单的先开始),直接运行命令启动即可
./bin/seata-server.sh

看到下面这个提示,说明启动成功,默认端口号8091

在这里插入图片描述
接下来介绍Spring Cloud Alibaba 接入Seata

业务场景

假设当前存在2个微服务:订单服务、支付服务。

  • 用户下单时,订单服务需要调用支付服务进行扣款
  • 当余额充足时下单成功,并全局提交事务
  • 当余额不足时下单失败,并全局回滚事务

版本说明

框架 版本号
Spring Boot 2.1.13.RELEASE
Spring Cloud Greenwich.SR6
Spring Cloud Alibaba 2.1.3.RELEASE
MySQL 8.0.11

创建父工程

创建父工程 hello-alibaba-seata,并引入依赖:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>${spring-cloud-version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-alibaba-dependencies</artifactId>
      <version>${spring-cloud-alibaba-version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

<dependencies>
  <!--Spring Boot-->
  <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-starter-data-jpa</artifactId>
  </dependency>

  <!--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>
  </dependency>

  <!--MySQL-->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>${mysql.version}</version>
  </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

创建公共服务 common-server

该工程主要封装公共的VO,由于测试场景比较简单,目前只有一个类

@Data
@Builder
public class ReduceAmountDto {
    
    
    private Long userId;
    private Integer amount;
}

创建支付服务 pay-order

在订单-支付的关系中,支付服务相当于provider,订单服务相当于consumer。因此,这边我们先创建支付服务。

  • 引入 common-server 依赖
    <dependencies>
        <dependency>
            <groupId>com.train</groupId>
            <artifactId>common-server</artifactId>
            <version>${project.parent.version}</version>
        </dependency>
    </dependencies>
  • 创建账户 Entity
@Data
@Entity
@Table(name = "t_account")
public class AccountInfo {
    
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    /**
     * 用户id
     */
    @Column(name = "user_id")
    private Long userId;
    /**
     * 余额
     */
    @Column(name = "amount")
    private Integer amount;
}
  • 创建Dao
public interface AccountDao extends CrudRepository<AccountInfo, Long> {
    
    
    /**
     *  根据userId获取账户信息
     */
    @Query(value = "SELECT a FROM AccountInfo a where a.userId = :userId")
    AccountInfo getByUserId(@Param("userId") Long userId);
    /**
     *  扣减账户余额
     */
    @Modifying
    @Query("UPDATE AccountInfo SET amount = amount - :amount WHERE userId = :userId")
    Integer reduceAmount(@Param("userId") Long userId, @Param("amount") Integer amount);
}
  • 创建 Service
@Service
public class PayServiceImpl implements PayService {
    
    

    @Autowired
    private AccountDao accountDao;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reduceAmount(Long userId, Integer reduceAmount) throws Exception {
    
    
        checkAmount(userId, reduceAmount);

        accountDao.reduceAmount(userId, reduceAmount);
    }

    private void checkAmount(Long userId, Integer reduceAmount) throws Exception {
    
    
        AccountInfo accountInfo = accountDao.getByUserId(userId);
        if (accountInfo == null){
    
    
            throw new Exception("找不到账户信息");
        }
        if (accountInfo.getAmount() < reduceAmount){
    
    
            throw new Exception("余额不足");
        }
    }
}
  • 创建 controller
@RestController
@RequestMapping("/pay")
public class PayController {
    
    

    @Autowired
    private PayService payService;

    @PostMapping("/reduceAmount")
    public String reduceAmount(@RequestBody ReduceAmountDto input) throws Exception {
    
    
        payService.reduceAmount(input.getUserId(), input.getAmount());
        return "扣款成功";
    }
}
  • application.yml
server:
  port: 8012

spring:
  application:
    name: pay-server
  jpa:
    database: mysql
  datasource:
    url: jdbc:mysql://192.168.25.129:3306/pay_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: test
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.25.131:8848
management:
  endpoints:
    web:
      exposure:
        include: "*"

seata:
  enabled: true
  application-id: ${
    
    spring.application.name}
  tx-service-group: my_test_tx_group
  service:
    vgroup-mapping:
      my_test_tx_group: default
    grouplist:
      default: 192.168.25.131:8091
  config:
    type: file
    file:
      name: file.conf
  registry:
    type: file
    file:
      name: file.conf
  enable-auto-data-source-proxy: true

注意:如果用的不是MySQL8,就要改下 driver-class-name

创建订单服务 order-server

  • 引入 common-server 依赖
    <dependencies>
        <dependency>
            <groupId>com.train</groupId>
            <artifactId>common-server</artifactId>
            <version>${project.parent.version}</version>
        </dependency>
    </dependencies>
  • 创建订单 Entity
@Data
@Entity
@Table(name = "t_order")
public class Orders {
    
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    /**
     * 用户id
     */
    @Column(name = "user_id")
    private Long userId;
    /**
     * 金额
     */
    @Column(name = "amount")
    private Integer amount;
}
  • 创建订单 Dao
public interface OrdersDao extends CrudRepository<Orders, Long> {
    
    
}
  • 创建 Service
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
    
    

    @Autowired
    private OrdersDao orderDao;
    @Autowired
    private RestTemplate restTemplate;

    @Override
    @GlobalTransactional
    public void save(Orders order) {
    
    
        orderDao.save(order);

        //扣款
        String reduceUrl = "http://pay-server/pay/reduceAmount";
        ReduceAmountDto input = ReduceAmountDto.builder().userId(order.getUserId()).amount(order.getAmount()).build();
        String result = restTemplate.postForObject(reduceUrl, input, String.class);

        log.info("调用pay-server结果:" + result);
    }
}

@GlobalTransactional 表示开启Seata全局事务

  • 创建 controller
@RestController
@RequestMapping("/order")
public class OrderController {
    
    

    @Autowired
    private OrderService orderService;

    @RequestMapping("/save")
    public String save(@RequestBody Orders order){
    
    
        orderService.save(order);
        return "操作结束";
    }
}

测试

  1. 启动Nacos
  2. 启动Seata
  3. 启动pay-server
  4. 启动order-server
  5. 调用创建订单接口 http://localhost:8011/order/save ,构造相应的测试数据进行测试就行

猜你喜欢

转载自blog.csdn.net/qq_28834355/article/details/108858851