作者:小傅哥

博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!

一、前言:一个Bug

没想到一个Bug,竟然搞我两次!

我大抵是卷上瘾了,横竖都睡不着,坐起来身来打开Mac和外接显示器,这Bug没有由来,默然看着打印异常的屏幕,一个是我的,另外一个也是我的。


最近可能是卷源码,卷上瘾了。先是《手写Spring》,再是《手写Mybatis》,但没想到一个小问题竟然搞了我2次!

今天这个问题主要体现在大家平常用的Mybatis,在插入数据的时候,我们可以把库表索引的返回值通过入参对象返回回来。但是通过我自己手写的Mybatis,每次返回来的都是0,而不是最后插入库表的索引值。因为是手写的,不是直接使用Mybatis,所以我会从文件的解析、对象的映射、SQL的查询、结果的封装等一直排查下去,但竟然问题都不在这?!

  • 就是这个 selectKey 的配置,在执行插入SQL后,开始执行获取最后的索引值。
  • 通常只要配置的没问题,返回对象中也有对应的 id 字段,那么就可以正确的拿到返回值了。PS:问题就出现在这里,小傅哥手写的 Mybatis 竟然只难道返回一个0!

二、分析:诊断异常

可能大部分研发伙伴没有阅读过 Mybatis 源码,所以可能不太清楚这里发生了什么,小傅哥这里给大家画张图,告诉你发生了什么才让返回的结果为0的。

  • Mybatis 的处理过程可以分为两个大部分来看,一部分是解析,另外一部分是使用。解析的时候把 Mapper XML 中的 insert 标签语句解析出来,同时解析 selectKey 标签。最终解析完成后,把解析的语句信息使用 MappedStatement 映射语句类存放起来。便于后续在 DefaultSqlSession 执行操作的时候,可以从 Configuration 配置项中获取出来使用。

  • 那么这里有一个非常重要的点,就是执行 insert 插入的时候,里面还包含了一句查询的操作。那也就是说,我们会在一次 Insert 中,包含两条执行语句。重点:bug就发生在这里,为什么呢?因为最开始这两条语句执行的时候,在获取链接的时候,每一条都是获取一个新的链接,那么也就是说,insert xxx、select LAST_INSERT_ID() 在两个 connection 连接执行时,其实是不对的,没法获取到插入后的索引 ID,只有在一个链接或者一个事务下(一次 commit)才能有事务的特性,获取插入数据后的自增ID。

  • 而因为这部分最开始手写 JdbcTransaction 实现 Transaction 接口获取连接的时候,每一次都是新的链接,代码块如下;

    • 这里的链接获取,最开始没有 if null 的判断,每次都是直接获取链接,所以这种非一个链接下的两条 SQL 操作,所以必然不会获得到正确的结果,相当于只是单独执行 SELECT LAST_INSERT_ID() 所以最终的查询结果为 0 了就!你可以测试把这条语句复制到 SQL查询工具中执行

三、震惊:同一个坑

但其实就这么一个链接的问题,在小傅哥手写Spring中也同样遇到过。

在 Spring 中有一部分是关于事务的处理,其实这些事务的操作也是对 JDBC 的包装操作,依赖于数据源获得的链接来管理事务。而我们通常使用 Spring 也是结合着 Mybatis 配置上数据源的方式进行使用,那么在一个事务下操作多个 SQL 语句的时候,是怎么获得同一个链接的呢。因为从上面的案例中,我们得知保证事务的特性,需要在同一个链接下,即使是操作多条SQL

由于多个SQL的操作,已经是相当于每次都获取一个新的 Session 有一个新的链接从连接池中获得,但为了能达到事务的特性,所以在需要有事务操作下的多个 SQL 前需要开启事务操作,无论是手动还是注解。

而这个事务的开启动作处理做一些事务传播行为和隔离级别的限制,其实更重要的是让多个 SQL 的执行获取的链接,需要是同一个。所以这里就引入了 ThreadLocal 基于它在同一个线程操作下保存信息的同步特性,其实这里的从事务下获取的链接,其实就是保存到 TransactionSynchronizationManager#resources 属性中的。

虽然就这么一小块内容,但在小傅哥最开始手写Spring的时候,也是给漏下了。直到到测试的时候,才发现链接发现事务总是不成功,最初还以为是整个切面逻辑没有切进去或者是我的操作方式有误。直到逐步排查调试代码,发现原来多个SQL的执行竟然不是获得的同一个链接,所以也就没法让事务生效。

四、常见:事务失效

可能就是这么一个小小的链接问题,有时候就会引起一堆的异常,如果说我们没有学习过源码,那么可能也不知道这样的问题到底是如何发生的。所以往往深入的研究和探索,才能让你解释一个问题的时候,更加简单直接。

那么你说,事务失效的原因还有哪些?- 分享一些常见,如果你还有遇到其他的,可以发到评论区一起看看。

  1. 数据库引擎不支持事务:这里以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。https://dev.mysql.com/doc/refman/8.0/en/storage-en... 从 MySQL 5.5.5 开始的默认存储引擎是:InnoDB,之前默认的都是:MyISAM,所以这点要值得注意,底层引擎不支持事务再怎么搞都是白搭。
  2. 方法不是 public 的:来自 Spring 官方文档【When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.】@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。
  3. 没有被 Spring 管理:// @Service - 这里被注释掉了 public class OrderServiceImpl implements OrderService { @Transactional public void placeOrder(Order order) { // ... } }
  4. 数据源没有配置事务管理器:一般来自于自研的数据库路由组件 @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
  5. 异常被吞了。catch 后直接吃了,事务异常无法回滚。同时要配置上对应的异常 @Transactional(rollbackFor = Exception.class)

五、总结:学习经验

很多类似这样的技术问题,都是来自于小傅哥对源码的学习,最开始是遇到问题的时候去翻看源码,虽然很多时候也很难把整个逻辑捋顺,但一点点的积累确实会让研发人员对技术有更加夯实的认知。

那么在现在我之所以去手写Spring、手写Mybatis,也是希望通过把这样的知识全部整理处理,从中学习复杂逻辑的设计方案、设计原则和如何运用设计模式解决复杂场景的问题。PS:通常我们的业务代码复杂度很难到这个程度,所以在见过”天“后,以后所承接的业务就很容易做设计了。

另外就是对各类技术细节的把控,以及积累于这样的经验把相关技术设计运用到一些类似 SpringBoot Starter 等的开发,只有类似这样的广度、高度、深度,才能真的把个人的研发能力提升起来。PS:也是为了在技术的路上走的更远,无论是高级开发、架构师、CTO!

我大抵是卷上瘾了,横竖睡不着!竟让一个Bug,搞我两次!的更多相关文章

  1. Linux LVM逻辑卷配置过程详解

    许多Linux使用者安装操作系统时都会遇到这样的困境:如何精确评估和分配各个硬盘分区的容量,如果当初评估不准确,一旦系统分区不够用时可能不得不备份.删除相关数据,甚至被迫重新规划分区并重装操作系统,以 ...

  2. Windows Server 2012 磁盘管理之 简单卷、跨区卷、带区卷、镜像卷和RAID-5卷

    今天给客户配置故障转移群集,在Windows Server 2012 R2的系统上,通过iSCSI连接上DELL的SAN存储后,在磁盘管理里面发现可以新建 简单卷.跨区卷.带区卷.镜像卷.RAID-5 ...

  3. AIX 5L 系统管理技术 —— 存储管理——卷组

    卷组 在安装系统时,就会创建一个rootvg卷组.包含自带硬盘(内置硬盘)和系统逻辑卷,一个系统只能有一个rootvg卷组.一般情况下rootvg卷组最好只包含自带硬盘. 一.创建卷组 在创建卷组之前 ...

  4. AIX 5L 系统管理技术 —— 存储管理——物理卷

    一.向系统中添加一块硬盘 方法一 该方法适用于在配置之前能够重新启动系统的情况.在系统启动时,就会运行cfgmgr命令,它可自动配置系统中的新设备.当完成了系统启动后,以root用户进入系统,用lsp ...

  5. Activity 横竖屏切换

    前言 在开发中常要处理横竖屏切换,怎么处理先看生命周期 申明 Activity 横竖屏切换时需要回调两个函数 ,所以在此将这个两个函数暂时看成是Activity 横竖屏切换的生命周期的一部分,这两个函 ...

  6. LVM逻辑卷基本概念及LVM的工作原理

    这篇随笔将详细讲解Linux磁盘管理机制中的LVM逻辑卷的基本概念以及LVM的工作原理!!! 一.传统的磁盘管理 其实在Linux操作系统中,我们的磁盘管理机制和windows上的差不多,绝大多数都是 ...

  7. 家里蹲大学数学杂志 Charleton University Mathematics Journal 官方目录[共七卷493期,6055页]

    家里蹲大学数学杂志[官方网站]从由赣南师范大学张祖锦老师于2010年创刊;每年一卷, 自己有空则出版, 没空则搁置, 所以一卷有多期.本杂志至2016年12月31日共7卷493期, 6055页.既然做 ...

  8. 献给广大it从业人士:早睡早起,晚睡也早起

    早睡早起占人体健康的百分之七十:心态.饮食.及时调理各占百分之十,我们就可以知道早睡早起的重要性. 我们白天是放电,晚上睡觉是充电.晚上只冲了50%的电,白天还要释放100%,那50%哪来的?就是从五 ...

  9. 学习OpenStack之 (4): Linux 磁盘、分区、挂载、逻辑卷管理 (Logical Volume Manager)

    0. 背景: inux用户安装Linux操作系统时遇到的一个常见的难以决定的问题就是如何正确地评估各分区大小,以分配合适的硬盘空间.普通的磁盘分区管理方式在逻辑分区划分好之后就无法改变其大小,当一个逻 ...

随机推荐

  1. 1.Markdown语法

    Markdown学习 一.标题:(# +标题名字) 标题 三级标题 四级标题 二.字体 (空格内容前后的空格删掉) Hello,World! **粗体** Hello,World! *斜体* Hell ...

  2. html 移动端与PC端之间的转换

    href 里面写路径 if ((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackB ...

  3. 最新MATLAB R2021b超详细安装教程(附完整安装文件)

    摘要:本文详细介绍Matlab R2021b的安装步骤,为方便安装这里提供了完整安装文件的百度网盘下载链接供大家使用.从文件下载到证书安装本文都给出了每个步骤的截图,按照图示进行即可轻松完成安装使用. ...

  4. 1.1 Qt Creater使用Python开发桌面软件的操作流程

    Qt Creater及Python的下载与安装过程不再赘述,读者可自行在网上搜索相应的下载与安装方法. 首先我们打开Qt Creater,单击"Create Project"按钮或 ...

  5. ValidForm5.3.2 忽略表单项校验详解

    ValidForm 官方文档 项目的需求是这样的:一个checkbox,一个input,选中checkbox的时候,需要校验input,取消选中的时候,不要校验input. <input typ ...

  6. 开发并发布npm包,支持TypeScript提示,rollup构建打包

    前言: 工作了几年,想把一些不好找现成的库的常用方法整理一下,发布成npm包,方便使用.也学习一下开发发布流程. 主要用到的工具:npm. 开发库:babel.typescript.rollup.es ...

  7. Prim 最小生成树 图解

    ​ 什么是生成树 子图:G=<V,E>,G'=<V', E'>,为两个图(V为点集,即图中点的集合,E为边集),如果V'是V的子集且E'是E的子集,则G'是G的子图. 如果V' ...

  8. php错误异常及其排错

    错误和异常 错误 php程序自身的问题,一般是由非法的语法,环境问题导致 异常 一般是业务逻辑上出现的不合预期.与正常流程不同的状况,不是语法错误 错误异常继承关系 小括号表示php版本 php7下的 ...

  9. 【CSAPP】Attack Lab实验笔记

    attacklab这节玩的是利用一个字符串进行缓冲区溢出漏洞攻击,就小时候想象中黑客干的事儿. 做题的时候好几次感叹这些人的脑洞,"这都可以攻击?还能这么注入?这还可能借力打力?" ...

  10. 一文说透 MySQL JSON 数据类型(收藏)

    JSON 数据类型是 MySQL 5.7.8 开始支持的.在此之前,只能通过字符类型(CHAR,VARCHAR 或 TEXT )来保存 JSON 文档. 相对字符类型,原生的 JSON 类型具有以下优 ...