springcloud第三讲

目录

三、springcloud03

3.1 普通事务

3.2 演示事务

3.2.1、建库

3.2.2 创建父工程

3.2.3 编写公共微服务

3.2.4 编写账户微服务

3.2.5 编写库存微服务

3.2.6 编写订单微服务

3.2.7 测试

3.3 分布式事务

3.4 介绍seata

3.5 搭建seata服务器

3.5.1 解决集群共享数据

3.5.2 seata连接nacos

3.6 配置微服务客户端

3.6.1 创建数据库

3.6.2 修改微服务代码

三、springcloud03

3.1 普通事务

普通事务

1、ACID事务的特点:原子性、一致性、隔离性、持久性

2、事务并发带来的问题:脏读、幻读、不可重复读、丢失修改

3、我们可以使用事务的隔离级别来解决

读未提交、读已提交、可重复读、串行化

事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列SQL操作,这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行。

 

3.2 演示事务

3.2.1、建库

创建三个数据库,每个数据库一张表,表名和库名一致即可,这里使用的是mysql5.7的建表语句,没有collect

/*
 Navicat Premium Data Transfer
​
 Source Server         : localhost_3306
 Source Server Type    : MySQL
 Source Server Version : 80011
 Source Host           : localhost:3306
 Source Schema         : seata_account
​
 Target Server Type    : MySQL
 Target Server Version : 80011
 File Encoding         : 65001
​
 Date: 28/11/2022 15:28:32
*/
​
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for t_account
-- ----------------------------
DROP TABLE IF EXISTS `t_account`;
CREATE TABLE `t_account`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NULL DEFAULT NULL,
  `total` int(11) NULL DEFAULT NULL,
  `used` int(11) NULL DEFAULT NULL,
  `residue` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4;
​
-- ----------------------------
-- Records of t_account
-- ----------------------------
INSERT INTO `t_account` VALUES (1, 1, 1000, 100, 900);
​
SET FOREIGN_KEY_CHECKS = 1;
/*
 Navicat Premium Data Transfer
​
 Source Server         : localhost_3306
 Source Server Type    : MySQL
 Source Server Version : 80011
 Source Host           : localhost:3306
 Source Schema         : seata_order
​
 Target Server Type    : MySQL
 Target Server Version : 80011
 File Encoding         : 65001
​
 Date: 28/11/2022 15:28:38
*/
​
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for t_order
-- ----------------------------
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NULL DEFAULT NULL,
  `product_id` int(11) NULL DEFAULT NULL,
  `count` int(11) NULL DEFAULT NULL,
  `money` bigint(20) NULL DEFAULT NULL,
  `status` int(11) NULL DEFAULT 0 COMMENT '0 未支付  1 已支付',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 46 CHARACTER SET = utf8mb4;
​
-- ----------------------------
-- Records of t_order
-- ----------------------------
INSERT INTO `t_order` VALUES (47, 1, 1, 5, 100, 1);
​
SET FOREIGN_KEY_CHECKS = 1;
/*
 Navicat Premium Data Transfer
​
 Source Server         : localhost_3306
 Source Server Type    : MySQL
 Source Server Version : 80011
 Source Host           : localhost:3306
 Source Schema         : seata_storage
​
 Target Server Type    : MySQL
 Target Server Version : 80011
 File Encoding         : 65001
​
 Date: 28/11/2022 15:28:45
*/
​
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
​
-- ----------------------------
-- Table structure for t_storage
-- ----------------------------
DROP TABLE IF EXISTS `t_storage`;
CREATE TABLE `t_storage`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `product_id` int(11) NULL DEFAULT NULL,
  `total` int(11) NULL DEFAULT NULL COMMENT '总量',
  `used` int(11) NULL DEFAULT NULL COMMENT '已经使用的数量',
  `residue` int(11) NULL DEFAULT NULL COMMENT '剩余的数据量',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8;
​
-- ----------------------------
-- Records of t_storage
-- ----------------------------
INSERT INTO `t_storage` VALUES (1, 1, 100, 0, 100);
​
SET FOREIGN_KEY_CHECKS = 1;

3.2.2 创建父工程

删掉src,只保留pom文件即可

<?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">
    <modelVersion>4.0.0</modelVersion>
​
    <groupId>com.ykq</groupId>
    <artifactId>seata_parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>seata-common</module>
        <module>seata-account</module>
        <module>seata-order</module>
        <module>seata-storage</module>
    </modules>
    <packaging>pom</packaging>
    <!--引入父工程-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-cloud.version>Hoxton.SR8</spring-cloud.version>
        <spring-cloud-alibaba.version>2.2.3.RELEASE</spring-cloud-alibaba.version>
    </properties>
    <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>
</project>

3.2.3 编写公共微服务

1、pom文件

<?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>seata_parent</artifactId>
        <groupId>com.ykq</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
​
    <artifactId>seata-common</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
​
</project>

2、实体类

package com.ykq.entity;
​
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//账户
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Account {
    private int id;
    private int userId;
    private int total;
    private int used;
    private int residue;
}
package com.ykq.entity;
​
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//统一返回结果
@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);
    }
}
package com.ykq.entity;
​
import lombok.Data;
//订单
@Data
public class Order {
    private int id;
    private int userId;
    private int productId;
    private double money;
    private int count;
    private int status;
}
package com.ykq.entity;
​
import lombok.Data;
//库存
@Data
public class Storage {
    private int id;
    private int productId;
    private int total;
    private int used;//被用了多少个
    private int residue;//剩余的个数。
}

3.2.4 编写账户微服务

1、pom文件

<?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>seata_parent</artifactId>
        <groupId>com.ykq</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
​
    <artifactId>seata-account</artifactId>
    <dependencies>
        <!--引入公共依赖-->
        <dependency>
            <groupId>com.ykq</groupId>
            <artifactId>seata-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--nacos注册中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--nacos配置中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</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-jdbc</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--openfeign:用于服务间远程调用-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </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>
            <exclusions>
                <!--排除其中的某个版本,可以再在外面添加自己需要的版本-->
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    </dependencies>
</project>

2、配置文件

server:
  port: 8002
spring:
  # 数据源配置
  datasource:
    url: jdbc:mysql://localhost:3306/seata_account?serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 密码
  #配置服务名
  application:
    name: seata-account
  #nacos注册中心的地址
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
mybatis:
  mapperLocations: classpath:mapper/*.xml
logging:
  level:
    com.ykq.dao: debug

3、mapper文件

<?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.ykq.dao.AccountDao">
    <resultMap id="account" type="com.ykq.entity.Account">
        <result property="id" column="id" jdbcType="BIGINT"/>
        <result property="userId" column="user_id" jdbcType="BIGINT"/>
        <result property="total" column="total" jdbcType="DECIMAL"/>
        <result property="used" column="used" jdbcType="DECIMAL"/>
        <result property="residue" column="residue" jdbcType="DECIMAL"/>
    </resultMap>
​
    <update id="decrease">
        update t_account set used = used + #{money},residue = residue - #{money}
        where user_id=#{userId}
    </update>
</mapper>

4、mapper接口

package com.ykq.dao;
​
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
​
import java.math.BigDecimal;
​
@Mapper
public interface AccountDao {
​
    void decrease(@Param("userId") int userId, @Param("money") double money);
}

5、service接口

package com.ykq.service;
​
public interface AccountService {
​
    void decrease(int userId, double money);
}

6、service实现层

package com.ykq.service.impl;
​
import com.ykq.dao.AccountDao;
import com.ykq.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
​
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    @Override
    public void decrease(int userId, double money) {
            accountDao.decrease(userId,money);
    }
}

7、controller层

package com.ykq.controller;
​
import com.ykq.entity.CommonResult;
import com.ykq.service.AccountService;
import org.springframework.web.bind.annotation.*;
​
import javax.annotation.Resource;
import java.math.BigDecimal;
​
@RestController
@RequestMapping(value = "account")
public class AccountController {
​
    @Resource
    private AccountService accountService;
​
    /**
     * 根据用户id扣余额
     * @param userId
     * @param money
     * @return
     */
    @PostMapping("/increase/{userId}/{money}")
    public CommonResult increase(@PathVariable("userId")Integer userId,@PathVariable("money")double money) {
        accountService.decrease(userId, money);
        return new CommonResult(200,"账户余额扣减成功,哈哈哈");
    }
}

8、启动类

package com.ykq;
​
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
​
//1.3
@SpringBootApplication
@EnableDiscoveryClient
public class AccountApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(AccountApplication.class, args);
    }
​
}

3.2.5 编写库存微服务

1、pom文件

<?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>seata_parent</artifactId>
        <groupId>com.ykq</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
​
    <artifactId>seata-storage</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.ykq</groupId>
            <artifactId>seata-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
 
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</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-jdbc</artifactId>
        </dependency>
​
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
​
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
​
</project>

和账户微服务的pom文件差不多,可以参考账户微服务

2、配置文件

server:
  port: 8003
spring:
  # 数据源配置
  datasource:
    url: jdbc:mysql://localhost:3306/seata_storage?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 密码
  application:
    name: seata-storage
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    alibaba:
      seata:
        tx-service-group: fsp_tx_group
mybatis:
  mapperLocations: classpath:mapper/*.xml
logging:
  level:
    com.ykq.dao: debug

3、mapper文件

<?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.ykq.dao.StorageDao">
​
    <resultMap id="order" type="com.ykq.entity.Storage">
        <result property="id" column="id" jdbcType="BIGINT"/>
        <result property="product_id" column="productId" jdbcType="BIGINT"/>
        <result property="total" column="total" jdbcType="INTEGER"/>
        <result property="used" column="used" jdbcType="INTEGER"/>
        <result property="residue" column="residue" jdbcType="INTEGER"/>
    </resultMap>
​
    <update id="decrease">
        update t_storage set used = used + #{count},residue = residue -#{count}
        where product_id = #{productId}
    </update>
</mapper>

4、mapper接口

package com.ykq.dao;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
​
public interface StorageDao {
    void decrease(@Param("productId") int productId,@Param("count") Integer count);
}
​
​

5、service接口

package com.ykq.service;
​
public interface StorageService {
    void decrease(int productId, Integer count);
}

6、service实现类

package com.ykq.service.impl;
​
import com.ykq.dao.StorageDao;
import com.ykq.service.StorageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
​
@Service
@Slf4j
public class StorageServiceImpl implements StorageService {
    @Autowired
    private StorageDao storageDao;
    @Override
    @Transactional
    public void decrease(int productId, Integer count) {
        log.info("库存扣减开始----");
        storageDao.decrease(productId,count);
        log.info("库存扣减结束----");
    }
}

7、controller层

package com.ykq.controller;
​
​
import com.ykq.entity.CommonResult;
import com.ykq.service.StorageService;
import org.springframework.web.bind.annotation.*;
​
import javax.annotation.Resource;
​
@RestController
@RequestMapping(value = "/storage")
public class StorageController {
​
    @Resource
    private StorageService storageService;
​
    /**
     * 根据商品id扣减库存
     * @param productId
     * @param count
     * @return
     */
    @PostMapping("/increase/{productId}/{count}")
    CommonResult increase(@PathVariable("productId") int productId, @PathVariable("count") int count){
        storageService.decrease(productId, count);
        return new CommonResult(200,"库存扣减成功,哈哈哈哈");
    }
}

8、启动类

package com.ykq;
​
import org.mybatis.spring.annotation.MapperScan;
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.transaction.annotation.EnableTransactionManagement;
​
@SpringBootApplication
@MapperScan("com.ykq.dao")
public class StorageApplication {
​
    public static void main(String[] args) {
        SpringApplication.run(StorageApplication.class, args);
    }
​
}
​

3.2.6 编写订单微服务

1、pom文件

<?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>seata_parent</artifactId>
        <groupId>com.ykq</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
​
    <artifactId>seata-order</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.ykq</groupId>
            <artifactId>seata-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
​
​
        <!--阿里巴巴的注册中心nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--springbootweb项目-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--spring连接数据库的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
​
        <!--springboot和mybatis整合的依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--openfeign的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
​
        <!--热部署依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <!--mysql的驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--为了让你的application.yml文件有提示-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!--spring单元测试的依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
​
</project>

2、配置文件

server:
  port: 8001
​
spring:
  application:
    name: seata-order
# 数据源
  datasource:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/seata_order?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
      username: root
      password: 密码
# 把该服务注册到nacos上
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
​
# 配置mybatis映射文件的路径
mybatis:
  mapperLocations: classpath:mapper/*.xml
# 显示mybatis执行的sql语句
logging:
  level:
    com.ykq.dao: debug

3、mapper文件

<?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.ykq.dao.OrderDao">
​
    <resultMap id="order" type="com.ykq.entity.Order">
        <result property="id" column="id" jdbcType="BIGINT"/>
        <result property="user_id" column="userId" jdbcType="BIGINT"/>
        <result property="product_id" column="productId" jdbcType="BIGINT"/>
        <result property="count" column="count" jdbcType="INTEGER"/>
        <result property="money" column="money" jdbcType="BIGINT"/>
        <result property="status" column="status" jdbcType="INTEGER"/>
    </resultMap>
​
​
    <insert id="saveOrder" useGeneratedKeys="true" keyProperty="id">
        insert into t_order(user_id,product_id,count,money,status)
        value (#{userId},#{productId},#{count},#{money},0)
    </insert>
​
    <update id="updateStatus">
        update t_order set status = 1
        where id = #{id} and status = 0
    </update>
​
</mapper>

4、mapper接口

package com.ykq.dao;
​
import com.ykq.entity.Order;
​
public interface OrderDao {
    void saveOrder(Order order);
​
    void updateStatus(int id);
}

5、feign接口

因为要实现远程调用,所以这里要编写feign接口

package com.ykq.feign;
​
import com.ykq.entity.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
​
@FeignClient(value="seata-account")
public interface AccountFeign {
​
    @PostMapping("/account/increase/{userId}/{money}")
    public CommonResult increase(@PathVariable("userId")Integer userid, @PathVariable("money")double money);
}
package com.ykq.feign;
​
import com.ykq.entity.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
​
@FeignClient("seata-storage")
public interface StorageFeign {
    @PostMapping("/storage/increase/{productId}/{count}")
    CommonResult increase(@PathVariable("productId") int productId, @PathVariable("count") int count);
}

6、service接口

package com.ykq.service;
​
import com.ykq.entity.Order;
​
public interface OrderService {
    void saveOrder(Order order);
}
​

7、service实现类

package com.ykq.service.impl;
​
import com.ykq.dao.OrderDao;
import com.ykq.entity.Order;
import com.ykq.feign.AccountFeign;
import com.ykq.feign.StorageFeign;
import com.ykq.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
​
import javax.annotation.Resource;
​
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
​
    @Autowired
    private OrderDao orderDao;
​
    @Autowired
    private AccountFeign accountFeign;//表示调用的远程服务。
​
    @Autowired
    private StorageFeign storageFeign;
​
    @Override
    @Transactional
    public void saveOrder(Order order) {
        log.info("-------->开始创建新订单");
        orderDao.saveOrder(order);
​
        log.info("-------订单微服务开始调用账户,做扣减");
        accountFeign.increase(order.getUserId(),order.getMoney()); //当前的事务回滚只会回滚自己的数据
        log.info("-------订单微服务开始调用账户,做扣减end");
        
        log.info("--------订单微服务开始调用库存,做扣减");
        storageFeign.increase(order.getProductId(),order.getCount());
        log.info("-------订单微服务开始调用库存,做扣减end");
​
​
        log.info("-------修改订单状态");
        orderDao.updateStatus(order.getId());
​
        log.info("-------修改订单状态结束");
​
        log.info("--------下订单结束了,哈哈哈哈");
    }
​
}

8、controller层

package com.ykq.controller;
​
import com.ykq.entity.CommonResult;
import com.ykq.entity.Order;
import com.ykq.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
​
@RestController
@RequestMapping("order")
public class OrderController {
    @Autowired
    private OrderService orderService;
    @GetMapping("addOrder")
    public CommonResult addOrder(Order order){
        try {
            orderService.saveOrder(order);
            return new CommonResult(2000, "下单成功");
        }catch (Exception e){
            return new CommonResult(5000, "下单失败");
        }
    }
}

9、启动类

package com.ykq;
​
import org.mybatis.spring.annotation.MapperScan;
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;
import org.springframework.transaction.annotation.EnableTransactionManagement;
​
@SpringBootApplication //排除原有的数据源的配置
@MapperScan("com.ykq.dao")
@EnableFeignClients //开启feign的注解
@EnableTransactionManagement
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }
}

3.2.7 测试

代码编写完成,如果是粘贴的整个项目,配置文件红maven仓库也需要修改为自己的

3.2.7.1 成功测试

1、数据库初始状态

 

 

 

2、浏览器访问订单资源进行测试

请求为:http://localhost:8001/order/addOrder/?userId=1&productId=1&count=5&money=100

响应结果为:{"code":2000,"message":"下单成功","data":null}

 

3、响应成功后数据库

 

 

 

3.2.7.2 异常测试

人为的给订单微服务程序添加一个异常情况 【算术异常即可】比如:10/0

1、修改订单的service实现类

package com.ykq.service.impl;
​
import com.ykq.dao.OrderDao;
import com.ykq.entity.Order;
import com.ykq.feign.AccountFeign;
import com.ykq.feign.StorageFeign;
import com.ykq.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
​
import javax.annotation.Resource;
​
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
​
    @Autowired
    private OrderDao orderDao;
​
    @Autowired
    private AccountFeign accountFeign;//表示调用的远程服务。
​
    @Autowired
    private StorageFeign storageFeign;
​
    @Override
    @Transactional
    public void saveOrder(Order order) {
        log.info("-------->开始创建新订单");
        orderDao.saveOrder(order);
​
        log.info("-------订单微服务开始调用账户,做扣减");
        accountFeign.increase(order.getUserId(),order.getMoney()); //当前的事务回滚只会回滚自己的数据
        log.info("-------订单微服务开始调用账户,做扣减end");
        //添加异常
        int  c=10/0;
        log.info("--------订单微服务开始调用库存,做扣减");
        storageFeign.increase(order.getProductId(),order.getCount());
        log.info("-------订单微服务开始调用库存,做扣减end");
​
​
        log.info("-------修改订单状态");
        orderDao.updateStatus(order.getId());
​
        log.info("-------修改订单状态结束");
​
        log.info("--------下订单结束了,哈哈哈哈");
    }
}

注:这里异常添加在账户微服务之后,订单微服务之前

2、再次进行测试

请求:http://localhost:8001/order/addOrder/?userId=1&productId=1&count=5&money=100

请求结果:{"code":5000,"message":"下单失败","data":null}

3、查看数据库状态

 

 

 

3.3 分布式事务

分布式服务结构图如下:

 

因为每个事务都连接了自己的数据库,所以在进行事务操作时,

一个微服务(A)的成功,就会改变自己的数据库,即使其它微服务(B)失败,该微服务(A)也不会回滚自己的数据库

1. 借助rabbitMQ消息中间件
2. 可以自己编写代码完成分布式事务。
3. 借助于第三方分布式事务服务器。---阿里巴巴seata

3.4 介绍seata

seata官网地址:Seata部署指南

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT[两次提交]、TCC、SAGA 和XA 事务模式,为用户打造一站式的分布式解决方案。

这次主要练习的就是AT事务模式

 

Seata的执行流程如下:

1、A服务【订单微服务】的TM[事务发起者]向TC[seata服务端]申请开启一个全局事务,TC就会创建一个全局事务并返回一个唯一的XID

2、A服务开始远程调用B服务【账户微服务】,此时XID会在微服务的调用链上传播

3、B服务的RM向TC注册分支事务,并将其纳入XID对应的全局事务的管辖

4、B服务执行分支事务,向数据库做操作

5、全局事务调用链处理完毕,TM根据有无异常向TC发起全局事务的提交或者回滚

6、TC协调其管辖之下的所有分支事务, 决定是否回滚

根据上面的测试进行举例来看,每一部分的角色如下

TM:事务的发起者--下单

RM: 连接数据库--微服务

TC:全局事务管理者--seata服务器

XID:全局事务id

3.5 搭建seata服务器

seata官网:Release v1.3.0 · seata/seata · GitHub

seata在1.3.0之后支持集群模式

解压文件

 

3.5.1 解决集群共享数据

1、修改conf/file.conf文件,更改数据保存的位置

 

1.1将源码压缩包解压后,找到其中script文件夹,将其复制到seata文件目录下

 

1.2 找到script\server\db目录下的mysql.sql文件,就是我们需要的三张表,将表添加到mysql中的seata数据库

 

1.3 因为在设置数据库时,我们选择的驱动是8的驱动,所以我们也需要有8的jar包,默认读取的是lib文件目录下的jar包,所以可以将seata/lib/jdbc/目录下的jar包粘贴到seata/lib目录下

3.5.2 seata连接nacos

当seata是一个集群时,微服务连接seata会无法选择seata,将seata接入到nacos,别的微服务就可以在nacos中拉取对应的seata服务

1、找到seata/conf/registry.conf文件进行修改

 

2、上传配置文件到nacos配置中心

找到seata\script\config-center目录下的config.txt文件,修改其中的配置,修改完之后,这些配置是需要放到配置中心的

 

注!!!!!!!!!!!!!!

url那一行,在最后多了一个双引号,一定要删掉

然后使用脚本将这些修改好的配置文件,传到nacos注册中心,脚本位置在seata\script\config-center\nacos\nacos-config.sh,如果安装了git,那么这里双击此脚本,会自动调用git的控制台,执行命令,将配置上传到配置中心

 

再执行时,也可以指定nacos的信息

 

3、启动搭建好的seata服务器,他会自动注册到nacos中

找到seata\bin目录下的seata-server.bat文件,双击即可执行,

如果需要指定端口号,可以进入到cmd中使用命令执行

seata-server.bat -Dserver.port=8989

 

3.6 配置微服务客户端

3.6.1 创建数据库

在每一个微服务的数据库中建一张日志表,表结构在seata\script\client\at\db目录下的mysql.sql中

 

3.6.2 修改微服务代码

这里先使用storage模块进行测试

1、添加依赖

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
            <!--如果seata的版本不对,可以在这里先排除一个版本,然后再添加新的依赖-->
            <exclusions>
                <exclusion>
                    <groupId>io.seata</groupId>
                    <artifactId>seata-spring-boot-starter</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
​
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <!--添加自己需要的版本-->
            <version>1.3.0</version>
        </dependency>

当然这里版本是对应的,所以不需要修改版本

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        </dependency>

2、编写配置文件这里改为properties文件

server.port=8003
#配置数据源
spring.datasource.url=jdbc:mysql://localhost:3306/seata_storage?serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=密码
#
spring.application.name=seata-storage
#注册中心地址
spring.cloud.nacos.discovery.server-addr=localhost:8848
#seata服务组的名字
spring.cloud.alibaba.seata.tx-service-group=guangzhou
seata.service.vgroup-mapping.guangzhou=default
#扫描mapper文件
mybatis.mapper-locations=classpath:mapper/*.xml
#日志扫描
logging.level.com.ykq.dao=debug
#seata注册类型
seata.registry.type=nacos
#注册中心的信息-----------------------------
#指定seata服务器所在的注册中心地址
seata.registry.nacos.server-addr=localhost:8848
#nacos的用户名和密码
seata.registry.nacos.username=nacos
seata.registry.nacos.password=nacos
#指定seata服务器在注册中心的组名,默认为SEATA_GROUP
seata.registry.nacos.group=SEATA_GROUP
#指定seata服务器在注册中心的服务名,默认为seata-server
seata.registry.nacos.application=seata-server
#注册中心的信息----------------------------------
#指定seata服务器所在的配置中心地址
seata.config.nacos.server-addr=localhost:8848
#nacos的用户名和密码
seata.config.nacos.username=nacos
seata.config.nacos.password=nacos
#指定seata服务器在配置中心的组名,默认为SEATA_GROUP
seata.config.nacos.group=SEATA_GROUP

3、运行项目进行测试

报错: no available service 'default' found, please make sure registry config correct

在配置文件中添加一行,这里的guangzhou就是自己命名的seata组名

seata.service.vgroup-mapping.guangzhou=default

再次运行就可以成功了,就代表着客户端也搭建完毕,将其他模块修改一下即可

4、修改发起者的代码 [订单微服务的service实现类]

package com.ykq.service.impl;
​
import com.ykq.dao.OrderDao;
import com.ykq.entity.Order;
import com.ykq.feign.AccountFeign;
import com.ykq.feign.StorageFeign;
import com.ykq.service.OrderService;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
​
import javax.annotation.Resource;
​
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
​
    @Autowired
    private OrderDao orderDao;
​
    @Autowired
    private AccountFeign accountFeign;//表示调用的远程服务。
​
    @Autowired
    private StorageFeign storageFeign;
​
    @Override
    @GlobalTransactional   //使用这个注解进行分布式事务
    public void saveOrder(Order order) {
        log.info("-------->开始创建新订单");
        orderDao.saveOrder(order);
​
        log.info("-------订单微服务开始调用账户,做扣减");
        accountFeign.increase(order.getUserId(),order.getMoney()); //当前的事务回滚只会回滚自己的数据
        log.info("-------订单微服务开始调用账户,做扣减end");
        //添加异常
        // int  c=10/0;
        log.info("--------订单微服务开始调用库存,做扣减");
        storageFeign.increase(order.getProductId(),order.getCount());
        log.info("-------订单微服务开始调用库存,做扣减end");
​
        log.info("-------修改订单状态");
        orderDao.updateStatus(order.getId());
​
        log.info("-------修改订单状态结束");
​
        log.info("--------下订单结束了,哈哈哈哈");
    }
​
}

然后发起请求[数据库已经改为最初状态 拥有1000元,库存为100,订单数为0]

1、先验证正常情况

请求:http://localhost:8001/order/addOrder/?userId=1&productId=1&count=5&money=100

请求结果:{"code":2000,"message":"下单成功","data":null}

 

 

 

2、再测试异常情况

请求:http://localhost:8001/order/addOrder/?userId=1&productId=1&count=5&money=100

请求结果:

{"code":5000,"message":"下单失败","data":null}

 

 

其中一个微服务的事务失败,所有的事务都会回滚,

事务过程中,生成的信息存在日志表中,事务的信息存在seata数据库的三张表中,通过打断点可以看到中间的信息

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值