四、springCloudAlibaba基础篇(分布式事务)

四、springCloudAlibaba基础篇(分布式事务)
前言
一、环境准备
二、依赖列表
三、代码编写
3.1、共用配置
3.2、服务提供者代码编写
3.3、服务调用者代码编写
3.4、测试方式
前言
下面是一个用于插入用户的代码片段,在插入用户前会记录下用户操作,图中因为 1 / 0 必然会导致异常,导致后半部分代码不会执行,此时为了保证事务的原子性,插入用户操作记录也应该回滚,spring为我们提供的声明式事务只用添加@Transactional就可以达到效果。
现在我们的项目是微服务架构,我们调用的方法可能是另一台机器的,那么事务还会生效吗?答案是不会的,此时可以使用分布式事务seata解决,
Seata 是由阿里巴巴开发的一款开源的分布式事务解决方案,官网都是中文的: seata官网

@Autowired
private UsersRespository userRespository;
@Autowired
private UsersLogRespository usersLogRespository;
//@Autowired
//private UsersLogService usersLogService;
@Override
@Transactional
public Users insertUser2() {
    UsersLog usersLog = new UsersLog();
    usersLog.setMsg("用户创建");
    usersLog = usersLogRespository.save(usersLog);
    //usersLog = usersLogService.save(usersLog);
    int i = 1 / 0;

    Users u = new Users();
    u.setName(UUID.randomUUID().toString());
    u.setLogId(usersLog.getLogId());
    userRespository.save(u);
    return null;
}


一、环境准备
步骤一:铺好被褥准备一会下载时睡一觉在这里插入图片描述
步骤二:进入到seata官网点击下载,俗话说的好,不用最新版,跳过第二版,所以我选择了1.4.2版本,找个目录解压

在这里插入图片描述

步骤三:修改conf目录中的file.conf配置文件,设置事务日志存储模式用数据库,并自定义一个事务组。并在连接的数据库中执行以下SQL语句

DROP TABLE IF EXISTS `global_table`;
CREATE TABLE `global_table` (
  `xid` VARCHAR(128)  NOT NULL,
  `transaction_id` BIGINT,
  `status` TINYINT NOT NULL,
  `application_id` VARCHAR(32),
  `transaction_service_group` VARCHAR(32),
  `transaction_name` VARCHAR(128),
  `timeout` INT,
  `begin_time` BIGINT,
  `application_data` VARCHAR(2000),
  `gmt_create` DATETIME,
  `gmt_modified` DATETIME,
  PRIMARY KEY (`xid`),
  KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
  KEY `idx_transaction_id` (`transaction_id`)
);


DROP TABLE IF EXISTS `branch_table`;
CREATE TABLE `branch_table` (
  `branch_id` BIGINT NOT NULL,
  `xid` VARCHAR(128) NOT NULL,
  `transaction_id` BIGINT ,
  `resource_group_id` VARCHAR(32),
  `resource_id` VARCHAR(256) ,
  `lock_key` VARCHAR(128) ,
  `branch_type` VARCHAR(8) ,
  `status` TINYINT,
  `client_id` VARCHAR(64),
  `application_data` VARCHAR(2000),
  `gmt_create` DATETIME,
  `gmt_modified` DATETIME,
  PRIMARY KEY (`branch_id`),
  KEY `idx_xid` (`xid`)
);


DROP TABLE IF EXISTS `lock_table`;
CREATE TABLE `lock_table` (
  `row_key` VARCHAR(128) NOT NULL,
  `xid` VARCHAR(96),
  `transaction_id` LONG ,
  `branch_id` LONG,
  `resource_id` VARCHAR(256) ,
  `table_name` VARCHAR(32) ,
  `pk` VARCHAR(36) ,
  `gmt_create` DATETIME ,
  `gmt_modified` DATETIME,
  PRIMARY KEY(`row_key`)
);

CREATE TABLE `undo_log` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `branch_id` BIGINT(20) NOT NULL,
  `xid` VARCHAR(100) NOT NULL,
  `context` VARCHAR(128) NOT NULL,
  `rollback_info` LONGBLOB NOT NULL,
  `log_status` INT(11) NOT NULL,
  `log_created` DATETIME NOT NULL,
  `log_modified` DATETIME NOT NULL,
  `ext` VARCHAR(100) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

store {
  mode = "db"
  db {
    datasource = "druid"
    dbType = "mysql"
    driverClassName = "com.mysql.jdbc.Driver"
    url = "jdbc:mysql://127.0.0.1:13306/test1?rewriteBatchedStatements=true"
    user = "root"
    password = "admin123"
    minConn = 5
    maxConn = 100
    globalTable = "global_table"
    branchTable = "branch_table"
    lockTable = "lock_table"
    queryLimit = 100
    maxWait = 5000
  }
}
## 这个位置就是自定义事务组
service{ 
  vgroupMapping.my_test_tx_group = "xu_shiwu"
  default.grouplist = "127.0.0.1:8091"
  disableGlobalTransaction = false
  disable = false
}


步骤四:修改registry.conf配置文件,选择注册中心为nacos,设置连接信息

registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  type = "nacos"
  loadBalance = "RandomLoadBalance"
  loadBalanceVirtualNodes = 10

  nacos {
    application = "seata-server"
    serverAddr = "127.0.0.1:8848"
    group = "SEATA_GROUP"
    namespace = ""
    cluster = "default"
    username = "nacos"
    password = "nacos"
  }
}

config {
  type = "nacos"
  nacos {
    serverAddr = "127.0.0.1:8848"
    namespace = ""
    group = "SEATA_GROUP"
    username = "nacos"
    password = "nacos"
    dataId = "seataServer.properties"
  }
}


步骤五:进入bin目录启动双击启动,进入nacos检查是否启动成功

在这里插入图片描述

步骤六:naocs新增配置文件,分组和registry.conf配置文件里的保持一致,名称为service.vgroupMapping. + 自己起的事务组名称,我在这里就是xu_shiwu,选择test设置值为default。如果不配置,项目启动时就会看到这个ERROR哦

2022-08-21 16:32:40.492  INFO 12932 --- [           main] c.a.nacos.client.config.impl.CacheData   : [fixed-127.0.0.1_8848] [add-listener] ok, tenant=, dataId=service.vgroupMapping.xu_shiwu, group=SEATA_GROUP, cnt=1
2022-08-21 16:32:40.709 ERROR 12932 --- [           main] i.s.c.r.netty.NettyClientChannelManager  : can not get cluster name in registry config 'service.vgroupMapping.xu_shiwu', please make sure registry config correct
2022-08-21 16:32:40.802  INFO 12932 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]

在这里插入图片描述


二、依赖列表
这是我用的完整依赖(生产者与消费者用的都一样的),使用的JPA操作数据库,可以换成mybatis,效果都是一样的

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </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.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
        <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.4.0</version>
    </dependency>
</dependencies>

三、代码编写
3.1、共用配置
写这块时候代码有点乱了,再加上自己调来调去的,代码结构比价混乱,
所以截图看一下我的代码结构吧,下边会给上所有代码,如果有照着以前写代码的小伙伴,自己按需粘贴 注意resource目录,把seata的俩文件粘贴进来了(是改完后的,不是刚解压那会的)

在这里插入图片描述

两边的实体类都是一样的,get和set方法别忘了加上,如果和我一样用的是 JPA,表会自动创建的

@Entity
@Table(name = "users")
@DynamicUpdate
public class Users implements Serializable {
    @Id
    //@GeneratedValue(strategy = GenerationType.AUTO)
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private Long uid;
    @Column(columnDefinition = "varchar(50) comment '姓名'")
    private String name;
    @Column(columnDefinition = "varchar(50) comment '密码'")
    private String pwd;
    @Column(name = "log_id")
    private Long LogId;
}
@Entity
@Table(name = "users_log")
@DynamicUpdate
public class UsersLog implements Serializable {
    @Id
    //@GeneratedValue(strategy = GenerationType.AUTO)
    @GeneratedValue(strategy = GenerationType.IDENTITY) 
    private Long logId;
    private String log;
}

配置文件只需要把端口号,项目名称改了就好,数据源改了,seata的事务名称是自己在配置文件里写的

server:
  port: 8201
spring:
  profiles:
    active: test
  application:
    name: user01
  cloud:
    nacos:
      discovery:
        server-addr: http://localhost:8848
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url:  jdbc:mysql://127.0.0.1:13306/test1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
  jpa:
    generate-ddl: false
    database-platform: org.hibernate.dialect.MySQL5Dialect
    show-sql: true
    hibernate:
      ddl-auto: update
      properties:
        use_sql_comments: false
        format_sql: false

seata:
  enabled: true
  enable-auto-data-source-proxy: true
  # 这个tx-service-group 跟配置文件的一样,
  tx-service-group: xu_shiwu
  config:
    type: nacos
    nacos:
      serverAddr: 127.0.0.1:8848
      group: SEATA_GROUP
      username: nacos
      password: nacos
  registry:
    type: nacos
    nacos:
      application: seata-server
      server-addr: 127.0.0.1:8848
      group: SEATA_GROUP
      namespace:
      username: nacos
      password: nacos
  service:
    vgroup-mapping:
      # 这个my_test_tx_group 是配置文件里自定的
      my_test_tx_group: xu_shiwu
    disable-global-transaction: false
    client:
      rm:
        report-success-enable: false

3.2、服务提供者代码编写
这里是把user01项目当服务提供者使用的,代码如下

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class UserApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }
}
// 接口
public interface UserService {
    public String info(@RequestParam("name") String name, @RequestParam("pwd") String pwd);
    public String findOne(@PathVariable("id") String id);
    public Users insert(Users users);
}
// 接口实现类
@Service
public class UserServiceImpl implements UserService{
    @Autowired
    private UsersRespository usersRespository;
    @Override
    public String info(String name, String pwd) {
        return null;
    }
    @Override
    public String findOne(String id) {
        Optional<Users> byId = usersRespository.findById(Long.parseLong(id));
        try {
         return byId.get().getName();
        }catch (Exception e){
         return "未找到用户";
        }
    }
    @Override
    public Users insert(Users users) {
        Users save = usersRespository.save(users);
        return save;
    }
}

@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("user/info")
    public String info(@RequestParam("name") String name, @RequestParam("pwd") String pwd){
        return userService.info(name, pwd);
    }
    @GetMapping("user/findOne/{id}")
    public String findOne(@PathVariable("id") String id) {
        return userService.findOne(id);
    }
    @PostMapping("user/insert")
    public Users insert(@RequestBody Users users) {
        return userService.insert(users);
    }
}
/**
* 这个是JPA的接口,所以没有给定义方法
*/
public interface UsersRespository extends JpaRepository<Users, Long>, JpaSpecificationExecutor<Users> {
}


3.3、服务调用者代码编写
这里是把provider01项目当服务提供者使用的,代码如下

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
@RestController
@RequestMapping("/userLog")
public class UsersLogController {
    @Autowired
    private UsersLogService usersLogService;
    @Autowired
    private UserService userService;

    @RequestMapping("/findUserId")
    public String findUserById(){
        return userService.findOne("1");
    }

    @RequestMapping("/test1")
    public UsersLog insert(){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        UsersLog usersLog = new UsersLog();
        usersLog.setLog("日志记录" + simpleDateFormat.format(new Date()));
        return usersLogService.insert(usersLog);
    }

    @RequestMapping("/test2")
    public String insertAndUser(){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        UsersLog usersLog = new UsersLog();
        usersLog.setLog("日志记录" + simpleDateFormat.format(new Date()));

        Users users = new Users();
        users.setName("张三");
        users.setPwd("1223333");
        return usersLogService.insertAndUser(usersLog, users);
    }
}
/**
* 这是JPA接口
*/
public interface UserLogRespository extends JpaRepository<UsersLog, Long>, JpaSpecificationExecutor<UsersLog> {
}

/** 远程调用users的接口 */
@Component
@FeignClient(name="user01")
public interface UserService {
    @GetMapping("user/info")
    public String info(@RequestParam("name") String name, @RequestParam("pwd") String pwd);

    @GetMapping("user/findOne/{id}")
    public String findOne(@PathVariable("id") String id);

    @PostMapping("user/insert")
    public Users insert(@RequestBody Users users);
}
/**
* 当前项目自身接口与实现类
*/
public interface UsersLogService {
    public UsersLog insert(UsersLog usersLog);
    public String insertAndUser(UsersLog usersLog, Users users);
}
@Service
public class UsersLogServiceImpl implements UsersLogService{
    @Autowired
    private UserLogRespository userLogRespository;
    @Autowired
    private UserService userService;

    public UsersLog insert(UsersLog usersLog){
        return userLogRespository.save(usersLog);
    }

    @GlobalTransactional(name = "xu_shiwu")
    public String insertAndUser(UsersLog usersLog, Users users){
        userLogRespository.save(usersLog);
        //int i = 1 / 0;
        userService.insert(users);
        //int x = 2 / 0;
        return "success";
    }
}


3.4、测试方式
我这里设置provider01项目端口为8101,分别调用了三个接口
测试项目远程调用是否正常:http://localhost:8101/userLog/findUserId
测试当前项目JPA能否保存数据:http://localhost:8101/userLog/test1
用于事务测试:http://localhost:8101/userLog/test2

结论:当把 @GlobalTransactional(name = “xu_shiwu”)注释掉,并执行int i = 1 / 0代码,UserLog表会成功添加数据,而user表无数据废话,远程接口都没调用。当把注解打开,UsersLog表数据会回滚成功,然而并不能证明user表那边事务也会回滚!!!
解除int x = 2 / 0,如果两张表都没有数据,则表示分布式事务回滚成功!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值