Mybatis-Plus 自动属性填充与自定义Insert into语句顺序

news2024/12/28 21:06:19

前言:系统中使用了Mybatis-Plus 自动属性填充为实体统一进行属性的填值,在Mapper的xml 文件中 insert into 语句 使用
<if test="id != null">id,</if> 进行判断会发现该属性是空的,明明已经为改字段进行了属性的自动填充,为什么Mybatis- 在拼接sql 语句时依然认为 改属性是空的呢;

1 问题重现:

1.1 在实体中使用了属性填充属性:

 @TableField(fill = FieldFill.INSERT)
 private String testFiled;

1.2 在拦截器里进行了属性填充:

@Override
public void insertFill(MetaObject metaObject) {
	this.setFieldValByName("testFiled", "test", metaObject);
}

1.3 mapper xml :

 insert into ${prefix}knowledge_authority
 <trim prefix="(" suffix=")" suffixOverrides=",">
   <if test="id != null">id,</if>
   <if test="testFiled != null">test_filed,</if>
  </trim>
 <trim prefix="values (" suffix=")" suffixOverrides=",">
   <if test="id != null">#{id},</if>
    <if test="testFiled != null">#{testFiled },</if>
  </trim>

在对实体id 设置完成之后,进行数据的插入,发现插入的数据中只有id 没有testFiled 属性;

2 推断问题产生的原因:

原因1:属性填充正常,但是在xml sql 语句中,在某些情况下 <if> 判断有问题;

原因2:自定义填充属性有问题,导致想要填充的属性没有被填充值,导致进行 <if> 判断有问题;

愿意3:属性填充和 <if> 标签判断都没有问题,但是sql 拼接的时机 在属性填充之前进行;

<if> 标签只进行简单的空判断,出问题的可能性不大,从原因2 入手:
使用自定义属性填充时,会调用MybatisParameterHandler 类中的process()方法完成属性填充的调用;

 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) {
           		// 插入时 id 的填充
               this.populateKeys(tableInfo, metaObject, entity);
               // 这里会在insert 时 调用我们自己定义的拦截器进行属性的自动填充
               this.insertFill(metaObject, tableInfo);
           } else {
            // 这里会在update 时 调用我们自己定义的拦截器进行属性的自动填充
               this.updateFill(metaObject, tableInfo);
           }
       }
   }

}

通过debug 我们发现,在插入数据时确实调用了process 方法,并对实体完成了属性的填充,属性填充是正常的;所以会不会是原因3 ,属性填充的时机和sql 拼接的时机不同造成的。
如果 先进行了sql的拼接,此时进行 <if> 判断时 发现改属性为空,必然会跳过了该属性的拼接,即使后面自动填充为属性填充了数据,但是由于sql已经完成了拼接,最终执行的sql 也是没有该属性的;

基于此猜想,我们将xml 中 判断标签去掉,只保留占位符:

 insert into ${prefix}knowledge_authority
 <trim prefix="(" suffix=")" suffixOverrides=",">
   <if test="id != null">id,</if>
  	test_filed,
  </trim>
 <trim prefix="values (" suffix=")" suffixOverrides=",">
   <if test="id != null">#{id},</if>
     #{testFiled },
  </trim>

此时在次进行插入,发现插入成功,并且testFiled 属性也是有值的;

3 从Mybatis-Plus 代码层面查看sql 语句的拼接:

3.1 先看下sql 拼接的流程:
MybatisParameterHandler 是 Mybatis 中用于处理数据库操作参数的接口,它的实现类 DefaultParameterHandler 负责将 Java 对象转换为 JDBC 预处理语句需要的参数值,以及将参数值设置到预处理语句中。其中,BoundSql 对象就是用于封装 SQL 语句和对应的参数值的。

BoundSql 对象的赋值过程主要由 SqlSource 和 ParameterMapping 来完成,具体流程如下:

  1. Mybatis 在执行 SQL 语句之前,会根据 Mapper 接口定义的方法和传入的参数生成 MappedStatement 对象。在 MappedStatement 中包含了 SQL 语句、参数映射信息等相关的元数据。
  2. MappedStatement 负责生成 BoundSql 对象。在生成 BoundSql 对象时,Mybatis 会首先根据 SQL 语句和参数信息生成一个 StaticSqlSource 对象,然后再通过它生成一个 DynamicSqlSource 对象。DynamicSqlSource 会根据传入的参数信息和 Mapper 接口定义的 SQL 语句,动态生成最终的 SQL 语句和参数值。这个过程中,ParameterMapping 负责将 Java 对象中的属性值和 SQL 语句中的占位符做映射关联,SqlSource 负责根据参数信息和 SQL 语句生成 BoundSql 对象。
  3. 生成 BoundSql 对象后,Mybatis 会通过 ParameterHandler 将 BoundSql 对象中的 SQL 语句和参数值设置到 JDBC 预处理语句中。默认的 ParameterHandler 实现类是 DefaultParameterHandler,它会通过反射获取 PreparedStatement 对象,并调用 setXxx() 方法将参数值设置到预处理语句中。在设置参数值的过程中,DefaultParameterHandler 会根据 ParameterMapping 中的信息获得 Java 对象中对应属性的值,并将其赋值给 BoundSql 对象中对应的参数占位符。
  4. 综上所述,BoundSql 对象的赋值过程主要由 SqlSource 和 ParameterMapping 来完成。它们会根据传入的参数信息和 Mapper 接口定义的 SQL 语句,动态生成最终的 SQL 语句和参数值,并将它们设置到 BoundSql 对象中。

3.2 MybatisParameterHandler 中的 BoundSql boundSql:

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;
        // 拼接好的sql
        this.boundSql = boundSql;
        this.configuration = mappedStatement.getConfiguration();
        this.sqlCommandType = mappedStatement.getSqlCommandType();
        // 主键id 和 属性的自动填充
        this.parameterObject = this.processParameter(parameter);
    }
}

可以看到在创建MybatisParameterHandler 对象时,boundSql 已经完成了sql 的解析和拼接,然后在this.processParameter(parameter) 方法完成了主键id 和 属性的自动填充,从构造方法可以看到,boundSql 的拼接是先于processParameter(parameter) 属性填充的方法的,这就解释了为什么我们明明已经为改属性进行了填充,为什么 最终自定义的insert into 语句 标签判断是空的,本质就是因为两者的顺序问题;

3.3 sql 语句的拼接:
进入DynamicSqlSource 类getBoundSql 方法:

public class DynamicSqlSource implements SqlSource {
    private final Configuration configuration;
    private final SqlNode rootSqlNode;

    public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
        this.configuration = configuration;
        this.rootSqlNode = rootSqlNode;
    }

    public BoundSql getBoundSql(Object parameterObject) {
    	// 参数解析
        DynamicContext context = new DynamicContext(this.configuration, parameterObject);
       	// sql 拼接
        this.rootSqlNode.apply(context);
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        // 占位符拼接
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        // sql 拼接
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        context.getBindings().forEach(boundSql::setAdditionalParameter);
        return boundSql;
    }
}

this.rootSqlNode.apply(context):

public class MixedSqlNode implements SqlNode {
    private final List<SqlNode> contents;

    public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
    }

    public boolean apply(DynamicContext context) {
    	// 这里会判断xml 中的所有属性标签,只有判断为true ,才进行属性的拼接
        this.contents.forEach((node) -> {
            node.apply(context);
        });
        return true;
    }
}

可以看到这里会根据标签不同调用不同的实现完成判断:
在这里插入图片描述
并且会逐个进行属性的判断,只有为true 才进行属性拼接:
在这里插入图片描述
以IfSqlNode 为例,可以看出只有当属性不为空时,才返回true 否则返回false,只有在返回true 时后续才会对改属性进行拼接
在这里插入图片描述

4 总结:

Mybatis-Plus 自定义的sql 语句其BoundSql的解析和拼接是在属性填充之前进行的,所以如果在自定义sql 语句中使用了<if>标签进行属性的非空判断,就不会拼接改属性,此时需要在自定义的sql 中去除<<if>的非空判断直接使用#{testFiled },这样最终在进数据插入时,Mybatis会动态的替换掉改占位符。

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

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

相关文章

百度2023年Q1财报解析:AI+生态战略加速助推

原创 | 文 BFT机器人 01 百度靠AI实现翻身 &#xff08;一&#xff09;盈利能力 百度凭借着强大的AI能力&#xff0c;成功地实现了从依赖搜索业务的互联网公司到AI公司的转型。 从盈利能力层面上看&#xff0c;在第一季度&#xff0c;百度实现了营收311.44亿元&#xff0c;同比…

unity愤怒的小鸟学习制作(二)

终于又开始了啦啦啦&#xff0c;我有一个自己的相机了&#xff0c;真开心&#xff0c;诶嘿 视频链接和素材如下&#xff1a;视频 小鸟的飞出 想要让小鸟在拉开弹弓之后能飞出去&#xff0c;就必须让这个组件失活&#xff0c;如下 所以我们更改脚本内容&#xff0c;加入&#…

HarmonyOS应用端云一体化开发主要流程

图示 主要步骤 序号 阶段 任务 说明 1 创建端云一体化开发工程 选择工程类型与云开发模板 确定工程类型&#xff1a;选择“Application”或“Atomic Service”页签&#xff0c;确定创建的是HarmonyOS应用工程还是原子化服务工程。选择云开发模板&#xff0c;包括通用云开…

【FMC202】基于FMC标准的1路CameraLink Full 输入、1路DVI输出 子卡模块

产品概述 FMC202是一款基于FMC接口标准的1路CameraLink Full模式&#xff08;或者2路CameraLink Base模式&#xff09;采集、1路HDMI&#xff08;DVI&#xff09;视频输出的子卡模块&#xff0c;该模块具有2个CameraLink端口&#xff08;SDR&#xff0c;26PIN&#xff09;&…

Docker部署apache superset

使用Docker compose在docker中部署Apache Superset 说明&#xff1a;部署步骤按照superset官网说明总结而来-2023年 1、第一步安装docker 、docker compose。 这里我选择手动下载rpm包&#xff0c;然后yum install *.rpm方式来安装。 下载地址&#xff1a;https://download.…

MADDPG-学习笔记(2)

注意&#xff1a;进行本文的实验前&#xff0c;为了加快训练速度&#xff0c;进行了参数调整 num-episodes&#xff1a;由60000改成了10000 lr:由0.01改成了0.1 batch-size:由1024改成了32 1.报错 1.1 AttributeError: Scenario object has no attribute benchmark_data …

ApiKit 介绍及基本用法

1、ApiKit介绍及下载 ApiKitAPI 管理 Mock 自动化测试 异常监控 团队协作 结合 API 设计、文档管理、自动化测试、监控、研发管理和团队协作的一站式 API 生产平台&#xff0c;从个人开发者到跨国企业用户&#xff0c;Apikit 帮助全球超过50万开发者和10万家企业更快、更好…

【Flutter】Flutter CLI (2):调试分析项目 flutter analyze 命令详解

文章目录 一、前言二、对现有项目进行分析和调试1. flutter analyze2. 修改代码暴露错误3. 再次执行flutter analyze4. 调试模式运行代码 flutter run三、本文涉及命令的完整说明1.flutter analyze四、总结一、前言 在上一篇文章中,我们对 Flutter CLI 的命令进行了分类,并通…

Array的扩展方法(from、find、findlndex、includes)

Array.from() 构造函数方法:Array.from() 将类数组或可遍历对象转换为真正的数组 示例 <script>// 构造函数方法:Array.from()// 将类数组或可遍历对象转换为真正的数组let arr {0: a,1: b,2: c,length: 3,};let arr1 Array.from(arr);console.log(arr1);//["…

对回溯的理解与思考(从决策树遍历角度分析)

对于回溯的经典问题&#xff0c;就是全排列和各种各样全排列的变体和八皇后问题。 算法框架 对于回溯算法框架。其实解决一个回溯问题&#xff0c;实际上就是一个决策树的遍历过程。 这也就是为什么在刷算法题之前&#xff0c;一定要从树的题目开始刷&#xff0c;后期可以很方…

检索 COM 类工厂中 CLSID 为 {} 的组件失败, 内存资源不足,无法处理此命令

如果您收到ERROR_NOT_ENOUGH_MEMORY消息&#xff0c;提示没有足够的存储空间来处理此命令描述&#xff0c;请按照本文中列出的故障排除步骤进行修复。 此错误代码影响Windows服务器&#xff0c;导致系统崩溃&#xff0c;并在错误日志中显示“没有足够的存储空间来处理此命令”。…

Qt+QtWebApp开发笔记(二):http服务器日志系统介绍、添加日志系统至Demo测试

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/130762721 红胖子网络科技博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

Clickhouse 入门到精通-Clickhouse工作原理

Clickhouse 为什么做查询分析那么快&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f; 因为clickhouse使用了下列方案&#xff1a; clickhouse 数据分区clickhouse 列式存储clickhouse 一级索引&#xff08;主键索引&#…

企业数字转型加速器!居然是他!该不会还有人没用上吧?

随着数字化时代的到来和技术的发展&#xff0c;企业数字化转型已经成为全球企业发展的重要趋势。然而&#xff0c;数字化转型的过程却并非一帆风顺&#xff0c;常常因为 IT 复杂度高、开发周期长等问题而遇到许多挑战&#xff0c;这时候低代码开发平台就能够发挥重要作用。 低代…

我们为什么还要学习Altium Designer?

Altium Designe&#xff08;简称“AD”&#xff09;是电子设计领域中备受推崇的软件工具之一&#xff0c;拥有强大的功能和灵活的设计环境&#xff0c;也是要用最广泛的EDA工具之一&#xff0c;为电子工程师提供了无限可能&#xff0c;但很多工程师学完AD基本操作就转投其他EDA…

支付宝小程序打包成APP

发行——原生App-云打包——填写安卓包的信息&#xff08;安卓证书可在香蕉云编下载&#xff09;——打包——下载APK 第一步&#xff1a;点击菜单栏发行 第二步&#xff1a;选择远程APP-云打包 第三步&#xff1a;在香蕉云编&#xff08;https://www.yunedit.com/&#xff0…

K8S之yaml文件,声明式管理方法

目录 第一章.声明式管理方法 1.1.声明式管理方法 1.2.kubectl create 和 kubectl apply区别 1.3.查看资源配置清单 1.4.解释资源配置清单 1.5.修改资源配置清单并应用 第二章.yaml文件格式 2.1.yaml文件简述 2.2.YAML 语法格式 2.3.查看 api 资源版本标签 2.4.写一个…

MVC中Controller向View传值的几种方式

MVC中Controller向View传值的几种方式 文章目录 MVC中Controller向View传值的几种方式一、ViewModel使用ViewModel 二、ViewData在控制器和视图间使用ViewData传递数据在 ViewDataTest 视图中使用ViewData的数据在视图和部分视图间使用ViewData 三、ViewBag四、TempData五、Ses…

搭建短链服务

目录 一、背景 1.1短链接的优势 1.1.1优点一 1.1.2优点二 1.1.3优点三 1.1.4优点四 1.1.4优点五 二、原理 2.1利用http重定向 3.1实现方案 3.1.1发号器实现 3.1.2存储实现 3.1.3映射实现 3.2架构图 一、背景 短链在互联网中盛行&#xff0c;搭建自己短链平台&…

硬件工程师-MOS管

MOSFET 场效应管 N管 P管 对标三极管 N管 P管 三极管具有功率放大的作用 MOSFET也具有功率作用&#xff0c; 控制级的电流很小 控制信号的内阻大 输出级的电流很大 输出信号的内阻很小 三极管的缺点&#xff1a;流控…