spring boot:使用分布式事務seata(druid 1.1.23 / seata 1.3.0 / mybatis / spring boot 2.3.2)


一,什么是seata?

Seata:Simpe Extensible Autonomous Transcaction Architecture,
是阿里中間件開源的分布式事務解決方案。
前身是阿里的Fescar
 
官方站:
http://seata.io/zh-cn/ 
官方代碼地址:
https://github.com/seata/seata 
官方文檔站:
http://seata.io/zh-cn/docs/overview/what-is-seata.html
各版本的release下載地址:
https://github.com/seata/seata/releases

 

說明:劉宏締的架構森林是一個專注架構的博客,地址:https://www.cnblogs.com/architectforest

         對應的源碼可以訪問這里獲取: https://github.com/liuhongdi/

說明:作者:劉宏締 郵箱: 371125307@qq.com

 

二,seata-server的安裝:

參見:
https://www.cnblogs.com/architectforest/p/13507695.html

 

三,演示項目的相關信息

1,項目地址:

https://github.com/liuhongdi/seata

 

2,功能說明:

    分別實現了:同一個項目中不同數據庫間的分布式事務

                      用resttemplate訪問不同url的分布式事務

    

3,項目結構:

 

四,配置文件說明

1,pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
       <!--seata   begin-->
        <dependency>
            <groupId>io.seata</groupId>
            <artifactId>seata-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>
        <!--seata   end-->
        <!--druid begin-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.23</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.4.2</version>
        </dependency>
        <!--druid   end-->
        <!--mybatis begin-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--mybatis end-->
        <!--mysql begin-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--mysql end-->

 

2,application.properties

#error
server.error.include-stacktrace=always
#error log
logging.level.org.springframework.web=trace
#app name
spring.application.name = txtest

#   數據源goodsdb基本配置
spring.datasource.druid.goodsdb.username = root
spring.datasource.druid.goodsdb.password = lhddemo
spring.datasource.druid.goodsdb.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.druid.goodsdb.url = jdbc:mysql://127.0.0.1:3306/store?useAffectedRows=true&serverTimezone=UTC
spring.datasource.druid.goodsdb.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.goodsdb.initialSize = 5
spring.datasource.druid.goodsdb.minIdle = 5
spring.datasource.druid.goodsdb.maxActive = 20
spring.datasource.druid.goodsdb.maxWait = 60000
spring.datasource.druid.goodsdb.timeBetweenEvictionRunsMillis = 60000
spring.datasource.druid.goodsdb.minEvictableIdleTimeMillis = 300000
spring.datasource.druid.goodsdb.validationQuery = SELECT 1 FROM DUAL
spring.datasource.druid.goodsdb.testWhileIdle = true
spring.datasource.druid.goodsdb.testOnBorrow = false
spring.datasource.druid.goodsdb.testOnReturn = false
spring.datasource.druid.goodsdb.poolPreparedStatements = true
#   數據源orderdb基本配置
spring.datasource.druid.orderdb.username = root
spring.datasource.druid.orderdb.password = lhddemo
spring.datasource.druid.orderdb.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.druid.orderdb.url = jdbc:mysql://127.0.0.1:3306/orderdb?useAffectedRows=true&serverTimezone=UTC
spring.datasource.druid.orderdb.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.orderdb.initialSize = 5
spring.datasource.druid.orderdb.minIdle = 5
spring.datasource.druid.orderdb.maxActive = 20
spring.datasource.druid.orderdb.maxWait = 60000
spring.datasource.druid.orderdb.timeBetweenEvictionRunsMillis = 60000
spring.datasource.druid.orderdb.minEvictableIdleTimeMillis = 300000
spring.datasource.druid.orderdb.validationQuery = SELECT 1 FROM DUAL
spring.datasource.druid.orderdb.testWhileIdle = true
spring.datasource.druid.orderdb.testOnBorrow = false
spring.datasource.druid.orderdb.testOnReturn = false
spring.datasource.druid.orderdb.poolPreparedStatements = true

#配置監控統計攔截的filters
#stat:監控統計sql
#'wall':sql防火牆
spring.datasource.druid.filters = stat,wall,log4j2
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize = 20
spring.datasource.druid.useGlobalDataSourceStat = true
spring.datasource.druid.connectionProperties = druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

#druid sql firewall monitor
spring.datasource.druid.filter.wall.enabled=true

#druid sql monitor
spring.datasource.druid.filter.stat.enabled=true
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=10000
spring.datasource.druid.filter.stat.merge-sql=true

#druid uri monitor
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*

#druid session monitor
spring.datasource.druid.web-stat-filter.session-stat-enable=true
spring.datasource.druid.web-stat-filter.profile-enable=true

#druid spring monitor
spring.datasource.druid.aop-patterns=com.druid.*

#monintor,druid login user config
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.login-username=root
spring.datasource.druid.stat-view-servlet.login-password=root

#mybatis
mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
mybatis.type-aliases-package=com.example.demo.mapper
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#log
logging.config = classpath:log4j2.xml

#seata
seata.enabled=true
seata.application-id=txtest
seata.tx-service-group=txtest-group
seata.service.vgroup-mapping.txtest-group=default
seata.service.grouplist.default=127.0.0.1:8091
seata.client.undo.log-serialization=jackson
seata.client.undo.log-table=undo_log

說明:因為涉及到兩個數據源,spring.datasource.druid用來供生成數據源使用

         seata的配置要注意:

          seata.application-id=txtest: 通常與應用程序的名字(spring.application.name)一致:

          seata.service.grouplist.default的值要和seata server服務的ip:端口一致

         seata.tx-service-group 用來指定所屬事務的分組,一台seata server 可管理多個事務組,

        seata.service.vgroup-mapping.txtest-group=default:把服務組命名為default

        seata.service.grouplist.default=127.0.0.1:8091:指定服務組的server地址和端口

 

3, log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF">
<appenders>
    <Console name="Console" target="SYSTEM_OUT">
        <!--只接受程序中DEBUG級別的日志進行處理-->
        <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
        <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%file:%line] %-5level %logger{35} - %msg %n"/>
    </Console>
    <!--處理INFO級別的日志,並把該日志放到logs/info.log文件中-->
    <RollingFile name="RollingFileInfo" fileName="./logs/info.log"
                 filePattern="logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
        <Filters>
            <ThresholdFilter level="INFO"/>
            <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
        </Filters>
        <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%file:%line] %-5level %logger{35} - %msg %n"/>
        <Policies>
            <SizeBasedTriggeringPolicy size="500 MB"/>
            <TimeBasedTriggeringPolicy/>
        </Policies>
    </RollingFile>
    <!--處理WARN級別的日志,並把該日志放到logs/warn.log文件中-->
    <RollingFile name="RollingFileWarn" fileName="./logs/warn.log"
                 filePattern="logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
        <Filters>
            <ThresholdFilter level="WARN"/>
            <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
        </Filters>
        <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%file:%line] %-5level %logger{35} - %msg %n"/>
        <Policies>
            <SizeBasedTriggeringPolicy size="500 MB"/>
            <TimeBasedTriggeringPolicy/>
        </Policies>
    </RollingFile>
    <!--處理error級別的日志,並把該日志放到logs/error.log文件中-->
    <RollingFile name="RollingFileError" fileName="./logs/error.log"
                 filePattern="logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
        <ThresholdFilter level="ERROR"/>
        <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%file:%line] %-5level %logger{35} - %msg %n"/>
        <Policies>
            <SizeBasedTriggeringPolicy size="500 MB"/>
            <TimeBasedTriggeringPolicy/>
        </Policies>
    </RollingFile>
    <!--druid的日志記錄追加器-->
    <RollingFile name="druidSqlRollingFile" fileName="./logs/druid-sql.log"
                 filePattern="logs/$${date:yyyy-MM}/api-%d{yyyy-MM-dd}-%i.log.gz">
        <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%file:%line] %-5level %logger{35} - %msg %n"/>
        <Policies>
            <SizeBasedTriggeringPolicy size="500 MB"/>
            <TimeBasedTriggeringPolicy/>
        </Policies>
    </RollingFile>
</appenders>
<loggers>
    <AsyncRoot level="info">
        <appender-ref ref="Console"/>
        <appender-ref ref="RollingFileInfo"/>
        <appender-ref ref="RollingFileWarn"/>
        <appender-ref ref="RollingFileError"/>
    </AsyncRoot>
    <!--記錄druid-sql的記錄-->
    <AsyncLogger name="druid.sql.Statement" level="debug" additivity="false">
        <appender-ref ref="druidSqlRollingFile"/>
    </AsyncLogger>
</loggers>
</configuration>

 

4,    兩個數據庫中的數據表:

goods表

復制代碼
CREATE TABLE `goods` (
 `goodsId` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
 `goodsName` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '' COMMENT 'name',
 `subject` varchar(200) NOT NULL DEFAULT '' COMMENT '標題',
 `price` decimal(15,2) NOT NULL DEFAULT '0.00' COMMENT '價格',
 `stock` int(11) NOT NULL DEFAULT '0' COMMENT 'stock',
 PRIMARY KEY (`goodsId`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='商品表'
復制代碼

goods表中的數據:

INSERT INTO `goods` (`goodsId`, `goodsName`, `subject`, `price`, `stock`) VALUES
(3, '100分電動牙刷', '好用到讓你愛上刷牙', '59.00', 100);

order表:

復制代碼
CREATE TABLE `orderinfo` (
 `orderId` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
 `orderSn` varchar(100) NOT NULL DEFAULT '' COMMENT '編號',
 `orderTime` timestamp NOT NULL DEFAULT '1971-01-01 00:00:01' COMMENT '下單時間',
 `orderStatus` tinyint(4) NOT NULL DEFAULT '0' COMMENT '狀態:0,未支付,1,已支付,2,已發貨,3,已退貨,4,已過期',
 `userId` int(12) NOT NULL DEFAULT '0' COMMENT '用戶id',
 `price` decimal(10,0) NOT NULL DEFAULT '0' COMMENT '價格',
 `addressId` int(12) NOT NULL DEFAULT '0' COMMENT '地址',
 PRIMARY KEY (`orderId`),
 UNIQUE KEY `orderSn` (`orderSn`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='訂單表'
復制代碼

 

5,在每個庫中創建seata回滾數據時要用到的undo_log數據表

CREATE TABLE `undo_log` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'increment id',
 `branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
 `xid` varchar(100) NOT NULL COMMENT 'global transaction id',
 `context` varchar(128) NOT NULL COMMENT 'undo_log context,such as serialization',
 `rollback_info` longblob NOT NULL COMMENT 'rollback info',
 `log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
 `log_created` datetime NOT NULL COMMENT 'create datetime',
 `log_modified` datetime NOT NULL COMMENT 'modify datetime',
 PRIMARY KEY (`id`),
 UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COMMENT='AT transaction mode undo table'

 

五,java代碼說明

1,GoodsdbSourceConfig.java

@Configuration
@MapperScan(basePackages = "com.seata.demo.mapper.goodsdb", sqlSessionTemplateRef = "goodsdbSqlSessionTemplate")
public class GoodsdbSourceConfig {

    @Bean
    @Primary
    @ConfigurationProperties("spring.datasource.druid.goodsdb")
    public DataSource goodsdbDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public SqlSessionFactory goodsdbSqlSessionFactory(@Qualifier("goodsdbDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        //bean.setDataSource();
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/goodsdb/*.xml"));
        return bean.getObject();
    }

    @Bean
    @Primary
    public DataSourceTransactionManager goodsdbTransactionManager(@Qualifier("goodsdbDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    @Primary
    public SqlSessionTemplate goodsdbSqlSessionTemplate(@Qualifier("goodsdbSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

 

2,OrderdbSourceConfig.java

@Configuration
@MapperScan(basePackages = "com.seata.demo.mapper.orderdb", sqlSessionTemplateRef = "orderdbSqlSessionTemplate")
public class OrderdbSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid.orderdb")
    public DataSource orderdbDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    public SqlSessionFactory orderdbSqlSessionFactory(@Qualifier("orderdbDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/orderdb/*.xml"));
        return bean.getObject();
    }

    @Bean
    public DataSourceTransactionManager orderdbTransactionManager(@Qualifier("orderdbDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public SqlSessionTemplate orderdbSqlSessionTemplate(@Qualifier("orderdbSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

說明:以上分別為goodsdb/orderdb創建兩個數據源

 

3,GoodsController.java

@RestController
@RequestMapping("/goods")
public class GoodsController {

    private static final String SUCCESS = "SUCCESS";
    private static final String FAIL = "FAIL";

    @Resource
    private GoodsMapper goodsMapper;

    //更新商品庫存 參數:商品id,數量
    @RequestMapping("/goodsstock/{goodsId}/{count}")
    @ResponseBody
    public String goodsStock(@PathVariable Long goodsId,
                            @PathVariable int count) {
         int res = goodsMapper.updateGoodsStock(goodsId,count);
         System.out.println("res:"+res);

         if (res>0) {
             return SUCCESS;
         } else {
             return FAIL;
         }
    }
}

rest方式調用訪問goodsdb庫時使用

 

4,OrderController.java

@RestController
@RequestMapping("/order")
public class OrderController {

    private static final String SUCCESS = "SUCCESS";
    private static final String FAIL = "FAIL";

    @Resource
    private OrderMapper orderMapper;

    //添加訂單:參數:商品id,數量
    @RequestMapping("/orderadd/{goodsId}/{count}")
    @ResponseBody
    public String orderAdd(@PathVariable Long goodsId,
                             @PathVariable int count) {
        Order order = new Order();
        //得到sn
        String orderSn = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
        order.setOrderSn(orderSn);
        order.setOrderStatus(0);
        order.setPrice(new BigDecimal(100.00));
        order.setUserId(8);

        int orderId = orderMapper.insertOneOrder(order);
        System.out.println("orderId:"+order.getOrderId());

        if (orderId>0) {
            return SUCCESS;
        } else {
            return FAIL;
        }
    }
}

rest方式訪問orderdb庫時使用

 

5,HomeController.java

@RestController
@RequestMapping("/home")
public class HomeController {

    private static final String SUCCESS = "SUCCESS";
    private static final String FAIL = "FAIL";

    @Resource
    private OrderMapper orderMapper;

    @Resource
    private GoodsMapper goodsMapper;

    //添加一個訂單,訪問兩個數據庫
    @GlobalTransactional(timeoutMills = 300000,rollbackFor = Exception.class)
    @GetMapping("/addorderseata")
    public String addOrderSeata(@RequestParam(value="isfail",required = true,defaultValue = "0") int isFail) {

        String goodsId = "3";
        String goodsNum = "1";

        Order order = new Order();
        //生成訂單
        String orderSn = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
        order.setOrderSn(orderSn);
        order.setOrderStatus(0);
        order.setPrice(new BigDecimal(100.00));
        order.setUserId(8);
        int orderId = orderMapper.insertOneOrder(order);
        System.out.println("orderId:"+order.getOrderId());
        //更新商品庫存
        int count = -1;
        int res = goodsMapper.updateGoodsStock(Long.parseLong(goodsId),count);
        System.out.println("res:"+res);
        //測試失敗的情況
        if (isFail == 1) {
            int divide = 0;
            int resul = 100 / divide;
        }

        if (res>0) {
            return SUCCESS;
        } else {
            return FAIL;
        }
    }


    //添加一個訂單,訪問兩個url(分別訪問不同的數據庫)
    @GlobalTransactional(timeoutMills = 300000,rollbackFor = Exception.class)
    @GetMapping("/addorderseatarest")
    public String addOrderSeataRest(@RequestParam(value="isfail",required = true,defaultValue = "0") int isFail) {
        String goodsId = "3";
        String goodsNum = "1";
        //得到事務的xid
        RestTemplate restTemplate = new RestTemplate();
        String xid = RootContext.getXID();
        System.out.println("xid before send:"+xid);
        if (StringUtils.isEmpty(xid)) {
            System.out.println("xid is null,return");
            return FAIL;
        }
        //把事務的xid添加到header
        HttpHeaders headers = new HttpHeaders();
        headers.add(RootContext.KEY_XID, xid);
        System.out.println("xid not null");
        //生成訂單,傳遞xid
        String urlAddOrder = "http://127.0.0.1:8080/order/orderadd/"+goodsId+"/"+goodsNum+"/";
        String resultAdd = restTemplate.postForObject(urlAddOrder,new HttpEntity<String>(headers),String.class);
        if (!SUCCESS.equals(resultAdd)) {
            throw new RuntimeException();
        }
        //更新商品庫存,傳遞xid
        String goodsUPNum = "-1";
        String urlUpStock = "http://127.0.0.1:8080/goods/goodsstock/"+goodsId+"/"+goodsUPNum+"/";
        String resultUp = restTemplate.postForObject(urlUpStock,new HttpEntity<String>(headers),String.class);
        if (!SUCCESS.equals(resultUp)) {
            throw new RuntimeException();
        }
        //測試失敗的情況
        if (isFail == 1) {
            int divide = 0;
            int resul = 100 / divide;
        }
        return SUCCESS;
    }
}

需要注意的地方在於:用resttemplate訪問其他url時,需要用xid傳遞全局事務

否則事務會不生效

 

6,SeataFilter.java

@Component
public class SeataFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        String xid = req.getHeader(RootContext.KEY_XID.toLowerCase());
        System.out.println("xid:"+xid);
        boolean isBind = false;
        if (StringUtils.isNotBlank(xid)) {
            //如果xid不為空,則RootContext需要綁定xid,
            //供seata識別這是同一個分布式事務
            RootContext.bind(xid);
            isBind = true;
        }
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            if (isBind) {
                RootContext.unbind();
            }
        }
    }
    @Override
    public void destroy() {
    }
}

這個過濾器主要用來保存xid值

如果使用seata的spring-cloud starter包,則不需要做這個工作

 

7,GoodsMapper.java

@Repository
@Mapper
public interface GoodsMapper {
    //更新庫存
    int updateGoodsStock(@Param("goodsId") Long goodsId, @Param("changeAmount") int changeAmount);
}

 

8,OrderMapper.java

@Repository
@Mapper
public interface OrderMapper {
    //插入一條訂單
    int insertOneOrder(Order order);
}

 

9,Goods.java/Order.java/GoodsMapper.xml/OrderMapper.xml
   為節省篇幅,這些代碼請從github上查看

 

六,測試效果

1,添加一個訂單:
先測試事務成功的情況:
查看orderdb中undo_log表的下一個自增值:
下一個自增值 70
id為3的商品當前庫存:100
 
訪問:
http://127.0.0.1:8080/home/addorderseata

orderinfo表中增加了一條訂單記錄
查看id為3的商品庫存:99

查看控制台:
2020-08-19 18:04:24.962 [http-nio-8080-exec-2] [:] INFO  io.seata.tm.TransactionManagerHolder - TransactionManager Singleton io.seata.tm.DefaultTransactionManager@6dc18e9e 
2020-08-19 18:04:25.016 [http-nio-8080-exec-2] [:] INFO  io.seata.tm.api.DefaultGlobalTransaction - Begin new global transaction [192.168.3.163:8091:39410791815843840] 
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5f4f6f65] was not registered for synchronization because synchronization is not active
JDBC Connection [io.seata.rm.datasource.ConnectionProxy@5ef86b80] will not be managed by Spring
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5f4f6f65]
orderId:96
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@624dddb2] was not registered for synchronization because synchronization is not active
JDBC Connection [io.seata.rm.datasource.ConnectionProxy@307310b5] will not be managed by Spring
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@624dddb2]
res:1
2020-08-19 18:04:25.991 [http-nio-8080-exec-2] [:] INFO  io.seata.tm.api.DefaultGlobalTransaction - [192.168.3.163:8091:39410791815843840] commit status: Committed 
2020-08-19 18:04:26.045 [http-nio-8080-exec-2] [AbstractMessageConverterMethodProcessor.java:265] DEBUG org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor - Using 'text/html', given [text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json] 
2020-08-19 18:04:26.074 [http-nio-8080-exec-2] [FrameworkServlet.java:1131] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK, headers={masked} 
2020-08-19 18:04:26.622 [rpcDispatch_RMROLE_1_1_4] [:] INFO  io.seata.core.rpc.processor.client.RmBranchCommitProcessor - rm client handle branch commit process:xid=192.168.3.163:8091:39410791815843840,branchId=39410794303066112,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/orderdb,applicationData=null 
2020-08-19 18:04:26.626 [rpcDispatch_RMROLE_1_1_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch committing: 192.168.3.163:8091:39410791815843840 39410794303066112 jdbc:mysql://127.0.0.1:3306/orderdb null 
2020-08-19 18:04:26.627 [rpcDispatch_RMROLE_1_1_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch commit result: PhaseTwo_Committed 
2020-08-19 18:04:26.642 [rpcDispatch_RMROLE_1_2_4] [:] INFO  io.seata.core.rpc.processor.client.RmBranchCommitProcessor - rm client handle branch commit process:xid=192.168.3.163:8091:39410791815843840,branchId=39410795796238336,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/store,applicationData=null 
2020-08-19 18:04:26.643 [rpcDispatch_RMROLE_1_2_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch committing: 192.168.3.163:8091:39410791815843840 39410795796238336 jdbc:mysql://127.0.0.1:3306/store null 
2020-08-19 18:04:26.643 [rpcDispatch_RMROLE_1_2_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch commit result: PhaseTwo_Committed 

可以看到GlobalTransaction的begin 和commit 記錄

 
測試失敗的測試:訪問:
http://127.0.0.1:8080/home/addorderseata?isfail=1

在這里發生了一次除0錯,

查看orderinfo表:記錄未插入

查看id為3的商品庫存:99,沒發生變化

查看orderdb中undo_log表的下一個自增值:
下一個自增值    72
查看控制台:
2020-08-19 18:10:30.582 [http-nio-8080-exec-5] [:] INFO  io.seata.tm.api.DefaultGlobalTransaction - Begin new global transaction [192.168.3.163:8091:39412325219831808] 
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5de874f6] was not registered for synchronization because synchronization is not active
2020-08-19 18:10:30.595 [http-nio-8080-exec-5] [:] ERROR com.alibaba.druid.pool.DruidAbstractDataSource - discard long time none received connection. , jdbcUrl : jdbc:mysql://127.0.0.1:3306/orderdb?useAffectedRows=true&serverTimezone=UTC, jdbcUrl : jdbc:mysql://127.0.0.1:3306/orderdb?useAffectedRows=true&serverTimezone=UTC, lastPacketReceivedIdleMillis : 362967 
JDBC Connection [io.seata.rm.datasource.ConnectionProxy@723d744f] will not be managed by Spring
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5de874f6]
orderId:97
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@460d292b] was not registered for synchronization because synchronization is not active
2020-08-19 18:10:30.717 [http-nio-8080-exec-5] [:] ERROR com.alibaba.druid.pool.DruidAbstractDataSource - discard long time none received connection. , jdbcUrl : jdbc:mysql://127.0.0.1:3306/store?useAffectedRows=true&serverTimezone=UTC, jdbcUrl : jdbc:mysql://127.0.0.1:3306/store?useAffectedRows=true&serverTimezone=UTC, lastPacketReceivedIdleMillis : 363066 
JDBC Connection [io.seata.rm.datasource.ConnectionProxy@1f25e694] will not be managed by Spring
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@460d292b]
res:1
2020-08-19 18:10:30.812 [rpcDispatch_RMROLE_1_3_4] [:] INFO  io.seata.core.rpc.processor.client.RmBranchRollbackProcessor - rm handle branch rollback process:xid=192.168.3.163:8091:39412325219831808,branchId=39412325991583744,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/store,applicationData=null 
2020-08-19 18:10:30.813 [rpcDispatch_RMROLE_1_3_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch Rollbacking: 192.168.3.163:8091:39412325219831808 39412325991583744 jdbc:mysql://127.0.0.1:3306/store 
2020-08-19 18:10:30.962 [rpcDispatch_RMROLE_1_3_4] [:] INFO  io.seata.rm.datasource.undo.AbstractUndoLogManager - xid 192.168.3.163:8091:39412325219831808 branch 39412325991583744, undo_log deleted with GlobalFinished 
2020-08-19 18:10:30.964 [rpcDispatch_RMROLE_1_3_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch Rollbacked result: PhaseTwo_Rollbacked 
2020-08-19 18:10:30.976 [rpcDispatch_RMROLE_1_4_4] [:] INFO  io.seata.core.rpc.processor.client.RmBranchRollbackProcessor - rm handle branch rollback process:xid=192.168.3.163:8091:39412325219831808,branchId=39412325689593856,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/orderdb,applicationData=null 
2020-08-19 18:10:30.976 [rpcDispatch_RMROLE_1_4_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch Rollbacking: 192.168.3.163:8091:39412325219831808 39412325689593856 jdbc:mysql://127.0.0.1:3306/orderdb 
2020-08-19 18:10:31.012 [rpcDispatch_RMROLE_1_4_4] [:] INFO  io.seata.rm.datasource.undo.AbstractUndoLogManager - xid 192.168.3.163:8091:39412325219831808 branch 39412325689593856, undo_log deleted with GlobalFinished 
2020-08-19 18:10:31.014 [rpcDispatch_RMROLE_1_4_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch Rollbacked result: PhaseTwo_Rollbacked 
2020-08-19 18:10:31.035 [http-nio-8080-exec-5] [:] INFO  io.seata.tm.api.DefaultGlobalTransaction - [192.168.3.163:8091:39412325219831808] rollback status: Rollbacked 

可以看到事務的begin和rollback

 
 
2,用rest方式測試事務的執行:
  訪問:
http://127.0.0.1:8080/home/addorderseatarest?isfail=1

可以觀察到undo_log中自增值的變化和控制台的輸出
與上一個例子基本一致,大家自己觀察效果即可

 

七,查看spring boot 的版本:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.2.RELEASE)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM