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

news2025/2/25 14:36:41

文章目录

  • 前言
  • 一、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)) {
            //检查 parameterObject
            if (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;
    }

    @Override
    public 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 params
                        value = 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.coloradmin.cn/o/1031057.html

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

相关文章

实现表格表头自定义编辑、一键导入、增加列

1.前言 本文基于vue2及elementUI的表格组件 2.效果及功能展示 3.需求背景 有时候客户急需看到需求实现的页面&#xff0c;而此时后端接口没有&#xff0c;产品原型没有&#xff0c;只能前端出马&#xff0c;画一个静态页面&#xff0c;来展示客户想要的东西&#xff0c;如果是…

P1827 [USACO3.4] 美国血统 American Heritage(前序 + 中序 生成后序)

P1827 [USACO3.4] 美国血统 American Heritage&#xff08;前序 中序 生成后序&#xff09; 一、前言 二叉树入门题。涉及到树的基本知识、树的结构、树的生成。 本文从会从结构&#xff0c;到完成到&#xff0c;优化。 二、基础知识 Ⅰ、二叉树的遍历 前序遍历&#xff…

CRM软件系统维护客户的主要方法

客户的重要性&#xff0c;相信每一个做企业的人都非常清楚。为了维护好客户&#xff0c;很多企业都使用CRM客户管理系统&#xff0c;建立“以客户为中心”的经营理念&#xff0c;提高企业客户服务水平&#xff0c;进而在提高客户满意度的同时提高企业的盈利。那么&#xff0c;企…

ubuntu、linux in window安装docker教程

1、首先进入管理员权限。 2、更新软件源。 sudo apt update 3、安装一些依赖 sudo apt install apt-transport-https ca-certificates curl software-properties-common 4、为系统添加Docker的密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-k…

28.CSS 渐变圆文本动画

效果 源码 index.html <!doctype html> <html> <head><meta charset="utf-8"><title>Glowing Gradient Circle Text Animation</title><link rel="stylesheet" href="style.css"> </head> &l…

合同被篡改,被变更,被调换风险大?君子签电子合同有效化解

纸质合同签署文件类型多&#xff0c;签署量大&#xff0c;人为干预较多&#xff0c;合同被篡改&#xff0c;被变更&#xff0c;被调换风险大&#xff0c;难以防范和避免。 请注意&#xff0c;出现以下几个情况&#xff0c;代表你已经遭遇合同“调包计”了&#xff01; 1、合…

“undefined reference to XXX“问题总结

"undefined reference to XXX"问题总结 引言 我们在Linux下用C/C工作的时候&#xff0c;经常会遇到"undefined reference to XXX"的问题&#xff0c;直白地说就是在链接(从.cpp源代码到可执行的ELF文件&#xff0c;要经过预处理->编译->链接三个阶…

换台电脑python使用uiautomator2操作逍遥模拟器

前几天写了一篇文章python使用uiautomator2操作雷电模拟器_小小爬虾的博客-CSDN博客 今天用另外一个环境和模拟器再次测试。 环境如下&#xff1a;win7 sp1 64位&#xff1b;Python3.8.10&#xff1b;逍遥模拟器9.0.6&#xff1b;android版本9&#xff1b;逍遥模拟器自带adb版…

【数据结构】顺序表与ArrayList

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

2023研究生数学建模D题思路代码 区域双碳目标与路径规划研究

D题思路代码 区域双碳目标与路径规划研究 完整解题思路可看视频&#xff1a; 2023华为杯研赛D题区域双碳目标与路径规划研究&#xff08;附代码全保姆教程&#xff09;_哔哩哔哩_bilibili​www.bilibili.com/video/BV1Cm4y157CH/?spm_id_from333.999.0.0 问题一&#xff1a;…

将序列中的每一行重复扩展为指定数量的行Series.repeat()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 将序列中的每一行 重复扩展为指定数量的行 Series.repeat() [太阳]选择题 以下说法正确的是 import pandas as pd s1pd.Series([10, 25, 3]) s1.index [A, B, C] print(【显示】s1为&#xff…

超级好用的高效率记笔记软件

高效率的记录学习笔记&#xff0c;可以帮助我们日后进行知识的回顾、学习及分享&#xff0c;对于有记录学习笔记需求的学生来讲&#xff0c;在日常生活中找一款好用的记录学习笔记的非常有必要的&#xff0c;手机作为一款比较便携的工具&#xff0c;在手机上安装笔记类工具&…

家居行业如何借助AI营销数智化转型?《2023 家居行业AI营销第一课(重庆站)》给你答案

商务部将2023年定为“消费提振年”。作为仅次于汽车消费的家庭第二大消费支出&#xff0c;家居产业的高质量发展与扩大内需提振消费息息相关。随着今年利好政策不断发布&#xff0c;家居建材行业的市场环境及消费潜力得到大幅度改善。 随着ChatGPT等新技术的出现与消费需求升级…

安全基础 --- nodejs沙箱逃逸

nodejs沙箱逃逸 沙箱绕过原理&#xff1a;沙箱内部找到一个沙箱外部的对象&#xff0c;借助这个对象内的属性即可获得沙箱外的函数&#xff0c;进而绕过沙箱 前提&#xff1a;使用vm模块&#xff0c;实现沙箱逃逸环境。&#xff08;vm模式是nodejs中内置的模块&#xff0c;是no…

【腾讯云】打造未来智能应用的基石:腾讯混元大模型

写在前面&#xff1a;博主是一只经过实战开发历练后投身培训事业的“小山猪”&#xff0c;昵称取自动画片《狮子王》中的“彭彭”&#xff0c;总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域&#xff0c;如今终有小成…

vue-cli-service build 不同环境的配置

目录 &#x1f91c; 背景 &#x1f91c; vue-cli-service介绍 &#x1f91c; 环境变量和模式 &#x1f91c; 配置不同模式 &#x1f91c;index.html使用环境变量 &#x1f91c; 验证 &#x1f91c; 参考资料 &#x1f91c; 背景 在项目部署时&#xff0c;我们需要在测试…

如何通过文件自动备份软件进行自动化备份?

​为什么要使用文件自动备份软件 有一位做客户资料保管登记的朋友&#xff0c;每天会在电脑上录入很多新的客户资料&#xff0c;并需要进行相关维护。比如删掉一些取消合作的客户&#xff0c;或者添加一些备注等等。对于像他这种工作性质的人来说&#xff0c;很需要一个可以…

c++ this指针与空指针调用类方法以及常函数

一、this指针 说明 1、c的成员变量与成员内函数是分开存储 2、每一个非静态成员函数只会诞生一份函数实例&#xff0c;多个同类型的队形公用的是同一份成员函数的代码 3、this指向调用这一份成员函数代码的对象实例 4、this是一个隐藏的指向对象实例的一个指针,无需定义直接使…

使用Vue-cli构建spa项目及结构解析

一&#xff0c;Vue-cli是什么&#xff1f; 是一个官方发布的Vue脚手架工具&#xff0c;用于快速搭建Vue项目结构&#xff0c;提供了现代前端开发所需要的一些基础功能&#xff0c;例如&#xff1a;Webpack打包、ESLint语法检查、单元测试、自动化部署等等。同时&#xff0c;Vu…

百度SEO不稳定的原因及解决方法(百度SEO不稳定因素的5大包括)

百度SEO优化不稳定介绍&#xff1a;蘑菇号-www.mooogu.cn 随着百度SEO算法的不断变化和升级&#xff0c;许多网站的SEO排名经常出现不稳定的情况&#xff0c;这种情况在一定程度上影响了网站的流量和排名&#xff0c;导致网站的质量评分降低。因此&#xff0c;深入分析百度SEO…