spring boot:用dynamic-datasource-spring-boot-starter配置多數據源訪問seata(seata 1.3.0 / spring boot 2.3.3)


一,dynamic-datasource-spring-boot-starter的優勢?

1,dynamic-datasource-spring-boot-starter 是一個基於springboot的快速集成多數據源的啟動器

它由苞米豆團隊出品,集成多數據源時非常方便

 

2,官方站及文檔:

官方站

https://mybatis.plus/

官方代碼站:

https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter

官方文檔站:

https://mybatis.plus/guide/dynamic-datasource.html

 

3,seata的用途:

Seata:Simpe Extensible Autonomous Transcaction Architecture,
是阿里中間件,開源的分布式事務解決方案
官方站:
http://seata.io/zh-cn/

 

說明:劉宏締的架構森林是一個專注架構的博客,地址: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/dynamicseata

 

2,項目功能說明:

        用dynamic-datasource-spring-boot-starter整合兩個數據源+mybatis+druid+seata實現分布式事務

 

3,項目結構:如圖:

 

 

4,用到的數據庫:

 

四,配置文件說明

 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.1.0</version>
        </dependency>
        <!--dynamic datasource begin-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!--dynamic datasource   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
logging.level.org.springframework.web=trace
#name
spring.application.name = my_test_tx
# orderdb設置為主數據源
spring.datasource.dynamic.primary = orderdb
spring.datasource.dynamic.seata = true
# orderdb數據源配置
spring.datasource.dynamic.datasource.orderdb.url = jdbc:mysql://127.0.0.1:3306/orderdb?useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.datasource.dynamic.datasource.orderdb.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.orderdb.username = root
spring.datasource.dynamic.datasource.orderdb.password = lhddemo
spring.datasource.dynamic.datasource.orderdb.type= com.alibaba.druid.pool.DruidDataSource
spring.datasource.dynamic.datasource.orderdb.druid.initial-size=5
spring.datasource.dynamic.datasource.orderdb.druid.max-active=20
spring.datasource.dynamic.datasource.orderdb.druid.min-idle=5
spring.datasource.dynamic.datasource.orderdb.druid.max-wait=60000
spring.datasource.dynamic.datasource.orderdb.druid.min-evictable-idle-time-millis=300000
spring.datasource.dynamic.datasource.orderdb.druid.max-evictable-idle-time-millis=300000
spring.datasource.dynamic.datasource.orderdb.druid.time-between-eviction-runs-millis=60000
spring.datasource.dynamic.datasource.orderdb.druid.validation-query=select 1
spring.datasource.dynamic.datasource.orderdb.druid.validation-query-timeout=-1
spring.datasource.dynamic.datasource.orderdb.druid.test-on-borrow=false
spring.datasource.dynamic.datasource.orderdb.druid.test-on-return=false
spring.datasource.dynamic.datasource.orderdb.druid.test-while-idle=true
spring.datasource.dynamic.datasource.orderdb.druid.pool-prepared-statements=true
spring.datasource.dynamic.datasource.orderdb.druid.filters=stat,wall,log4j2
spring.datasource.dynamic.datasource.orderdb.druid.share-prepared-statements=true
# goodsdb數據源配置
spring.datasource.dynamic.datasource.goodsdb.url = jdbc:mysql://127.0.0.1:3306/store?useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.datasource.dynamic.datasource.goodsdb.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.goodsdb.username = root
spring.datasource.dynamic.datasource.goodsdb.password = lhddemo
spring.datasource.dynamic.datasource.goodsdb.type= com.alibaba.druid.pool.DruidDataSource
spring.datasource.dynamic.datasource.goodsdb.druid.initial-size=5
spring.datasource.dynamic.datasource.goodsdb.druid.max-active=20
spring.datasource.dynamic.datasource.goodsdb.druid.min-idle=5
spring.datasource.dynamic.datasource.goodsdb.druid.max-wait=60000
spring.datasource.dynamic.datasource.goodsdb.druid.min-evictable-idle-time-millis=300000
spring.datasource.dynamic.datasource.goodsdb.druid.max-evictable-idle-time-millis=300000
spring.datasource.dynamic.datasource.goodsdb.druid.time-between-eviction-runs-millis=60000
spring.datasource.dynamic.datasource.goodsdb.druid.validation-query=select 1
spring.datasource.dynamic.datasource.goodsdb.druid.validation-query-timeout=-1
spring.datasource.dynamic.datasource.goodsdb.druid.test-on-borrow=false
spring.datasource.dynamic.datasource.goodsdb.druid.test-on-return=false
spring.datasource.dynamic.datasource.goodsdb.druid.test-while-idle=true
spring.datasource.dynamic.datasource.goodsdb.druid.pool-prepared-statements=true
spring.datasource.dynamic.datasource.goodsdb.druid.filters=stat,wall,log4j2
spring.datasource.dynamic.datasource.goodsdb.druid.share-prepared-statements=true
#   配置監控統計攔截的filters,去掉后監控界面sql無法統計,'wall'用於防火牆
#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
# IP白名單 (沒有配置或者為空,則允許所有訪問)
spring.datasource.druid.stat-view-servlet.allow = 127.0.0.1,192.168.163.1
# IP黑名單 (存在共同時,deny優先於allow)
spring.datasource.druid.stat-view-servlet.deny = 192.168.10.1
#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.application-id=my_test_tx
seata.tx-service-group=my_test_tx_group

seata.service.vgroup-mapping.my_test_tx_group=default
seata.service.grouplist.default=127.0.0.1:8091

說明:因為是用dynamic-datasource來整合seata,需要配置:

         spring.datasource.dynamic.seata = true

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

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

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

 

3,數據庫的相關業務表:

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='訂單表'
復制代碼

 每個庫中seata要使用的事務恢復日志表:

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,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() {
    }
}

通過rest方式訪問url時,分布式事務需要傳遞事務的xid

 

2,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
    @DS("goodsdb")
    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;
         }
    }

    //商品詳情 參數:商品id
    @GetMapping("/goodsinfo")
    @ResponseBody
    @DS("goodsdb")
    public Goods goodsInfo(@RequestParam(value="goodsid",required = true,defaultValue = "0") Long goodsId) {
        Goods goods = goodsMapper.selectOneGoods(goodsId);
        return goods;
    }
}

 

3,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);
        if (orderId>0) {
            return SUCCESS;
        } else {
            return FAIL;
        }
    }
    
    //訂單詳情,參數:訂單id
    @GetMapping("/orderinfo")
    @ResponseBody
    public Order orderInfo(@RequestParam(value="orderid",required = true,defaultValue = "0") Long orderId) {
        Order order = orderMapper.selectOneOrder(orderId);
        return order;
    }
}

 

4,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 GoodsService goodsService;

    //添加一個訂單,直接訪問數據庫
    @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);
        //修改數據庫中商品的庫存
        int goodsUPNum = -1;
        String res = goodsService.goodsStock(Long.parseLong(goodsId),goodsUPNum);
        //是否要引發異常
        if (isFail == 1) {
            int divide = 0;
            int resul = 100 / divide;
        }
        return SUCCESS;
    }

    //添加一個訂單,rest訪問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";
        RestTemplate restTemplate = new RestTemplate();
        
        //得到事務的xid
        String xid = RootContext.getXID();
        System.out.println("xid before send:"+xid);
        if (StringUtils.isEmpty(xid)) {
            System.out.println("xid is null,return");
            return FAIL;
        }

        //增加一條訂單的記錄
        HttpHeaders headers = new HttpHeaders();
        headers.add(RootContext.KEY_XID, xid);
        System.out.println("xid not null");
        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();
        }

        //修改數據庫中商品的庫存
        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;
    }
}

說明:@GlobalTransactional 注解用來生成分布式事務

         @DS注解用來指定goodsdb這個庫,

         因為orderdb被設置為了primary,所以無需指定

 

5,DemoApplication.java

@SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

說明:因為我們使用了druid-spring-boot-starter依賴包,

         druid會自動檢查數據庫的url配置,而我們使用了多個數據源,

         所以要exclude掉DruidDataSourceAutoConfigure這個class

 

6,GoodsService.java

@Service
public class GoodsService {

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

    @Resource
    private GoodsMapper goodsMapper;
    
//更新數據庫 @DS(
"goodsdb") public String goodsStock(Long goodsId, int count) { int res = goodsMapper.updateGoodsStock(goodsId,count); System.out.println("res:"+res); if (res>0) { return SUCCESS; } else { return FAIL; } } }

說明:要用DS注解指定goodsdb這個庫

 

7, OrderMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dynamicseata.demo.mapper.orderdb.OrderMapper">
    <select id="selectOneOrder" parameterType="long" resultType="com.dynamicseata.demo.pojo.Order">
        select * from orderinfo where orderId=#{orderId}
    </select>
    <insert id="insertOneOrder" parameterType="com.dynamicseata.demo.pojo.Order" useGeneratedKeys="true" keyProperty="orderId" >
        insert into orderinfo(orderSn,orderTime,orderStatus,userId,price)
        values(
            #{orderSn},now(),#{orderStatus},#{userId},#{price}
        )
     </insert>
</mapper>

 

8,GoodsMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dynamicseata.demo.mapper.goodsdb.GoodsMapper">
    <select id="selectOneGoods" parameterType="long" resultType="com.dynamicseata.demo.pojo.Goods">
        select * from goods where goodsId=#{goodsId}
    </select>
    <update id="updateGoodsStock">
        UPDATE goods SET
        stock = stock+#{changeAmount,jdbcType=INTEGER}
        WHERE goodsId = #{goodsId,jdbcType=BIGINT}
    </update>
</mapper>

 

9,Goods.java,Order.java,GoodsMapper.java,OrderMapper.java
  等代碼,可以訪問github.com

 

六,測試效果

1,測試seata訪問兩個庫的事務效果:

先查看goodsdb數據庫中id為3商品的庫存:100

訪問成功效果的url:

http://127.0.0.1:8080/home/addorderseata

訪問后:可以看到庫存變成了99,

而且orderdb訂單中新插入了訂單記錄

查看控制台中關於分布式事務的輸出:

2020-08-21 19:22:09.447 [http-nio-8080-exec-1] [:] INFO  org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet' 
2020-08-21 19:22:09.448 [http-nio-8080-exec-1] [FrameworkServlet.java:525] INFO  org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet' 
2020-08-21 19:22:09.480 [http-nio-8080-exec-1] [FrameworkServlet.java:542] DEBUG org.springframework.web.servlet.DispatcherServlet - enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data 
2020-08-21 19:22:09.480 [http-nio-8080-exec-1] [FrameworkServlet.java:547] INFO  org.springframework.web.servlet.DispatcherServlet - Completed initialization in 32 ms 
xid:null
2020-08-21 19:22:09.526 [http-nio-8080-exec-1] [:] INFO  io.seata.common.loader.EnhancedServiceLoader - load ContextCore[null] extension by class[io.seata.core.context.FastThreadLocalContextCore] 
2020-08-21 19:22:09.560 [http-nio-8080-exec-1] [:] INFO  io.seata.common.loader.EnhancedServiceLoader - load TransactionManager[null] extension by class[io.seata.tm.DefaultTransactionManager] 
2020-08-21 19:22:09.560 [http-nio-8080-exec-1] [:] INFO  io.seata.tm.TransactionManagerHolder - TransactionManager Singleton io.seata.tm.DefaultTransactionManager@1b211325 
2020-08-21 19:22:09.563 [http-nio-8080-exec-1] [:] INFO  io.seata.common.loader.EnhancedServiceLoader - load LoadBalance[null] extension by class[io.seata.discovery.loadbalance.RandomLoadBalance] 
2020-08-21 19:22:09.564 [http-nio-8080-exec-1] [:] INFO  io.seata.core.rpc.netty.NettyClientChannelManager - will connect to 127.0.0.1:8091 
2020-08-21 19:22:09.565 [http-nio-8080-exec-1] [:] INFO  io.seata.core.rpc.netty.NettyPoolableFactory - NettyPool create channel to transactionRole:TMROLE,address:127.0.0.1:8091,msg:< RegisterTMRequest{applicationId='my_test_tx', transactionServiceGroup='my_test_tx_group'} > 
2020-08-21 19:22:09.609 [http-nio-8080-exec-1] [:] INFO  io.seata.core.rpc.netty.NettyPoolableFactory - register success, cost 32 ms, version:1.3.0,role:TMROLE,channel:[id: 0xd46743ae, L:/127.0.0.1:45978 - R:/127.0.0.1:8091] 
2020-08-21 19:22:09.625 [http-nio-8080-exec-1] [:] INFO  io.seata.tm.api.DefaultGlobalTransaction - Begin new global transaction [192.168.3.173:8091:40155132419117056] 
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25d9820f] was not registered for synchronization because synchronization is not active
JDBC Connection [io.seata.rm.datasource.ConnectionProxy@7bd566ba] will not be managed by Spring
==>  Preparing: insert into orderinfo(orderSn,orderTime,orderStatus,userId,price) values( ?,now(),?,?,? )
==> Parameters: 20200821192209638(String), 0(Integer), 8(Integer), 100(BigDecimal)
2020-08-21 19:22:09.915 [http-nio-8080-exec-1] [:] INFO  io.seata.common.loader.EnhancedServiceLoader - load SQLRecognizerFactory[druid] extension by class[io.seata.sqlparser.druid.DruidDelegatingSQLRecognizerFactory] 
2020-08-21 19:22:09.994 [http-nio-8080-exec-1] [:] INFO  io.seata.common.loader.EnhancedServiceLoader - load SQLOperateRecognizerHolder[mysql] extension by class[io.seata.sqlparser.druid.mysql.MySQLOperateRecognizerHolder] 
2020-08-21 19:22:10.064 [http-nio-8080-exec-1] [:] INFO  io.seata.common.loader.EnhancedServiceLoader - load KeywordChecker[mysql] extension by class[io.seata.rm.datasource.undo.mysql.keyword.MySQLKeywordChecker] 
2020-08-21 19:22:10.065 [http-nio-8080-exec-1] [:] INFO  io.seata.common.loader.EnhancedServiceLoader - load TableMetaCache[mysql] extension by class[io.seata.rm.datasource.sql.struct.cache.MysqlTableMetaCache] 
2020-08-21 19:22:10.195 [http-nio-8080-exec-1] [:] INFO  io.seata.common.loader.EnhancedServiceLoader - load UndoLogManager[mysql] extension by class[io.seata.rm.datasource.undo.mysql.MySQLUndoLogManager] 
2020-08-21 19:22:10.202 [http-nio-8080-exec-1] [:] WARN  io.seata.common.loader.EnhancedServiceLoader - load [io.seata.rm.datasource.undo.parser.ProtostuffUndoLogParser] class fail. io/protostuff/runtime/RuntimeEnv 
2020-08-21 19:22:10.203 [http-nio-8080-exec-1] [:] INFO  io.seata.common.loader.EnhancedServiceLoader - load UndoLogParser[jackson] extension by class[io.seata.rm.datasource.undo.parser.JacksonUndoLogParser] 
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@25d9820f]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5a29a25f] was not registered for synchronization because synchronization is not active
JDBC Connection [io.seata.rm.datasource.ConnectionProxy@36a96f9c] will not be managed by Spring
==>  Preparing: UPDATE goods SET stock = stock+? WHERE goodsId = ?
==> Parameters: -1(Integer), 3(Long)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5a29a25f]
res:1
2020-08-21 19:22:11.025 [http-nio-8080-exec-1] [:] INFO  io.seata.tm.api.DefaultGlobalTransaction - [192.168.3.173:8091:40155132419117056] commit status: Committed 
2020-08-21 19:22:11.041 [http-nio-8080-exec-1] [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-21 19:22:11.053 [http-nio-8080-exec-1] [FrameworkServlet.java:1131] DEBUG org.springframework.web.servlet.DispatcherServlet - Completed 200 OK, headers={masked} 
2020-08-21 19:22:11.064 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.core.rpc.netty.RmMessageListener - onMessage:xid=192.168.3.173:8091:40155132419117056,branchId=40155134826647552,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/orderdb,applicationData=null 
2020-08-21 19:22:11.065 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch committing: 192.168.3.173:8091:40155132419117056 40155134826647552 jdbc:mysql://127.0.0.1:3306/orderdb null 
2020-08-21 19:22:11.066 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch commit result: PhaseTwo_Committed 
2020-08-21 19:22:11.088 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.core.rpc.netty.RmMessageListener - onMessage:xid=192.168.3.173:8091:40155132419117056,branchId=40155138186285056,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/store,applicationData=null 
2020-08-21 19:22:11.088 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch committing: 192.168.3.173:8091:40155132419117056 40155138186285056 jdbc:mysql://127.0.0.1:3306/store null 
2020-08-21 19:22:11.088 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch commit result: PhaseTwo_Committed 

 

測試發生異常時事務的效果:

先查看undo_log表的自增值:

orderdb庫中undo_log表:

下一個自增值    77

goodsdb庫中undo_log表:

下一個自增值    48

訪問:

http://127.0.0.1:8080/home/addorderseata?isfail=1

查看數據表,沒有新增訂單,商品也沒有減庫存,

注意查看兩個庫中undo_log表的自增值:

orderdb庫中undo_log表:

下一個自增值    81

goodsdb庫中undo_log表:

下一個自增值    50

發生了變化,說明曾經有undo_log寫入

查看控制台的輸出:

2020-08-21 19:30:31.695 [http-nio-8080-exec-1] [:] INFO  io.seata.tm.api.DefaultGlobalTransaction - Begin new global transaction [192.168.3.173:8091:40157238274297856] 
JDBC Connection [io.seata.rm.datasource.ConnectionProxy@4d10bcbf] will not be managed by Spring
==>  Preparing: insert into orderinfo(orderSn,orderTime,orderStatus,userId,price) values( ?,now(),?,?,? )
==> Parameters: 20200821193031695(String), 0(Integer), 8(Integer), 100(BigDecimal)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@42fca4a3]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@17ef4bcf] was not registered for synchronization because synchronization is not active
JDBC Connection [io.seata.rm.datasource.ConnectionProxy@5f4fe7f4] will not be managed by Spring
==>  Preparing: UPDATE goods SET stock = stock+? WHERE goodsId = ?
==> Parameters: -1(Integer), 3(Long)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@17ef4bcf]
res:1
2020-08-21 19:30:31.883 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.core.rpc.netty.RmMessageListener - onMessage:xid=192.168.3.173:8091:40157238274297856,branchId=40157238832140288,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/store,applicationData=null 
2020-08-21 19:30:31.883 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch Rollbacking: 192.168.3.173:8091:40157238274297856 40157238832140288 jdbc:mysql://127.0.0.1:3306/store 
2020-08-21 19:30:32.038 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.common.loader.EnhancedServiceLoader - load UndoExecutorHolder[mysql] extension by class[io.seata.rm.datasource.undo.mysql.MySQLUndoExecutorHolder] 
2020-08-21 19:30:32.075 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.rm.datasource.undo.AbstractUndoLogManager - xid 192.168.3.173:8091:40157238274297856 branch 40157238832140288, undo_log deleted with GlobalFinished 
2020-08-21 19:30:32.076 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch Rollbacked result: PhaseTwo_Rollbacked 
2020-08-21 19:30:32.081 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.core.rpc.netty.RmMessageListener - onMessage:xid=192.168.3.173:8091:40157238274297856,branchId=40157238739865600,branchType=AT,resourceId=jdbc:mysql://127.0.0.1:3306/orderdb,applicationData=null 
2020-08-21 19:30:32.081 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch Rollbacking: 192.168.3.173:8091:40157238274297856 40157238739865600 jdbc:mysql://127.0.0.1:3306/orderdb 
2020-08-21 19:30:32.091 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.rm.datasource.undo.AbstractUndoLogManager - xid 192.168.3.173:8091:40157238274297856 branch 40157238739865600, undo_log added with GlobalFinished 
2020-08-21 19:30:32.092 [rpcDispatch_RMROLE_1_4] [:] INFO  io.seata.rm.AbstractRMHandler - Branch Rollbacked result: PhaseTwo_Rollbacked 2020-08-21 19:30:32.147 [http-nio-8080-exec-1] [:] INFO  io.seata.tm.api.DefaultGlobalTransaction - [192.168.3.173:8091:40157238274297856] rollback status: Rollbacked 

 

2,測試seata用過rest方式訪問兩個庫的事務效果

分別訪問:成功效果:

http://127.0.0.1:8080/home/addorderseatarest

和 發生異常失敗效果:

http://127.0.0.1:8080/home/addorderseatarest?isfail=1

效果和直接訪問數據庫方式一致,大家自己查看即可

 

3,查看druid中建立的連接:訪問:

http://127.0.0.1:8080/druid

 

可以看到dynamic-datasource所創建的兩個到數據庫的連接

 

七,查看spring boot的版本:

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

 


免責聲明!

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



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