SpringCloud结合Seata分布式事务处理初级入门

简介:本文通过创建订单,库存,账户三个微服务项目,当订单请求发起后,如果账户微服务超时,验证订单,库存,账户对应的数据库数据是否会发生回滚操作。三个微服务源码地址,已经经过验证。

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)

经过测试,回滚发生,即三个库未发生变化

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值