mybatis/mp批量插入非自增主键数据

文章目录

  • 前言
  • 一、mp的批量插入是假的
  • 二、真正的批量插入
    • 1.利用sql注入器处理
    • 2.采用自编码,编写xml批量执行
      • 生成内容如下:
  • 三 问题
    • 问题描述
    • 问题原因
    • 问题解决
      • 粘贴一份,兼容集合
      • 替换原有文件
  • 总结
        • 自增与非自增区别:


前言

mybatis/mp 在实际开发中是常用的优秀持久层框架,但是在非自增主键的时候,单条数据插入式可以的,当批量插入的时候,如何做到填充主键呢?

这里的批量插入是指执行真正的批量插入!


一、mp的批量插入是假的

mp中细心的小伙伴会发现,批量插入是假的,以下是mybatis-plus的源码

    public boolean saveBatch(Collection<T> entityList, int batchSize) {String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE);return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {sqlSession.insert(sqlStatement, entity);});}

问题就出在这里: 循环调用的insert方法,不是insert into values

我想做的是做一个真正的批量执行,然后插入,但是问题是,当插入的时候,非自增主键没有被填充!!!

二、真正的批量插入

1.利用sql注入器处理

可以参考我的另一篇文章实现mybatis-plus 批量插入修改

2.采用自编码,编写xml批量执行

  1. 可以采用easycode生成器,选择最新修复版本,会自动生成xml批量插入
    easycode
  2. 直接手写

生成内容如下:

  1. mapper/dao
    /*** 批量新增数据(MyBatis原生foreach方法)** @param entities List<Goods> 实例对象列表* @return 影响行数*/int insertBatch(@Param("entities") List<Goods> entities);
  1. xml
    <!-- 批量插入 --><insert id="insertBatch" keyProperty="id">insert into dbo.goods(id,name, code, price)values<foreach collection="entities" item="entity" separator=",">(#{entity.id},#{entity.name}, #{entity.code}, #{entity.price})</foreach></insert>

这就能直接调用生成的 insertBatch 去执行真正的批量执行了!


三 问题

问题描述

虽然如上两种实现了批量插入,但是都有问题, 批量插入无法生成id,导致插入失败,因为主键不能为空

问题原因

经过不断 断点 跟踪mybatis执行,发现在 MybatisParameterHandler 中,如下代码有问题:

    private void process(Object parameter) {if (parameter != null) {TableInfo tableInfo = null;Object entity = parameter;if (parameter instanceof Map) {Map<?, ?> map = (Map)parameter;if (map.containsKey("et")) {Object et = map.get("et");if (et != null) {entity = et;tableInfo = TableInfoHelper.getTableInfo(et.getClass());}}} else {tableInfo = TableInfoHelper.getTableInfo(parameter.getClass());}if (tableInfo != null) {MetaObject metaObject = this.configuration.newMetaObject(entity);if (SqlCommandType.INSERT == this.sqlCommandType) {this.populateKeys(tableInfo, metaObject, entity);this.insertFill(metaObject, tableInfo);} else {this.updateFill(metaObject, tableInfo);}}}}

其中问题为:

	if (parameter instanceof Map) {Map<?, ?> map = (Map)parameter;if (map.containsKey("et")) {Object et = map.get("et");if (et != null) {entity = et;tableInfo = TableInfoHelper.getTableInfo(et.getClass());}}} else {tableInfo = TableInfoHelper.getTableInfo(parameter.getClass());}

此处 map.containsKey("et") 不对,因为我这里是批量插入,传入的不是et,就算我把参数名称改为et也不行
因为我是批量执行,这里获取到的应该是一个集合,list
但是这里是按照一个对象处理的 TableInfoHelper.getTableInfo(et.getClass())
所以一直获取不到tableInfo ,导致后续无法执行填充主键逻辑

问题解决

思路:

  1. 将MybatisParameterHandler 重新粘贴一份,然后修改上述的问题,增加判断逻辑,处理集合;
  2. 将当前的文件替换原有文件,使得mybatis执行的时候,走我这份文件即可(覆盖之前不兼容集合的文件)

粘贴一份,兼容集合

package com.baomidou.mybatisplus.core;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.*;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;/*** @author fulin* @since 2023/9/20 17:38*/
public class MybatisParameterHandler implements ParameterHandler {private final TypeHandlerRegistry typeHandlerRegistry;private final MappedStatement mappedStatement;private final Object parameterObject;private final BoundSql boundSql;private final Configuration configuration;private final SqlCommandType sqlCommandType;public MybatisParameterHandler(MappedStatement mappedStatement, Object parameter, BoundSql boundSql) {this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();this.mappedStatement = mappedStatement;this.boundSql = boundSql;this.configuration = mappedStatement.getConfiguration();this.sqlCommandType = mappedStatement.getSqlCommandType();this.parameterObject = processParameter(parameter);}public Object processParameter(Object parameter) {/* 只处理插入或更新操作 */if (parameter != null&& (SqlCommandType.INSERT == this.sqlCommandType || SqlCommandType.UPDATE == this.sqlCommandType)) {//检查 parameterObjectif (ReflectionKit.isPrimitiveOrWrapper(parameter.getClass())|| parameter.getClass() == String.class) {return parameter;}Collection<Object> parameters = getParameters(parameter);if (null != parameters) {// 感觉这里可以稍微优化一下,理论上都是同一个.parameters.forEach(this::process);} else {process(parameter);}}return parameter;}@Overridepublic Object getParameterObject() {return this.parameterObject;}private void process(Object parameter) {if (parameter != null) {TableInfo tableInfo = null;Object entity = parameter;if (parameter instanceof Map) {Map<?, ?> map = (Map<?, ?>) parameter;if (map.containsKey(Constants.ENTITY)) {Object et = map.get(Constants.ENTITY);if (et != null) {entity = et;tableInfo = TableInfoHelper.getTableInfo(entity.getClass());}}if (map.containsKey("entities")) {List list = (List<Object>) map.get("entities");if (CollectionUtils.isEmpty(list)) {return;}Optional first = list.stream().findFirst();if (!first.isPresent()) {return;}entity = first.get();tableInfo = TableInfoHelper.getTableInfo(entity.getClass());}} else {tableInfo = TableInfoHelper.getTableInfo(parameter.getClass());}if (tableInfo != null) {//到这里就应该转换到实体参数对象了,因为填充和ID处理都是争对实体对象处理的,不用传递原参数对象下去.MetaObject metaObject = this.configuration.newMetaObject(entity);if (SqlCommandType.INSERT == this.sqlCommandType) {populateKeys(tableInfo, metaObject, entity);insertFill(metaObject, tableInfo);} else {updateFill(metaObject, tableInfo);}}}}protected void populateKeys(TableInfo tableInfo, MetaObject metaObject, Object entity) {final IdType idType = tableInfo.getIdType();final String keyProperty = tableInfo.getKeyProperty();if (StringUtils.isNotBlank(keyProperty) && null != idType && idType.getKey() >= 3) {final IdentifierGenerator identifierGenerator = GlobalConfigUtils.getGlobalConfig(this.configuration).getIdentifierGenerator();Object idValue = metaObject.getValue(keyProperty);if (StringUtils.checkValNull(idValue)) {if (idType.getKey() == IdType.ASSIGN_ID.getKey()) {if (Number.class.isAssignableFrom(tableInfo.getKeyType())) {metaObject.setValue(keyProperty, identifierGenerator.nextId(entity));} else {metaObject.setValue(keyProperty, identifierGenerator.nextId(entity).toString());}} else if (idType.getKey() == IdType.ASSIGN_UUID.getKey()) {metaObject.setValue(keyProperty, identifierGenerator.nextUUID(entity));}}}}protected void insertFill(MetaObject metaObject, TableInfo tableInfo) {GlobalConfigUtils.getMetaObjectHandler(this.configuration).ifPresent(metaObjectHandler -> {if (metaObjectHandler.openInsertFill()) {if (tableInfo.isWithInsertFill()) {metaObjectHandler.insertFill(metaObject);} else {// 兼容旧操作 id类型为input或none的要用填充器处理一下if (metaObjectHandler.compatibleFillId()) {String keyProperty = tableInfo.getKeyProperty();if (StringUtils.isNotBlank(keyProperty)) {Object value = metaObject.getValue(keyProperty);if (value == null && (IdType.NONE == tableInfo.getIdType() || IdType.INPUT == tableInfo.getIdType())) {metaObjectHandler.insertFill(metaObject);}}}}}});}protected void updateFill(MetaObject metaObject, TableInfo tableInfo) {GlobalConfigUtils.getMetaObjectHandler(this.configuration).ifPresent(metaObjectHandler -> {if (metaObjectHandler.openUpdateFill() && tableInfo.isWithUpdateFill()) {metaObjectHandler.updateFill(metaObject);}});}/*** 处理正常批量插入逻辑* <p>* org.apache.ibatis.session.defaults.DefaultSqlSession$StrictMap 该类方法* wrapCollection 实现 StrictMap 封装逻辑* </p>** @return 集合参数*/@SuppressWarnings({"rawtypes", "unchecked"})protected Collection<Object> getParameters(Object parameterObject) {Collection<Object> parameters = null;if (parameterObject instanceof Collection) {parameters = (Collection) parameterObject;} else if (parameterObject instanceof Map) {Map parameterMap = (Map) parameterObject;if (parameterMap.containsKey("collection")) {parameters = (Collection) parameterMap.get("collection");} else if (parameterMap.containsKey("list")) {parameters = (List) parameterMap.get("list");} else if (parameterMap.containsKey("array")) {parameters = Arrays.asList((Object[]) parameterMap.get("array"));}}return parameters;}@Override@SuppressWarnings("unchecked")public void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();if (parameterMappings != null) {for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping parameterMapping = parameterMappings.get(i);if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;String propertyName = parameterMapping.getProperty();if (this.boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional paramsvalue = this.boundSql.getAdditionalParameter(propertyName);} else if (this.parameterObject == null) {value = null;} else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);value = metaObject.getValue(propertyName);}TypeHandler typeHandler = parameterMapping.getTypeHandler();JdbcType jdbcType = parameterMapping.getJdbcType();if (value == null && jdbcType == null) {jdbcType = this.configuration.getJdbcTypeForNull();}try {typeHandler.setParameter(ps, i + 1, value, jdbcType);} catch (TypeException | SQLException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);}}}}}
}

这里面仅仅加了 如下这一段,其他完全复制

 if (map.containsKey("entities")) {List list = (List<Object>) map.get("entities");if (CollectionUtils.isEmpty(list)) {return;}Optional first = list.stream().findFirst();if (!first.isPresent()) {return;}entity = first.get();tableInfo = TableInfoHelper.getTableInfo(entity.getClass());}

替换原有文件

在本地项目中创建于mybatis中MybatisParameterHandler 同包目录,再将同名文件放入即可
同包同名放入

这样就可以通过本地文件,覆盖jar包中的文件了

总结

对于此次的问题,是因为之前一直用的是自增主键,今天改为非自增主键,导致该问题;

自增与非自增区别:
  1. 自增: 数据库层面的ID填充
  2. 非自增: 代码层面的数据ID填充,需要在插入之前就获取到ID,并填充到实体中

此次问题解决,对于mybatis执行流程也有了更加清晰的认知,与君共勉~
文中涉及所有代码: 代码地址

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

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

相关文章

Linux:GlusterFS 集群

GlusterFS介绍 1&#xff09;Glusterfs是一个开源的分布式文件系统,是Scale存储的核心,能够处理千数量级的客户端.在传统的解决 方案中Glusterfs能够灵活的结合物理的,虚拟的和云资源去体现高可用和企业级的性能存储. 2&#xff09;Glusterfs通过TCP/IP或InfiniBand RDMA网络链…

【C++】String类基本接口介绍及模拟实现(多看英文文档)

string目录 如果你很赶时间&#xff0c;那么就直接看我本标题下的内容即可&#xff01;&#xff01; 一、STL简介 1.1什么是STL 1.2STL版本 1.3STL六大组件 1.4STL重要性 1.5如何学习STL 二、什么是string&#xff1f;&#xff1f;&#xff08;本质上是一个类&#xff0…

【Redis】深入探索 Redis 的数据类型 —— 列表 List

文章目录 一、List 类型介绍二、List 类型相关命令2.1 LPUSH 和 RPUSH、LPUSHX 和 RPUSHX2.2 LPOP 和 RPOP、BLPOP 和 BRPOP2.3 LRANGE、LINDEX、LINSERT、LLEN2.4 列表相关命令总结 三、List 类型内部编码3.1 压缩列表&#xff08;ziplist&#xff09;3.2 链表&#xff08;lin…

Git错误解决:如何处理“could not determine hash algorithm“问题

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

openssl创建CA证书教程

配置生成CA证书 总示意图&#xff1a; (1)&#xff0c;通过openssl创建CA证书 第一步&#xff1a;创建一个秘钥&#xff0c;这个便是CA证书的根本&#xff0c;之后所有的东西都来自这个秘钥 # 通过rsa算法生成2048位长度的秘钥 openssl genrsa -out myCA.key 2048 第二步&#…

Android Camera2获取摄像头的视场角(FOV)信息

一、概念 FOV&#xff08;Field of View&#xff09;是一个用于描述视野范围的术语。它通常用于计算设备&#xff08;如摄像机、虚拟现实头显或眼睛&#xff09;所能捕捉到的可见区域。 水平FOV&#xff08;Horizontal FOV&#xff09;&#xff1a;描述视野在水平方向上的范围…

JVM面试题-JVM对象的创建过程、内存分配、内存布局、访问定位等问题详解

对象 内存分配的两种方式 指针碰撞 适用场合&#xff1a;堆内存规整&#xff08;即没有内存碎片&#xff09;的情况下。 原理&#xff1a;用过的内存全部整合到一边&#xff0c;没有用过的内存放在另一边&#xff0c;中间有一个分界指针&#xff0c;只需要向着没用过的内存…

【最新面试问题记录持续更新,java,kotlin,android,flutter】

最近找工作&#xff0c;复习了下java相关的知识。发现已经对很多概念模糊了。记录一下。部分是往年面试题重新整理&#xff0c;部分是自己面试遇到的问题。持续更新中~ 目录 java相关1. 面向对象设计原则2. 面向对象的特征是什么3. 重载和重写4. 基本数据类型5. 装箱和拆箱6. …

【数据结构】顺序表与ArrayList

作者主页&#xff1a;paper jie 的博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《JAVA数据结构》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精…

Chinese-LLaMA-AIpaca

文章目录 关于 Chinese-LLaMA-Alpaca一、LLaMA模型 --> HF格式二、合并LoRA权重,生成全量模型权重方式1:单LoRA权重合并方式2:多LoRA权重合并(适用于Chinese-Alpaca-Plus )三、使用 Transformers 进行推理四、使用 webui 搭建界面1、克隆text-generation-webui并安装必…

企业图档加密系统

机械制造行业数据安全 机械制造企业对于设计工艺的能力要求非常高&#xff0c;其生产工业会涉及到大量设计图纸文档信息&#xff0c;一旦发生产品图纸丢失泄密情况&#xff0c;将造成重大损失。如何用技术手段保护企业的核心数据&#xff0c;保证企业的信息资料不会被无意或恶…

Clock Domain Crossing Design Verification Techniques Using System Verilog 学习

重要的设计考虑因素要求仔细构建多时钟设计时钟域交叉 (CDC) 边界。 本文详细介绍了一些最新策略和解决跨 CDC 边界传递一个或多个信号的最佳已知方法。论文中包含与 CDC 验证相关的技术和一个有趣的 2 深 FIFO用于在时钟域之间传递多个控制信号的设计。 虽然设计方法论文中描述…

WebGL 用鼠标控制物体旋转

目录 鼠标控制物体旋转 如何实现物体旋转 示例程序&#xff08;RotateObject.js&#xff09; 代码详解 示例效果 鼠标控制物体旋转 有时候&#xff0c;WebGL程序需要让用户通过鼠标操作三维物体。这一节来分析示例程序RotateObject&#xff0c;该程序允许用户通过拖动&…

2023华为杯研究生数学建模竞赛选题建议+初步分析

如下为C君的2023华为杯研究生数学建模竞赛&#xff08;研赛&#xff09;选题建议初步分析 2023华为杯研究生数学建模竞赛&#xff08;研赛&#xff09;选题建议 提示&#xff1a;DS C君认为的难度&#xff1a;CE<D<F&#xff0c;开放度&#xff1a;CDE<F。 华为专项…

1600*A. LCM Challenge(数论 || 找规律)

解析&#xff1a; n<3&#xff0c;特判 n为奇数&#xff0c;则n、n-1、n-2必定互质&#xff0c;所以结果即为三者之和。 n为偶数&#xff0c; 不会严格证明原因&#xff0c;但是找找规律&#xff0c;是这样的...... #include<bits/stdc.h> using namespace std; #de…

数据科学的文本技术 Text Technology(IR信息检索、搜索引擎)

一、文章摘要 1. 内容 * Introduction to IR and text processing, system components * Zipf, Heaps, and other text laws * Pre-processing: tokenization, normalisation, stemming, stopping. * Indexing: inverted index, boolean and proximity search * Evaluation m…

【RocketMQ专题】快速实战及集群架构原理详解

目录 课程内容一、MQ简介基本介绍*作用&#xff08;解决什么问题&#xff09; 二、RocketMQ产品特点2.1 RocketMQ介绍2.2 RocketMQ特点2.3 RocketMQ的运行架构2.4 消息模型 三、RocketMQ快速实战3.1 快速搭建RocketMQ服务3.2 快速实现消息收发3.3 搭建Maven客户端项目3.4 搭建R…

vue+axios+el-progress(elementUI组件)实现下载进度条实时监听(小白简洁版)

一、实现效果 二、实现方式 方案&#xff1a;使用axios方法onDownloadProgress方法监听下载进度 使用此方式的前提&#xff01;&#xff01;&#xff01;请让后端在响应头中加上content-length&#xff0c;存放下载文件的总大小&#xff0c;如下图&#xff1a; 三、代码 1、进…

U盘格式化后数据能恢复吗?详细答案看这里!

“U盘格式化之后数据还可以恢复吗&#xff1f;这真的困扰了我好久!之前由于u盘中病毒&#xff0c;不得已将它格式化了&#xff0c;但是我还有好多视频、图片都保存在里面&#xff0c;这该怎么办呢&#xff1f;” 小小的u盘&#xff0c;可是给我们带来了很多的便利的。在互联网时…

【直播预约中】 腾讯大数据 x StarRocks|构建新一代实时湖仓

随着信息时代的兴起&#xff0c;数据已成为推动业务决策和创新的核心要素&#xff1b;结构化、半结构化等多种类型的数据呈现爆炸式增长&#xff0c;如何高效处理和分析海量数据已经成为关键挑战&#xff0c;结合传统数仓与数据湖优势的湖仓一体&#xff08;Lakehouse&#xff…