一,什么是seata?

Seata:Simpe Extensible Autonomous Transcaction Architecture,
是阿里中间件开源的分布式事务解决方案。
前身是阿里的Fescar
 
官方站:
http://seata.io/zh-cn/ 
官方代码地址:
https://github.com/seata/seata 
官方文档站:
http://seata.io/zh-cn/docs/overview/what-is-seata.html
各版本的release下载地址:
https://github.com/seata/seata/releases

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: 371125307@qq.com

 

二,seata-server的安装:

参见:
https://www.cnblogs.com/architectforest/p/13507695.html

三,演示项目的相关信息

1,项目地址:

https://github.com/liuhongdi/seata

2,功能说明:

分别实现了:同一个项目中不同数据库间的分布式事务

用resttemplate访问不同url的分布式事务

3,项目结构:

四,配置文件说明

1,pom.xml

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

2,application.properties

#error
server.error.include-stacktrace=always
#error log
logging.level.org.springframework.web=trace
#app name
spring.application.name = txtest # 数据源goodsdb基本配置
spring.datasource.druid.goodsdb.username = root
spring.datasource.druid.goodsdb.password = lhddemo
spring.datasource.druid.goodsdb.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.druid.goodsdb.url = jdbc:mysql://127.0.0.1:3306/store?useAffectedRows=true&serverTimezone=UTC
spring.datasource.druid.goodsdb.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.goodsdb.initialSize = 5
spring.datasource.druid.goodsdb.minIdle = 5
spring.datasource.druid.goodsdb.maxActive = 20
spring.datasource.druid.goodsdb.maxWait = 60000
spring.datasource.druid.goodsdb.timeBetweenEvictionRunsMillis = 60000
spring.datasource.druid.goodsdb.minEvictableIdleTimeMillis = 300000
spring.datasource.druid.goodsdb.validationQuery = SELECT 1 FROM DUAL
spring.datasource.druid.goodsdb.testWhileIdle = true
spring.datasource.druid.goodsdb.testOnBorrow = false
spring.datasource.druid.goodsdb.testOnReturn = false
spring.datasource.druid.goodsdb.poolPreparedStatements = true
# 数据源orderdb基本配置
spring.datasource.druid.orderdb.username = root
spring.datasource.druid.orderdb.password = lhddemo
spring.datasource.druid.orderdb.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.druid.orderdb.url = jdbc:mysql://127.0.0.1:3306/orderdb?useAffectedRows=true&serverTimezone=UTC
spring.datasource.druid.orderdb.type = com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.orderdb.initialSize = 5
spring.datasource.druid.orderdb.minIdle = 5
spring.datasource.druid.orderdb.maxActive = 20
spring.datasource.druid.orderdb.maxWait = 60000
spring.datasource.druid.orderdb.timeBetweenEvictionRunsMillis = 60000
spring.datasource.druid.orderdb.minEvictableIdleTimeMillis = 300000
spring.datasource.druid.orderdb.validationQuery = SELECT 1 FROM DUAL
spring.datasource.druid.orderdb.testWhileIdle = true
spring.datasource.druid.orderdb.testOnBorrow = false
spring.datasource.druid.orderdb.testOnReturn = false
spring.datasource.druid.orderdb.poolPreparedStatements = true #配置监控统计拦截的filters
#stat:监控统计sql
#'wall':sql防火墙
spring.datasource.druid.filters = stat,wall,log4j2
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize = 20
spring.datasource.druid.useGlobalDataSourceStat = true
spring.datasource.druid.connectionProperties = druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 #druid sql firewall monitor
spring.datasource.druid.filter.wall.enabled=true #druid sql monitor
spring.datasource.druid.filter.stat.enabled=true
spring.datasource.druid.filter.stat.log-slow-sql=true
spring.datasource.druid.filter.stat.slow-sql-millis=10000
spring.datasource.druid.filter.stat.merge-sql=true #druid uri monitor
spring.datasource.druid.web-stat-filter.enabled=true
spring.datasource.druid.web-stat-filter.url-pattern=/*
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/* #druid session monitor
spring.datasource.druid.web-stat-filter.session-stat-enable=true
spring.datasource.druid.web-stat-filter.profile-enable=true #druid spring monitor
spring.datasource.druid.aop-patterns=com.druid.* #monintor,druid login user config
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.login-username=root
spring.datasource.druid.stat-view-servlet.login-password=root #mybatis
mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
mybatis.type-aliases-package=com.example.demo.mapper
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#log
logging.config = classpath:log4j2.xml #seata
seata.enabled=true
seata.application-id=txtest
seata.tx-service-group=txtest-group
seata.service.vgroup-mapping.txtest-group=default
seata.service.grouplist.default=127.0.0.1:8091
seata.client.undo.log-serialization=jackson
seata.client.undo.log-table=undo_log

说明:因为涉及到两个数据源,spring.datasource.druid用来供生成数据源使用

seata的配置要注意:

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

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

seata.tx-service-group 用来指定所属事务的分组,一台seata server 可管理多个事务组,

seata.service.vgroup-mapping.txtest-group=default:把服务组命名为default

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

3, log4j2.xml

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

4,    两个数据库中的数据表:

goods表

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

goods表中的数据:

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

order表:

CREATE TABLE `orderinfo` (
`orderId` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`orderSn` varchar(100) NOT NULL DEFAULT '' COMMENT '编号',
`orderTime` timestamp NOT NULL DEFAULT '1971-01-01 00:00:01' COMMENT '下单时间',
`orderStatus` tinyint(4) NOT NULL DEFAULT '0' COMMENT '状态:0,未支付,1,已支付,2,已发货,3,已退货,4,已过期',
`userId` int(12) NOT NULL DEFAULT '0' COMMENT '用户id',
`price` decimal(10,0) NOT NULL DEFAULT '0' COMMENT '价格',
`addressId` int(12) NOT NULL DEFAULT '0' COMMENT '地址',
PRIMARY KEY (`orderId`),
UNIQUE KEY `orderSn` (`orderSn`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='订单表'

5,在每个库中创建seata回滚数据时要用到的undo_log数据表

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

五,java代码说明

1,GoodsdbSourceConfig.java

@Configuration
@MapperScan(basePackages = "com.seata.demo.mapper.goodsdb", sqlSessionTemplateRef = "goodsdbSqlSessionTemplate")
public class GoodsdbSourceConfig { @Bean
@Primary
@ConfigurationProperties("spring.datasource.druid.goodsdb")
public DataSource goodsdbDataSource() {
return DruidDataSourceBuilder.create().build();
} @Bean
@Primary
public SqlSessionFactory goodsdbSqlSessionFactory(@Qualifier("goodsdbDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//bean.setDataSource();
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/goodsdb/*.xml"));
return bean.getObject();
} @Bean
@Primary
public DataSourceTransactionManager goodsdbTransactionManager(@Qualifier("goodsdbDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
} @Bean
@Primary
public SqlSessionTemplate goodsdbSqlSessionTemplate(@Qualifier("goodsdbSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}

2,OrderdbSourceConfig.java

@Configuration
@MapperScan(basePackages = "com.seata.demo.mapper.orderdb", sqlSessionTemplateRef = "orderdbSqlSessionTemplate")
public class OrderdbSourceConfig { @Bean
@ConfigurationProperties(prefix = "spring.datasource.druid.orderdb")
public DataSource orderdbDataSource() {
return DruidDataSourceBuilder.create().build();
} @Bean
public SqlSessionFactory orderdbSqlSessionFactory(@Qualifier("orderdbDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/orderdb/*.xml"));
return bean.getObject();
} @Bean
public DataSourceTransactionManager orderdbTransactionManager(@Qualifier("orderdbDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
} @Bean
public SqlSessionTemplate orderdbSqlSessionTemplate(@Qualifier("orderdbSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}

说明:以上分别为goodsdb/orderdb创建两个数据源

3,GoodsController.java

@RestController
@RequestMapping("/goods")
public class GoodsController { private static final String SUCCESS = "SUCCESS";
private static final String FAIL = "FAIL"; @Resource
private GoodsMapper goodsMapper; //更新商品库存 参数:商品id,数量
@RequestMapping("/goodsstock/{goodsId}/{count}")
@ResponseBody
public String goodsStock(@PathVariable Long goodsId,
@PathVariable int count) {
int res = goodsMapper.updateGoodsStock(goodsId,count);
System.out.println("res:"+res); if (res>0) {
return SUCCESS;
} else {
return FAIL;
}
}
}

rest方式调用访问goodsdb库时使用

4,OrderController.java

@RestController
@RequestMapping("/order")
public class OrderController { private static final String SUCCESS = "SUCCESS";
private static final String FAIL = "FAIL"; @Resource
private OrderMapper orderMapper; //添加订单:参数:商品id,数量
@RequestMapping("/orderadd/{goodsId}/{count}")
@ResponseBody
public String orderAdd(@PathVariable Long goodsId,
@PathVariable int count) {
Order order = new Order();
//得到sn
String orderSn = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
order.setOrderSn(orderSn);
order.setOrderStatus(0);
order.setPrice(new BigDecimal(100.00));
order.setUserId(8); int orderId = orderMapper.insertOneOrder(order);
System.out.println("orderId:"+order.getOrderId()); if (orderId>0) {
return SUCCESS;
} else {
return FAIL;
}
}
}

rest方式访问orderdb库时使用

5,HomeController.java

@RestController
@RequestMapping("/home")
public class HomeController { private static final String SUCCESS = "SUCCESS";
private static final String FAIL = "FAIL"; @Resource
private OrderMapper orderMapper; @Resource
private GoodsMapper goodsMapper; //添加一个订单,访问两个数据库
@GlobalTransactional(timeoutMills = 300000,rollbackFor = Exception.class)
@GetMapping("/addorderseata")
public String addOrderSeata(@RequestParam(value="isfail",required = true,defaultValue = "0") int isFail) { String goodsId = "3";
String goodsNum = "1"; Order order = new Order();
//生成订单
String orderSn = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
order.setOrderSn(orderSn);
order.setOrderStatus(0);
order.setPrice(new BigDecimal(100.00));
order.setUserId(8);
int orderId = orderMapper.insertOneOrder(order);
System.out.println("orderId:"+order.getOrderId());
//更新商品库存
int count = -1;
int res = goodsMapper.updateGoodsStock(Long.parseLong(goodsId),count);
System.out.println("res:"+res);
//测试失败的情况
if (isFail == 1) {
int divide = 0;
int resul = 100 / divide;
} if (res>0) {
return SUCCESS;
} else {
return FAIL;
}
} //添加一个订单,访问两个url(分别访问不同的数据库)
@GlobalTransactional(timeoutMills = 300000,rollbackFor = Exception.class)
@GetMapping("/addorderseatarest")
public String addOrderSeataRest(@RequestParam(value="isfail",required = true,defaultValue = "0") int isFail) {
String goodsId = "3";
String goodsNum = "1";
//得到事务的xid
RestTemplate restTemplate = new RestTemplate();
String xid = RootContext.getXID();
System.out.println("xid before send:"+xid);
if (StringUtils.isEmpty(xid)) {
System.out.println("xid is null,return");
return FAIL;
}
//把事务的xid添加到header
HttpHeaders headers = new HttpHeaders();
headers.add(RootContext.KEY_XID, xid);
System.out.println("xid not null");
//生成订单,传递xid
String urlAddOrder = "http://127.0.0.1:8080/order/orderadd/"+goodsId+"/"+goodsNum+"/";
String resultAdd = restTemplate.postForObject(urlAddOrder,new HttpEntity<String>(headers),String.class);
if (!SUCCESS.equals(resultAdd)) {
throw new RuntimeException();
}
//更新商品库存,传递xid
String goodsUPNum = "-1";
String urlUpStock = "http://127.0.0.1:8080/goods/goodsstock/"+goodsId+"/"+goodsUPNum+"/";
String resultUp = restTemplate.postForObject(urlUpStock,new HttpEntity<String>(headers),String.class);
if (!SUCCESS.equals(resultUp)) {
throw new RuntimeException();
}
//测试失败的情况
if (isFail == 1) {
int divide = 0;
int resul = 100 / divide;
}
return SUCCESS;
}
}

需要注意的地方在于:用resttemplate访问其他url时,需要用xid传递全局事务

否则事务会不生效

6,SeataFilter.java

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

这个过滤器主要用来保存xid值

如果使用seata的spring-cloud starter包,则不需要做这个工作

7,GoodsMapper.java

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

8,OrderMapper.java

@Repository
@Mapper
public interface OrderMapper {
//插入一条订单
int insertOneOrder(Order order);
}

9,Goods.java/Order.java/GoodsMapper.xml/OrderMapper.xml
   为节省篇幅,这些代码请从github上查看

六,测试效果

1,添加一个订单:
先测试事务成功的情况:
查看orderdb中undo_log表的下一个自增值:
下一个自增值 70
id为3的商品当前库存:100
 
访问:
http://127.0.0.1:8080/home/addorderseata

orderinfo表中增加了一条订单记录
查看id为3的商品库存:99

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

可以看到GlobalTransaction的begin 和commit 记录

 
测试失败的测试:访问:
http://127.0.0.1:8080/home/addorderseata?isfail=1

在这里发生了一次除0错,

查看orderinfo表:记录未插入

查看id为3的商品库存:99,没发生变化

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

可以看到事务的begin和rollback

 
 
2,用rest方式测试事务的执行:
  访问:
http://127.0.0.1:8080/home/addorderseatarest?isfail=1

可以观察到undo_log中自增值的变化和控制台的输出
与上一个例子基本一致,大家自己观察效果即可

 

七,查看spring boot 的版本:

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

spring boot:使用分布式事务seata(druid 1.1.23 / seata 1.3.0 / mybatis / spring boot 2.3.2)的更多相关文章

  1. spring boot:配置shardingsphere(sharding jdbc)使用druid数据源(druid 1.1.23 / sharding-jdbc 4.1.1 / mybatis / spring boot 2.3.3)

    一,为什么要使用druid数据源? 1,druid的优点 Druid是阿里巴巴开发的号称为监控而生的数据库连接池 它的优点包括: 可以监控数据库访问性能 SQL执行日志 SQL防火墙 但spring ...

  2. (附源码gitHub下载地址)spring boot -jta-atomikos分布式事务

    应用场景:双数据源,就是某些项目会涉及到两个数据源或者两个以上的数据源,这个多数据源的项目一般是数据同步,也就是把数据从另一个系统中,保存到另一个系统,两边的 数据库又不一样,比如一个Mysql.一个 ...

  3. Spring Cloud Alibaba分布式事务组件 seata 详解(小白都能看懂)

    一,什么是事务(本地事务)? 指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行. 简单的说,事务就是并发控制的单位,是用户定义的一个操作序列.      而一个逻辑工作单元要成 ...

  4. spring多数据源分布式事务的分析与解决方案

    一.概述 1.业务背景 对老系统进行重构合并,导致新系统需要同时对3个数据库进行管理.由于出现跨库业务,需要实现分布式事务. 2.开发环境 spring框架版本  4.3.10.RELEASE 持久层 ...

  5. spring3.0+mybatis+spring快速入门

    一.首先奉上项目目录结构: 说明: dao,mapping,model包下的所有内容可以使用Generator工具自助生成. 具体用法,可以网上学习一下,比较简单,主要做以下工作: 1.提供相关的数据 ...

  6. Spring Cloud同步场景分布式事务怎样做?试试Seata

    一.概述 在微服务架构下,虽然我们会尽量避免分布式事务,但是只要业务复杂的情况下这是一个绕不开的问题,如何保证业务数据一致性呢?本文主要介绍同步场景下使用Seata的AT模式来解决一致性问题. Sea ...

  7. spring boot 分布式事务实现(XA方式)

    关于spring boot 支持分布式事务,XA是常用的一种方式. 这里把相关的配置记下,方便以后使用. 首先配置两个不同的数据源 : 订单库.持仓库. /** * Created by zhangj ...

  8. Dubbo学习系列之十四(Seata分布式事务方案AT模式)

    一直说写有关最新技术的文章,但前面似乎都有点偏了,只能说算主流技术,今天这个主题,我觉得应该名副其实.分布式微服务的深水区并不是单个微服务的设计,而是服务间的数据一致性问题!解决了这个问题,才算是把分 ...

  9. springboot学习笔记:10.springboot+atomikos+mysql+mybatis+druid+分布式事务

    前言 上一篇文章我们整合了springboot+druid+mybatis+mysql+多数据源: 本篇文章大家主要跟随你们涛兄在上一届基础上配置一下多数据源情况下的分布式事务: 首先,到底啥是分布式 ...

随机推荐

  1. oracle之DML和DDL语句的其他用法

    DML和DDL语句的其他用法 17.1 DML语句-MERGE 作用:把数据从一个表复制到另一个表,插入新数据或替换掉老数据. Oracle 10g中MERGE有如下一些改进: 1.UPDATE或IN ...

  2. spring官网在线学习文档翻译

    Core Technologies (核心技术) Version 5.0.8.RELEASE 版本5.0.8RELEASE This part of the reference documentati ...

  3. 吴恩达《深度学习》-第一门课 (Neural Networks and Deep Learning)-第三周:浅层神经网络(Shallow neural networks) -课程笔记

    第三周:浅层神经网络(Shallow neural networks) 3.1 神经网络概述(Neural Network Overview) 使用符号$ ^{[

  4. [Leetcode]585. 2016年的投资(MySQL)

    题目 写一个查询语句,将 2016 年 (TIV_2016) 所有成功投资的金额加起来,保留 2 位小数. 对于一个投保人,他在 2016 年成功投资的条件是: 他在 2015 年的投保额 (TIV_ ...

  5. 万字详解TDengine 2.0整体架构设计思路

    ​导读:涛思数据8月3日将TDengine 的集群功能开源,TDengine具有超强的性能和功能,为什么能做到?它到底有哪些技术创新?今将TDengine的整体设计文档分享出来. 1: 数据模型 物联 ...

  6. Python 之 Django框架( Cookie和Session、Django中间件、AJAX、Django序列化)

    12.4 Cookie和Session 12.41 cookie Cookie具体指的是一段小信息,它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务 ...

  7. 设计模式之Command

    由于学习hystrix的使用和原理   所以就学习了command模式https://www.jdon.com/designpatterns/command.htm Command模式是最让我疑惑的一 ...

  8. 苏大文正节点一 ORA-00603 ORA-27504 ORA-27300 ORA-27301 ORA-27302 BUG

      Problem Description --------------------------------------------------- Tue Sep 01 04:05:33 2020 s ...

  9. BeautifulSoup解析页面

    beautiful soup是一个解析包,专门用来解析html语法的,lxml是一个解析器,用来分析以及定位内容的 .是class #是id import requests from bs4 impo ...

  10. 刷题[GWCTF 2019]你的名字

    解题思路 打开发现需要输入名字,猜测会有sql注入漏洞,测试一下发现单引号被过滤了,再fuzs下看看过滤了哪些 长度为1518和1519的都有过滤,测试一下,感觉不是sql注入了.那还有什么呢,考虑了 ...