一,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,项目结构:如图:

spring boot:用dynamic-datasource-spring-boot-starter配置多数据源访问seata(seata 1.3.0 / spring boot 2.3.3)-LMLPHP

4,用到的数据库:

spring boot:用dynamic-datasource-spring-boot-starter配置多数据源访问seata(seata 1.3.0 / spring boot 2.3.3)-LMLPHP

spring boot:用dynamic-datasource-spring-boot-starter配置多数据源访问seata(seata 1.3.0 / spring boot 2.3.3)-LMLPHP

四,配置文件说明

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表

spring boot:用dynamic-datasource-spring-boot-starter配置多数据源访问seata(seata 1.3.0 / spring boot 2.3.3)-LMLPHP
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='商品表'
spring boot:用dynamic-datasource-spring-boot-starter配置多数据源访问seata(seata 1.3.0 / spring boot 2.3.3)-LMLPHP

goods表中的数据:

INSERT INTO `goods` (`goodsId`, `goodsName`, `subject`, `price`, `stock`) VALUES
(3, '100分电动牙刷', '好用到让你爱上刷牙', '59.00', 100);

order表:

spring boot:用dynamic-datasource-spring-boot-starter配置多数据源访问seata(seata 1.3.0 / spring boot 2.3.3)-LMLPHP
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='订单表'
spring boot:用dynamic-datasource-spring-boot-starter配置多数据源访问seata(seata 1.3.0 / spring boot 2.3.3)-LMLPHP

每个库中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:用dynamic-datasource-spring-boot-starter配置多数据源访问seata(seata 1.3.0 / spring boot 2.3.3)-LMLPHP

七,查看spring boot的版本:

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