zoukankan      html  css  js  c++  java
  • 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)
  • 相关阅读:
    day 40 MySQL之视图、触发器、事务、存储过程、函数
    js多元运算
    继承以及Super
    Git工作流指南:Gitflow工作流
    JS中的逻辑运算符&&、||,位运算符|,&
    js的prototype理解
    JS对象—数组总结(创建、属性、方法)
    React-Native中props用法详解
    AJAX中同步和异步的区别和使用场景
    web前端之性能
  • 原文地址:https://www.cnblogs.com/architectforest/p/13542295.html
Copyright © 2011-2022 走看看