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

前言
技术的把控,往往都是体现在细节上!
如果说能用行,复制粘贴就能完成需求,出错了就手忙脚乱。那你一定不是一个高级开发,对很多的技术细节也都不了解。
目标
在前面所有的章节内容对 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;// ... 省略方法@Overridepublic 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/RawSqlSourceSqlSource 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");// defaultString resultMap = null;KeyGenerator keyGenerator = new NoKeyGenerator();// 解析成SqlSource,DynamicSqlSource/RawSqlSourceSqlSource 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/updatePreparedStatement 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.rhkb.cn/news/445970.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

水库抽样算法(大数据算法作业)

时隔一个多月&#xff0c;终于想起来写大数据算法基础的实验报告&#xff0c;主要是快截止了&#xff0c;hh 这两天加急把这个报告写完了~ 接下来&#xff0c;写一写证明过程&#xff08;参考书籍&#xff1a;高等教育出版社《数据科学与工程算法基础》&#xff09;主要代码以…

如何优雅的通过Spring Boot+Redission对订单实现定时关闭

简介 在电子商务及支付相关平台中&#xff0c;常规流程是首先生成订单或支付请求&#xff0c;用户随后会在规定时间内完成支付。如果用户未能在预设时限内完成支付动作&#xff0c;系统通常会执行相应的过期处理机制&#xff0c;即自动取消未支付的订单。 此外&#xff0c;这…

圈子系统APP小程序H5该如何设置IM?

搭建圈子系统的常见问题,以及圈子论坛系统的功能特点 社交圈子论坛系统的概念 圈子小程序源码 多客圈子系统 圈子是什么软件 跟进圈一个系统的软件 为圈子系统APP小程序H5设置IM&#xff08;即时通讯&#xff09;&#xff0c;需要遵循一系列步骤来确保通讯功能的稳定、安全和高…

Centos基线自动化检查脚本

此脚本是一个用于检查Linux系统安全配置的Bash脚本。它通过多项安全标准对系统进行评估&#xff0c;主要检查以下内容&#xff1a; IP地址获取&#xff1a;脚本首先获取主机的IP地址&#xff0c;确保其以10.115开头。 密码策略检查&#xff1a; 检查最小密码长度&#xff08;P…

yum仓库安装rabbitmq

yum仓库安装rabbitmq 1、配置yum仓库 vim /etc/yum.repos.d/rabbitmq.repo # In /etc/yum.repos.d/rabbitmq.repo## ## Zero dependency Erlang ##[rabbitmq_erlang] namerabbitmq_erlang baseurlhttps://packagecloud.io/rabbitmq/erlang/el/7/$basearch repo_gpgcheck1 gpg…

甲方安全和乙方安全的区别

信息安全工作&#xff0c;总会被人分成甲方和乙方&#xff0c;甲乙方原本只是商务层面需方和供方的代称&#xff0c;在安全领域&#xff0c;成了做公司内部安全和为客户提供安全的区别。 通常意义上&#xff0c;什么是甲方安全人员呢&#xff1f;就是在非安全业务的公司从事信…

从秒级到小时级:TikTok等发布首篇面向长视频理解的多模态大语言模型全面综述

文章链接&#xff1a;https://arxiv.org/pdf/2409.18938 亮点直击 追踪并总结从图像理解到长视频理解的MM-LLMs的进展;回顾了各种视觉理解任务之间的差异&#xff0c;并强调了长视频理解中的挑战&#xff0c;包括更细粒度的时空细节、动态事件和长期依赖性;详细总结了MM-LLMs在…

基于Raspberry Pi人脸识别自动门

人脸识别自动门 简介 在当今数字化时代&#xff0c;智能家居安全变得越来越重要。今天&#xff0c;我要向大家介绍一个结合了安全性与便利性的项目——人脸识别自动门。这个项目通过在门上实施基于面部识别的高级安全系统&#xff0c;使用摄像头验证房主的面部&#xff0c;自…

非线性降维方法与概率图模型

文章目录 摘要Abstract1.降维的动机1.1 线性方法方法1.1.1 主成分分析&#xff08;PCA)1.1.2 线性判别分析(LDA)1.1.3 线性降维方法中的不足 2.基于流形学习的非线性降维2.1 ISOMAP(Isometric feature mapping)2.2 LLE(locally linear embedding)2.3 LE(Laplacian Eigenmap)拉普…

Leetcode 1203. 项目管理

1.题目基本信息 1.1.题目描述 有 n 个项目&#xff0c;每个项目或者不属于任何小组&#xff0c;或者属于 m 个小组之一。group[i] 表示第 i 个项目所属的小组&#xff0c;如果第 i 个项目不属于任何小组&#xff0c;则 group[i] 等于 -1。项目和小组都是从零开始编号的。可能…

在docker的容器内如何查看Ubuntu系统版本

文章目录 写在前面一、问题描述二、解决方法参考链接 写在前面 自己的测试环境&#xff1a; docker 一、问题描述 由于 lsb_release -a 只能查看自己电脑&#xff08;宿主机&#xff09;的系统版本&#xff0c;如果在docker的容器内又应该如何查看Ubuntu系统版本呢&#xff…

mac 桌面版docker no space left on device

报错信息 docker pull镜像时报&#xff1a; failed to register layer: Error processing tar file(exit status 1): write /home/admin/oceanbase_bak/bin/observer: no space left on device 解决 增加 docker 虚拟磁盘大小。 调整完点击重启即可。

高校学科竞赛平台开发:SpringBoot技术选型与应用

3系统分析 3.1可行性分析 通过对本高校学科竞赛平台实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本高校学科竞赛平台采用SSM框架&#xff0c;JAVA作为开发语…

C#|.net core 基础 - 删除字符串最后一个字符的七大类N种实现方式

今天想通过和大家分享如何删除字符串最后一个字符的N种实现方法&#xff0c;来回顾一些基础知识点。 01第一类、字符串方式 这类方法是通过string类型自身方法直接实现。 1、Substring方法 相信大多数人第一个想到的可能就是这个方法。Substring方法是字符串内置方法&#…

【网络基础知识】网络通信概述与TCPIP、UDP协议

网络基础知识 介绍网络基础知识&#xff0c;譬如网络通信概述、OSI 七层模型、IP 地址、TCP/IP 协议族、TCP 和 UDP 协议等等&#xff0c; 旨在以引导入门、了解为主&#xff0c;其中并不会深入、详细地介绍这些内容&#xff1b; Linux网络编程入门移步&#xff1a;【Linux网络…

Mac上强大的菜单栏管理工具

想要Mac用的好&#xff0c;各种工具少不了&#xff0c;一款好用的软件对于提高使用效率和使用舒适度来说非常必要&#xff0c;iBar-强大的菜单栏图标管理工具 随着 Mac 运行的软件增加&#xff0c;状态栏中的图标也越来越多&#xff0c;不仅看得眼花缭乱&#xff0c;而且刘海屏…

小米电机与STM32——CAN通信

背景介绍&#xff1a;为了利用小米电机&#xff0c;搭建机械臂的关节&#xff0c;需要学习小米电机的使用方法。计划采用STM32驱动小米电机&#xff0c;实现指定运动&#xff0c;为此需要了解他们之间的通信方式&#xff0c;指令写入方法等。花了很多时间学习&#xff0c;但网络…

怎么把音频的速度调慢?6个方法调节音频速度

怎么把音频的速度调慢&#xff1f;调慢音频速度不仅可以帮助我们更好地捕捉细节&#xff0c;还能让我们在分析和学习时更加从容。这对于音乐爱好者来说&#xff0c;尤其有助于理解复杂的旋律和和声&#xff0c;使学习过程变得更加高效。而在语言学习中&#xff0c;放慢语速则能…

计算机网络第1章(概述)万字笔记详细版

1.1、计算机网络在信息时代的作用 计算机网络已由一种通信基础设施发展成为一种重要的信息服务基础设施计算机网络已经像水&#xff0c;电&#xff0c;煤气这些基础设施一样&#xff0c;成为我们生活中不可或缺的一部分 我国互联网发展状况 中国互联网络信息中心CNNIC 1.2、…

剪辑达人必备:四大抖音视频剪辑工具推荐!

在抖音这个短视频平台上&#xff0c;一个好的剪辑可以让视频内容更加生动有趣&#xff0c;吸引更多的观众。今天&#xff0c;我们就来探讨一下如何利用几款强大的剪辑工具&#xff0c;让你的抖音视频脱颖而出。 福昕视频剪辑&#xff1a;专业与易用并存 直达链接&#xff1a;…