栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Springboot集成分布式事务Seata

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Springboot集成分布式事务Seata

概述 springboot+springcloud+seata 版本选择

springboot:2.1.3.RELEASE;
springcloud:Greenwich.RELEASE
alibaba-seata:2.1.0.RELEASE

模块组成

父模块+子模块

pom.xml文件 父模块


    4.0.0
    cn.itcast.dtx
    pom
    1.0-SNAPSHOT
    dtx-seata-demo
    
        dtx-seata-demo-bank1
        dtx-seata-demo-bank2
    
    
        2.1.0.RELEASE
    
    
    
        
         
            
                org.springframework.boot
                spring-boot-dependencies
                2.1.3.RELEASE
                pom
                import
            
            
                org.springframework.cloud
                spring-cloud-dependencies
                Greenwich.RELEASE
                pom
                import
            
            
                com.alibaba.cloud
                spring-cloud-alibaba-dependencies
                2.1.0.RELEASE
                pom
                import
            
            
                io.springfox
                springfox-swagger2
                2.9.2
            
            
                io.springfox
                springfox-swagger-ui
                2.9.2
            
            
                org.projectlombok
                lombok
                1.18.0
            
            
                javax.servlet
                javax.servlet-api
                3.1.0
                provided
            
            
                javax.interceptor
                javax.interceptor-api
                1.2
            
            
                mysql
                mysql-connector-java
                5.1.47
            
            
                org.mybatis.spring.boot
                mybatis-spring-boot-starter
                2.0.0
            
            
                com.alibaba
                druid-spring-boot-starter
                1.2.3
            
            
                commons-lang
                commons-lang
                2.6
            
        
    

子模块


    
        dtx-seata-demo
        cn.itcast.dtx
        1.0-SNAPSHOT
    
    4.0.0
    dtx-seata-demo-bank1
    
        
            com.alibaba.cloud
            spring-cloud-alibaba-seata
        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
        
        
            mysql
            mysql-connector-java
        
        
            org.projectlombok
            lombok
        
    

配置

配置如下图:

spring配置

application.yml

spring:
  application:
    name: seata-demo-bank1
  profiles: 
    active: dev
  main:
    allow-bean-definition-overriding: true
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
server:
  servlet:
    context-path: /bank1
feign:
  hystrix:
    enabled: true
  compression:
    request:
      enabled: true # 配置请求GZIP压缩
      mime-types: ["text/xml","application/xml","application/json"] # 配置压缩支持的MIME TYPE
      min-request-size: 2048 # 配置压缩数据大小的下限
    response:
      enabled: true # 配置响应GZIP压缩

上面的配置指定的了dev配置环境,所以这里使用application-dev.yml命名
application-dev.yml

server: 
  port: 56081
spring:
  ##################### DB #####################
  datasource:
    druid:
      url: jdbc:mysql://127.0.0.1:3306/practice?useSSL=false&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.jdbc.Driver
      initialSize: 5
      minIdle: 5
      maxActive: 20
      maxWait: 60000
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT user()
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      poolPreparedStatements: true
      connection-properties: druid.stat.mergeSql:true;druid.stat.slowSqlMillis:5000
logging:
  level:
    root: INFO
    io:
      seata: DEBUG
    org:
      springframework:
        cloud:
          alibaba:
            seata:
              web: DEBUG
seata客户端配置

我们这里使用最简单的file类型来连接seata服务器,所以配置也是要file类型。

registry.conf
registry {
  # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
  # 这里使用file类型连接服务器
  type = "file"
  # 指定file的 配置文件
  file {
    name = "file.conf"
  }
}

config {
  # file、nacos 、apollo、zk
  # 指定file类型
  type = "file"

  file {
    name = "file.conf"
  }
}
file.conf
transport {
  # tcp udt unix-domain-socket
  type = "TCP"
  #NIO NATIVE
  server = "NIO"
  #enable heartbeat
  heartbeat = true
  #thread factory for netty
  thread-factory {
    boss-thread-prefix = "NettyBoss"
    worker-thread-prefix = "NettyServerNIOWorker"
    server-executor-thread-prefix = "NettyServerBizHandler"
    share-boss-worker = false
    client-selector-thread-prefix = "NettyClientSelector"
    client-selector-thread-size = 1
    client-worker-thread-prefix = "NettyClientWorkerThread"
    # netty boss thread size,will not be used for UDT
    boss-thread-size = 1
    #auto default pin or 8
    worker-thread-size = 8
  }
}
## transaction log store
store {
  ## store mode: file、db
  mode = "file"

  ## file store
  file {
    dir = "sessionStore"

    # branch session size , if exceeded first try compress lockkey, still exceeded throws exceptions
    max-branch-session-size = 16384
    # globe session size , if exceeded throws exceptions
    max-global-session-size = 512
    # file buffer size , if exceeded allocate new buffer
    file-write-buffer-cache-size = 16384
    # when recover batch read size
    session.reload.read_size = 100
    # async, sync
    flush-disk-mode = async
  }
}

service {
  #vgroup->rgroup
  # 命名规则是固定的:vgroup_mapping.[springcloud服务名]-fescar-service-group
  # 源码根据这个命名规则获取应用服务的信息
  vgroup_mapping.seata-demo-bank1-fescar-service-group = "default"
  #only support single node,指定seata server的地址
  default.grouplist = "127.0.0.1:8888"
  #degrade current not support
  enableDegrade = false
  #disable
  disable = false
}
client {
  async.commit.buffer.limit = 10000
  lock {
    retry.internal = 10
    retry.times = 30
  }
}
配置seata代理数据源

配置位置如下:

新增DatabaseConfiguration.java,Seata的RM通过DataSourceProxy才能在业务代码的事务提交时,通过这个切入点,与TC进行通信交互、记录undo_log等。

package cn.itcast.dtx.seatademo.bank1.config;

import com.alibaba.druid.pool.DruidDataSource;
import io.seata.rm.datasource.DataSourceProxy;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

@Configuration
public class DatabaseConfiguration {
    private final ApplicationContext applicationContext;

    public DatabaseConfiguration(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DruidDataSource ds0() {
        DruidDataSource druidDataSource = new DruidDataSource();
        return druidDataSource;
    }

    @Primary
    @Bean
    public DataSource dataSource(DruidDataSource ds0)  {
        DataSourceProxy pds0 = new DataSourceProxy(ds0);
        return pds0;
    }
}
启动类修改

注意要把springboot自带的数据源带来排除掉,否则出现配置的代理数据源与springboot自带的形成循环依赖。

package cn.itcast.dtx.seatademo.bank1;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
// 启动是排除springboot自带的数据源配置类
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableFeignClients(basePackages = {"cn.itcast.dtx.seatademo.bank1.spring"})
public class Bank1Server {
    public static void main(String[] args) {
        SpringApplication.run(Bank1Server.class, args);
    }
}
添加undo_log表

该表用来事务回滚,分支事务提交时记录事务相关信息,在分布式事务异常时回滚,分布式事务结束后会删除undo_log的记录。
在spring配置指定的数据库中创建表,每个需要注册到seata server的业务模块都有创建该表,创建语句如下:

-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
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,
    PRIMARY KEY (`id`),
    UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
seata server服务

seata分成服务端和客户端,客户端我们在项目中引入alibaba-seata即可,服务端需要单独部署。
这里只介绍最简单的单独部署,启动模式file。
这里选择版本:seata-server-1.2.0
下载地址:https://github.com/seata/seata/tags

启动
[seata服务端解压路径]/bin/seata-server.bat -p 8888 -m file

8888为服务对外服务端口,这里需要和file.conf中指定的一致;-m file表示以file模式启动。

seata使用示例

这里只简单的演示在一个微服务中,一个分布式事物包含两个分支事物。

创建业务表
CREATE TABLE `t_order` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `order_id` varchar(255) DEFAULT NULL COMMENT '订单',
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_name` varchar(255) DEFAULT NULL COMMENT '用户名称',
  `sex` tinyint(1) DEFAULT '0' COMMENT '性别,0:男,1:女',
  `age` int(3) DEFAULT '0' COMMENT '年龄',
  `address` varchar(255) DEFAULT NULL COMMENT '地址',
  `phone` varchar(11) DEFAULT NULL COMMENT '手机',
  `create_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
事务测试类

代码结构如下:

controller
package cn.itcast.dtx.seatademo.bank1.controller;

import cn.itcast.dtx.seatademo.bank1.service.TestGlobalTransService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;


@RestController
@RequestMapping(value = "/test/")
public class TestGlobalTrans {
    @Resource
    private TestGlobalTransService service;
    @GetMapping("/seataTrans")
    public String testSeataTrans() throws Exception {
        service.testTrans();
        return "success";
    }
}
service
package cn.itcast.dtx.seatademo.bank1.service;

import io.seata.spring.annotation.GlobalTransactional;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;


@Service
public class TestGlobalTransService {

    @Resource
    private TestOnlyTransService service;
	// 只需要添加@GlobalTransactional注解就说明启动了分布式事务
    @GlobalTransactional
    public void testTrans() throws Exception {
    	// 分支事务添加用户信息
        service.insertUser();
        // 分支事务添加订单信息
        service.insertOrder();
        // 抛出异常,事务回滚
        throw new Exception("test exception");
    }
}

负责分支事务的服务类,单独拿出来是因为spring事务代理要求事务方法如果和调用方法放在一个类中,代理不生效,具体原因不在赘述。

package cn.itcast.dtx.seatademo.bank1.service;

import cn.itcast.dtx.seatademo.bank1.dao.UserInfo;
import cn.itcast.dtx.seatademo.bank1.dao.OrderInfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;


@Service
public class TestOnlyTransService {

    @Resource
    private OrderInfo orderInfo;

    @Resource
    private UserInfo userInfo;

    @Transactional(rollbackFor = Exception.class)
    public void insertOrder() {
        orderInfo.insert();
    }

    @Transactional(rollbackFor = Exception.class)
    public void insertUser() {
        userInfo.insert();
    }
}
dao
package cn.itcast.dtx.seatademo.bank1.dao;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;


@Mapper
@Component
public interface OrderInfo {

    @Insert("insert into t_order (order_id,create_time) values ('aaaaaaaaa',now())")
    int insert();
}

@Mapper
@Component
public interface UserInfo {

    @Insert("insert into t_user (user_name,sex,age, create_time) values ('aaaaaaaaa', 0,18,now())")
    int insert();

}
测试结果
  1. 浏览器调用:http://127.0.0.1:56081/bank1/test/seataTrans
  2. 因为 我们的代码中有异常抛出,所有数据库中没有插入数据,事务回滚了。
2022-07-03 11:31:41.697  INFO 13816 --- [atch_RMROLE_1_8] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked
2022-07-03 11:31:41.697 DEBUG 13816 --- [atch_RMROLE_1_8] i.s.core.rpc.netty.RmMessageListener     : branch rollback result:xid=169.254.144.154:8888:2015847663,branchId=2015847665,branchStatus=PhaseTwo_Rollbacked,result code =Success,getMsg =null
2022-07-03 11:31:41.697 DEBUG 13816 --- [atch_RMROLE_1_8] i.s.core.rpc.netty.AbstractRpcRemoting   : send response:xid=169.254.144.154:8888:2015847663,branchId=2015847665,branchStatus=PhaseTwo_Rollbacked,result code =Success,getMsg =null,channel:[id: 0xbf86ca50, L:/127.0.0.1:62389 - R:/127.0.0.1:8888]
2022-07-03 11:31:41.701 DEBUG 13816 --- [lector_RMROLE_1] i.s.core.rpc.netty.AbstractRpcRemoting   : io.seata.core.rpc.netty.RmRpcClient@6e6f2c4f msgId:3, body:xid=169.254.144.154:8888:2015847663,branchId=2015847664,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/practice,applicationData=null
2022-07-03 11:31:41.701  INFO 13816 --- [atch_RMROLE_2_8] i.s.core.rpc.netty.RmMessageListener     : onMessage:xid=169.254.144.154:8888:2015847663,branchId=2015847664,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/practice,applicationData=null
2022-07-03 11:31:41.702  INFO 13816 --- [atch_RMROLE_2_8] io.seata.rm.AbstractRMHandler            : Branch Rollbacking: 169.254.144.154:8888:2015847663 2015847664 jdbc:mysql://127.0.0.1:3306/practice
2022-07-03 11:31:41.708  INFO 13816 --- [atch_RMROLE_2_8] i.s.rm.datasource.undo.UndoLogManager    : xid 169.254.144.154:8888:2015847663 branch 2015847664, undo_log deleted with GlobalFinished
2022-07-03 11:31:41.709  INFO 13816 --- [atch_RMROLE_2_8] io.seata.rm.AbstractRMHandler            : Branch Rollbacked result: PhaseTwo_Rollbacked
2022-07-03 11:31:41.709 DEBUG 13816 --- [atch_RMROLE_2_8] i.s.core.rpc.netty.RmMessageListener     : branch rollback result:xid=169.254.144.154:8888:2015847663,branchId=2015847664,branchStatus=PhaseTwo_Rollbacked,result code =Success,getMsg =null
2022-07-03 11:31:41.709 DEBUG 13816 --- [atch_RMROLE_2_8] i.s.core.rpc.netty.AbstractRpcRemoting   : send response:xid=169.254.144.154:8888:2015847663,branchId=2015847664,branchStatus=PhaseTwo_Rollbacked,result code =Success,getMsg =null,channel:[id: 0xbf86ca50, L:/127.0.0.1:62389 - R:/127.0.0.1:8888]
2022-07-03 11:31:41.714 DEBUG 13816 --- [io-56081-exec-4] io.seata.core.context.RootContext        : unbind 169.254.144.154:8888:2015847663
2022-07-03 11:31:41.714  INFO 13816 --- [io-56081-exec-4] i.seata.tm.api.DefaultGlobalTransaction  : [169.254.144.154:8888:2015847663] rollback status:Rollbacked
2022-07-03 11:31:41.734 ERROR 13816 --- [io-56081-exec-4] o.a.c.c.C.[.[.[.[dispatcherServlet]      : Servlet.service() for servlet [dispatcherServlet] in context with path [/bank1] threw exception [Request processing failed; nested exception is java.lang.Exception: test exception] with root cause

java.lang.Exception: test exception
	at cn.itcast.dtx.seatademo.bank1.service.TestGlobalTransService.testTrans(TestGlobalTransService.java:24) ~[classes/:na]
	at cn.itcast.dtx.seatademo.bank1.service.TestGlobalTransService$$FastClassBySpringCGLIB$$dcfd65aa.invoke() ~[classes/:na]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:749) ~[spring-aop-5.1.5.RELEASE.jar:5.1.5.RELEASE]
  1. 修改代码,删除异常,数据保存成功。
  @GlobalTransactional
    public void testTrans() throws Exception {
        service.insertUser();
        service.insertOrder();
//        throw new Exception("test exception");
    }


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1003918.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号