为什么 insert 配置 “SELECT LAST_INSERT_ID()” 返回个0呢?

一、前言:一个Bug

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

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

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

为什么 insert 配置 “SELECT LAST_INSERT_ID()” 返回个0呢?插图亿华云

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

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

为什么 insert 配置 “SELECT LAST_INSERT_ID()” 返回个0呢?插图1亿华云

Mybatis 的处理过程可以分为两个大部分来看,一部分是解析,另外一部分是使用。解析的时候把 Mapper XML 中的 insert 标签语句解析出来,同时解析 selectKey 标签。最终解析完成后,把解析的语句信息使用 MappedStatement 映射语句类存放起来。便于后续在 DefaultSqlSession 执行操作的时候,可以从 Configuration 配置项中获取出来使用。那么这里有一个非常重要的点,就是执行 insert 插入的时候,里面还包含了一句查询的操作。那也就是说,我们会在一次 Insert 中,包含两条执行语句。重点:bug就发生在这里,为什么呢?因为最开始这两条语句执行的时候,在获取链接的时候,每一条都是获取一个新的链接,那么也就是说,insert xxx、select LAST_INSERT_ID() 在两个 connection 连接执行时,其实是不对的,没法获取到插入后的索引 ID,只有在一个链接或者一个事务下(一次 commit)才能有事务的特性,获取插入数据后的自增ID。而因为这部分最开始手写 JdbcTransaction 实现 Transaction 接口获取连接的时候,每一次都是新的链接,代码块如下;

为什么 insert 配置 “SELECT LAST_INSERT_ID()” 返回个0呢?插图2亿华云

这里的链接获取,最开始没有 if null 的判断,每次都是直接获取链接,所以这种非一个链接下的两条 SQL 操作,所以必然不会获得到正确的结果,相当于只是单独执行SELECT LAST_INSERT_ID()

THE END
Copyright © 2024 亿华云