手写mybatis之返回Insert操作自增索引值

news2024/11/25 6:47:36

前言
技术的把控,往往都是体现在细节上!
如果说能用行,复制粘贴就能完成需求,出错了就手忙脚乱。那你一定不是一个高级开发,对很多的技术细节也都不了解。
目标
在前面所有的章节内容对 ORM 框架的实现中,其中对 SQL 的 insert/delete/update/select 操作都是一条执行语句。
那这样有什么问题吗?这里到没有什么问题,主要的特征在于与本章节要实现的内容上想对照来看,本章节要实现的是在执行插入 SQL 后要返回此条插入语句后的自增索引。
在这里插入图片描述
当一次数据库操作有2条执行 SQL 语句的时候,重点在于必须在同一个 DB 连接下,否则将会失去事务的特性。也就表示着,如果不是同一 DB 连接下,那么返回的自增ID将会是一个 0 值。
在这里插入图片描述
以解析 Mapper XML 为入口处理 insert/delete/update/select 类型的 SQL 为入口,获取 selectKey 标签,并对此标签内的 SQL 进行解析封装。把它也当成一个查询操作,封装成映射器语句。注意:这里只会对 insert 标签起作用,其他标签并不会配置 selectKey 的操作。
当把 selectKey 解析完成以后,也是像解析其他类型的标签一样,按照 MappedStatement 映射器语句存放到 Configuration 配置项中,这样后面执行 DefaultSqlSession 获取 SQL 的时候就可以从配置项获取了,并在执行器中完成 SQL 的操作。这里要注意,对于键值的处理,是单独包装的 KeyGenerator 键值生成器,完成 SQL 的调用和结果封装的。
创建键值生成器
键值生成器 KeyGenerator 接口和对于的实现类,是用于包装对 Mapper XML insert 标签中 selectKey 下语句的处理。这个接口由3个实现类,包括 :NoKeyGenerator、Jdbc3KeyGenerator、SelectKeyGenerator,不过我们本章节只会用到 SelectKeyGenerator 以及在默认没有 selectKey 标签的情况下,使用 NoKeyGenerator 进行替代。
1:NoKeyGenerator:默认空实现不对主键单独处理。
2:Jdbc3KeyGenerator:主要用于数据库的自增主键,比如 MySQL、PostgreSQL
3:SelectKeyGenerator:主要用于数据库不支持自增主键的情况,比如 Oracle、DB2。
接口定义

public interface KeyGenerator {

    void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

    void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

}

接口实现

public class SelectKeyGenerator implements KeyGenerator {

    public static final String SELECT_KEY_SUFFIX = "!selectKey";
    private boolean executeBefore;
    private MappedStatement keyStatement;

  	// ... 省略方法
  	
    @Override
    public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
        if (!executeBefore) {
            processGeneratedKeys(executor, ms, parameter);
        }
    }

    private void processGeneratedKeys(Executor executor, MappedStatement ms, Object parameter) {
        try {
            if (parameter != null && keyStatement != null && keyStatement.getKeyProperties() != null) {
                String[] keyProperties = keyStatement.getKeyProperties();
                final Configuration configuration = ms.getConfiguration();
                final MetaObject metaParam = configuration.newMetaObject(parameter);
                if (keyProperties != null) {
                    Executor keyExecutor = configuration.newExecutor(executor.getTransaction());
                    List<Object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
                    if (values.size() == 0) {
                        throw new RuntimeException("SelectKey returned no data.");
                    } else if (values.size() > 1) {
                        throw new RuntimeException("SelectKey returned more than one value.");
                    } else {
                        MetaObject metaResult = configuration.newMetaObject(values.get(0));
                        if (keyProperties.length == 1) {
                            if (metaResult.hasGetter(keyProperties[0])) {
                                setValue(metaParam, keyProperties[0], metaResult.getValue(keyProperties[0]));
                            } else {
                                setValue(metaParam, keyProperties[0], values.get(0));
                            }
                        } else {
                            handleMultipleProperties(keyProperties, metaParam, metaResult);
                        }
                    }
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Error selecting key or setting result to parameter object. Cause: " + e);
        }
    }

}

SelectKeyGenerator 核心实现主要体现在 processAfter 方法对 processGeneratedKeys 的调用处理。在这个方法的调用过程中,通过从配置项中获取 JDBC 链接和 Executor 执行器。之后使用执行器对传入进来的 MappedStatement 执行处理,也就是对应的 keyStatement 参数。
同和前面章节讲解执行 select 语句一样,在通过执行器 keyExecutor.query 获取到结果以后,使用 MetaObject 反射工具类,向对象的属性设置查询结果。这个封装的结果,就是封装到了入参对象中对应的字段上,比如用户对象的id字段
解析selectKey
selectKey 标签主要用在 Mapper XML 中的 insert 语句里,所以我们在解析这段内容的时候,主要是对 XMLStatementBuilder XML 语句构建器的解析过程进行扩展。

public void parseStatementNode() {

    // ... 省略部分处理   

    // 解析<selectKey> 本章节新增内容
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
   
    // 解析成SqlSource,DynamicSqlSource/RawSqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, element, parameterTypeClass);
  
    // 属性标记【仅对 insert 有用】, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值 本章节新增
    String keyProperty = element.attributeValue("keyProperty");
    KeyGenerator keyGenerator = null;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
        keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
        keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
    }

    // 调用助手类 
    builderAssistant.addMappedStatement(...)
}

通过 parseStatementNode 解析 insert/delete/update/select 标签方法,扩展对 selectKey 标签的处理。processSelectKeyNodes 方法是专门用于处理 selectKey 标签下的语句的。
另外是对 keyProperty 的初始操作,因为很多时候对 SQL 的解析里面并没有 selectKey 以及获取自增主键结果的返回处理,那么这个时候会采用默认的 keyGenerator 获取处理,通常都会是实例化 NoKeyGenerator 赋值。
selectKey 处理

<selectKey keyProperty="id" order="AFTER" resultType="long">
SELECT LAST_INSERT_ID()
</selectKey>

XMLStatementBuilder#parseSelectKeyNode XML语句构建器对应的 parseSelectKeyNode 专门用于解析 selectKey 标签下的 SQL 以及返回类型进行封装。

private void parseSelectKeyNode(String id, Element nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver) {
    String resultType = nodeToHandle.attributeValue("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    boolean executeBefore = "BEFORE".equals(nodeToHandle.attributeValue("order", "AFTER"));
    String keyProperty = nodeToHandle.attributeValue("keyProperty");
    
    // default
    String resultMap = null;
    KeyGenerator keyGenerator = new NoKeyGenerator();
    
    // 解析成SqlSource,DynamicSqlSource/RawSqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);
    SqlCommandType sqlCommandType = SqlCommandType.SELECT;
    
    // 调用助手类
    builderAssistant.addMappedStatement(id,
            sqlSource,
            sqlCommandType,
            parameterTypeClass,
            resultMap,
            resultTypeClass,
            keyGenerator,
            keyProperty,
            langDriver);
    
    // 给id加上namespace前缀
    id = builderAssistant.applyCurrentNamespace(id, false);
    
    // 存放键值生成器配置
    MappedStatement keyStatement = configuration.getMappedStatement(id);
    configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));
}

在 parseSelectKeyNode 中先进行对 selectKey 标签上,resultType、keyProperty 等属性的解析,之后解析 SQL 封装成 SqlSource,最后阶段是对解析信息的保存处理。分别存放成 MappedStatement 映射器语句、SelectKeyGenerator 键值生成器。
扩展预处理语句处理器
StatementHandler 语句处理器接口所定义的方法,在 SQL 执行上只有 update 和 query,所以我们要扩展的 insert 操作,也是对 update 方法的扩展操作处理。

public int update(Statement statement) throws SQLException {
		// 1. 执行 insert/delete/update
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    
    // 2. 执行 selectKey 语句
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
}

JDBC链接获取
由于是在同一个操作下,处理两条SQL,分别是插入和返回索引值。那么这两条 SQL 其实要在同一个链接下才能正确的获取到结果,也就是保证了一个事务的特性。

@Override
public Connection getConnection() throws SQLException {
    // 本章节新增;多个SQL在同一个JDBC连接下,才能完成事务特性
    if (null != connection) {
        return connection;
    }
    connection = dataSource.getConnection();
    connection.setTransactionIsolation(level.getLevel());
    connection.setAutoCommit(autoCommit);
    return connection;
}

也就是 JdbcTransaction#getConnection 方法,在前面章节中,我们实现时候只是一个 dataSource.getConnection() 获取链接,这样就相当于每次获得的连接都是一个新的连接。那么两条SQL的执行分别在各自的JDBC连接下,则不会正确的返回插入后的索引值。
所以这里我们进行判断,如果连接不为空,则不在创建新的JDBC连接,使用当前连接即可。这里的情况和 Spring 中的事务处理是一样的,Spring中需要在 ThreadLocal 保存连接
测试
配置Mapper XML 语句

<insert id="insert" parameterType="com.lm.mybatis.test.po.Activity">
    INSERT INTO activity
    (activity_id, activity_name, activity_desc, create_time, update_time)
    VALUES (#{activityId}, #{activityName}, #{activityDesc}, now(), now())

    <selectKey keyProperty="id" order="AFTER" resultType="long">
        SELECT LAST_INSERT_ID()
    </selectKey>
</insert>

在 insert 标签下,添加 selectKey 标签,并使用 SELECT LAST_INSERT_ID() 查询方法返回自增索引值。这个值会返回到入参对象 Activity.id 中
单元测试

@Test
public void test_insert() {
    // 1. 获取映射器对象
    IActivityDao dao = sqlSession.getMapper(IActivityDao.class);
    Activity activity = new Activity();
    activity.setActivityId(10004L);
    activity.setActivityName("测试活动");
    activity.setActivityDesc("测试数据插入");
    activity.setCreator("xiaofuge");

    // 2. 测试验证
    Integer res = dao.insert(activity);
    sqlSession.commit();
    logger.info("测试结果:count:{} idx:{}", res, JSON.toJSONString(activity.getId()));
}

总结
是在原有的 Mapper XML 对各类标签语句的解析中,对 insert 操作进行扩展,添加新的标签 selectKey 并通过这样一个标签的解析、执行、封装处理把最终的插入索引结果返回到入参对象的对应属性字段上。那么同时我们所处理的是类似 Mysql 这样带有自增索引的数据库,用这样的方式来串联起整个流程。
另外这里要注意,我们本章节是首次在一个操作中执行2条SQL语句,为了能让最后可以查询到自增索引,那么这两条 SQL 必须是在同一个链接下。读者在学习的过程中,可以尝试将 JdbcTransaction#getConnection 方法中的判断是否获取新的 JDBC 连接去掉,每次都获取最新的连接,运行测试看是否还能获得到插入后的索引值。

好了到这里就结束了手写mybatis之返回Insert操作自增索引值的学习,大家一定要跟着动手操作起来。需要源码的 可si我获取;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2212222.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VMware16虚拟机安装macOS Monterey 12详细教程

1、虚拟机配置安装 安装WMware Workstation 16,打开安包装包,只需点下一步即可,安装过程略。 安装完毕后,检查任务管理器,如果有VMware程序运行,就结束任务。 打开【运行】,快捷键win+R,输入services.msc 找到所有VMware开头的服务 将这些VMware服务逐一停用。 鼠标…

机器学习(10.7-10.13)(Pytorch LSTM和LSTMP的原理及其手写复现)

文章目录 摘要Abstract1 LSTM1.1 使用Pytorch LSTM1.1.1 LSTM API代码实现1.1.2 LSTMP代码实现 1.2 手写一个lstm_forward函数 实现单向LSTM的计算原理1.3 手写一个lstmp_forward函数 实现单向LSTMP的计算原理总结 摘要 LSTM是RNN的一个优秀的变种模型&#xff0c;继承了大部分…

【论文阅读笔记】Bigtable: A Distributed Storage System for Structured Data

文章目录 1 简介2 数据模型2.1 行2.2 列族2.3 时间戳 3 API4 基础构建4.1 GFS4.2 SSTable4.3 Chubby 5 实现5.1 Tablet 位置5.2 Tablet 分配5.3 为 tablet 提供服务5.4 压缩5.4.1 小压缩5.4.2 主压缩 6 优化6.1 局部性组6.2 压缩6.3 缓存6.4 布隆过滤器6.5 Commit日志实现6.6 T…

金融信用评分卡建模项目1:工具雏形

最近我一直忙着开发一个信用评分卡建模工具&#xff0c;所以没有时间更新示例或动态。今天&#xff0c;我很高兴地跟大家分享&#xff0c;这个工具的基本框架已经完成了&#xff0c;并且探索性的将大语言模型&#xff08;AI&#xff09;整合了进去。目前ai在工具中扮演智能助手…

力扣面试150 从中序与后序遍历序列构造二叉树 递归

Problem: 106. 从中序与后序遍历序列构造二叉树 &#x1f468;‍&#x1f3eb; 参考题解 &#x1f37b; Code 1 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNo…

前端的AI工具:ChatGPT Canvas与Claude Artifacts对比 -仅仅是OpenAI一个迟来的追赶吗?- 贺星舰五飞试验成功

如果你对OpenAI的ChatGPT Canvas和Anthropic的Claude Artifacts有所耳闻&#xff0c;可能会想知道这两个工具有何不同&#xff0c;以及哪个能让你的工作流程更加顺畅。这两个工具旨在提升生产力&#xff0c;但侧重点各异——编码、写作、创意和实时反馈。 本文将深入探讨ChatG…

STM32传感器模块编程实践(四)舵机+MPU6050陀螺仪模块融合云台模型

文章目录 一.概要二.实验模型原理1.硬件连接原理框图2.控制原理 三.实验模型控制流程四.云台模型程序五.实验效果视频六.小结 一.概要 云台主要用来固定摄像头。准确地说&#xff0c;云台是一种可以多角度调节的支撑设备&#xff0c;类似于人的脖子可以支撑着脑袋&#xff0c;…

C++STL--------vector

文章目录 一、vector常用接口介绍1、initializer_list2、接口有很多类似3、typeid(类型).name()4、find() 函数5、内置类型构造 二、vector()常用接口模拟实现 截图来源网站&#xff1a;https://legacy.cplusplus.com/reference/vector/vector/ 一、vector常用接口介绍 是一个…

架构设计笔记-8-系统质量属性与架构评估

目录 知识要点 案例分析 1.质量属性 2.非功能性需求 3.质量属性效用树&#xff0c;风险点/敏感点/权衡点&#xff0c;设计策略 4.管道过滤器/仓库风格&#xff0c;质量属性 5.质量属性效用树 6.质量属性 7.质量属性效用树 8.质量属性效用树&#xff0c;风险点/敏感点…

架构师备考-背诵精华(架构开发方法)

软件架构风格 类型 子类型 说明 数据流风格 批处理 每个处理步骤是一个单独的程序&#xff0c;每一步必须在前一步结束后才能开始&#xff0c;而且数据必须是完整的&#xff0c;以整体的方式传递。 管道过滤器 把系统分解为几个序贯的处理步骤&#xff0c;这些步骤之间…

目标检测系统【环境详细配置过程】(CPU版本)

&#xff08;如果你使用的是笔记本电脑&#xff0c;没有比较好的GPU&#xff0c;可以配置CPU运行环境&#xff09; 链接&#xff1a;上百种【基于YOLOv8/v10/v11的目标检测系统】目录&#xff08;pythonpyside6界面系统源码可训练的数据集也完成的训练模型&#xff09; 1.安装…

leetcode热题100.编辑距离

题目 72. 编辑距离 - 力扣&#xff08;LeetCode&#xff09; 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作&#xff1a; 插入一个字符删除一个字符替换一个字符 示例 1&#xff1a; 输…

Spring Web MVC快速入门:掌握Java Web开发基础

White graces&#xff1a;个人主页 &#x1f649;专栏推荐:Java入门知识&#x1f649; &#x1f439;今日诗词:桃李春风一杯酒&#xff0c;江湖夜雨十年灯&#x1f439; ⛳️点赞 ☀️收藏⭐️关注&#x1f4ac;卑微小博主&#x1f64f; ⛳️点赞 ☀️收藏⭐️关注&#x1f4…

基于Arduino的红外遥控智能小车实现方法

一、简介 使用红外遥控器实现智能小车前进、后退、左转、右转、停止运动五种动作。 二、控制方法 使用红外遥控器遥控智能小车运行之前&#xff0c;需要使用红外线接收电路来读取红外线遥控器的按键代码&#xff0c;将获取的按键代码定义为控制智能小车前进、后退、左转、右…

Web Socket 使用详解

在信息爆炸的时代&#xff0c;用户对网页的期待早已超越了静态内容的展示。实时聊天、股票报价、协同编辑等功能的实现&#xff0c;都离不开服务器与客户端之间持续、高效的数据交互。传统的HTTP请求-响应模型难以满足这种需求&#xff0c;而WebSocket的出现&#xff0c;为构建…

个人健康系统|个人健康数据管理系统|基于小程序+java的个人健康数据管理系统设计与实现(源码+数据库+文档)

个人健康数据管理系统 目录 基于小程序java的个人健康数据管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师…

C语言刷题 LeetCode 删除单链表的重复节点 双指针法

题目要求 链表结构&#xff1a;题目中提到的是未排序的链表&#xff0c;链表是由一系列节点组成的&#xff0c;每个节点包含一个值&#xff08;数据&#xff09;和一个指向下一个节点的指针。去重&#xff1a;我们需要遍历链表&#xff0c;删除所有重复的节点&#xff0c;只保…

开源新生活,社区齐乐活:COSCon'24 社区合作和开源集市招募中,诚邀广大社区参与!...

一年一度的开源盛会&#xff0c;COSCon24第九届中国开源年会暨开源社10周年嘉年华&#xff0c;将于11月2-3日&#xff0c;在北京•中关村国家自主创新示范区展示中心召开&#xff01;本次大会的主题是&#xff1a;「Open Source&#xff0c;Open Life | 开源新生活」&#xff0…

react antd redux 全局状态管理 解决修改菜单状态 同步刷新左侧菜单

npm i react-redux1.src新建两个文件 globalState.js 全局状态定义 store.js 全局存储定义 2.globalState.js import { createSlice } from "reduxjs/toolkit";export const globalState createSlice({name: "globalState",initialState: { data: {} },r…

Qt 学习第 天:QPainter类

一、先创建一个widget窗口 二、包含头文件 #include <QPainter> #include <QFont> 三、在widget.h中声明paintEvent函数 使用画家类在窗口中画图, 操作必须在paintEvent函数中完成 #ifndef WIDGET_H #define WIDGET_H#include <QWidget>namespace Ui { cla…