真正可落地的动态INSERT只有一种安全路径:字段名和占位符动态拼接,所有值必须走PreparedStatement绑定或框架级参数化机制;其他方式均易引发SQL注入、空值错乱、类型失败、关键字冲突等问题。

不能靠字符串拼接值来实现“灵活 INSERT”,否则必然触发 SQL 注入、空值语义错乱、类型转换失败、字段名关键字冲突等问题。真正可落地的动态 INSERT,只有一种安全路径:字段名和占位符动态拼接,所有值必须走 PreparedStatement 绑定或框架级参数化机制。
MyBatis 里用 <if></if> + <trim></trim> 拼字段和占位符
硬写固定字段会失去灵活性,但滥用 <if></if> 又容易导致末尾多逗号或开头缺括号。关键不是“加不加字段”,而是怎么组织语法结构。
-
<trim prefix="INSERT INTO table_name (" suffix=") VALUES (" suffixoverrides=","></trim>包住整个字段列表,suffixOverrides=","必须显式写,否则gender,后面再跟phone就变成gender,phone,,SQL 直接报错 - 每个
<if test="field != null and field != ''"></if>都要同时判null和空字符串,只判null会导致""插入到NOT NULL字段上 - 自增主键如
id,若实体中已有值,必须额外加<if test="id == null"></if>过滤,否则 SQL Server 报Cannot insert explicit value for identity column - 字段名映射需统一处理(如驼峰转下划线),否则
userName对应数据库列user_name时会找不到列
Java 反射生成动态 INSERT 时绕不开的三件事
手写反射拼 SQL 看似自由,实则每一步都埋着坑。重点不在“能不能取到值”,而在“取到后怎么安全放进 SQL”。
- 用
PropertyDescriptor获取getReadMethod()再调用invoke(),比直接Field.get()更稳,避免因访问权限或 NPE 中断流程 - 跳过
null和""字段是基础,但还得识别 JDBC 类型——比如Date要转成java.sql.Timestamp,否则setTimestamp()绑定时抛异常 - 拼
StringBuilder时,别用正则删末尾逗号;用deleteCharAt(sb.length() - 1)更可靠,尤其当字段名含中文或特殊字符时,正则易误删
SQL Server 导出数据脚本必须过滤这三类列
如果目标是生成可执行的 INSERT 脚本(比如环境同步),直接 SELECT + 字符串拼接值看似简单,但数据库层面必须做严格过滤,否则脚本根本跑不通。
- 查
sys.columns时加条件:is_identity = 0 AND is_computed = 0 AND is_rowguidcol = 0,漏掉任意一个都会导致插入失败 - 字段名一律套
QUOTENAME(column_name),否则遇到order、user这种关键字,脚本一执行就报错 - 值部分禁用字符串拼接,日期用
CONVERT(varchar, value, 120),数字用STR(value),避免科学计数法或千分位干扰 - 批量导出超 1000 行时,必须每批包一层
BEGIN TRY ... END TRY BEGIN CATCH ... END CATCH,单行失败不中断全量
最常被忽略的一点:动态 INSERT 的“动态”永远只该发生在字段结构层,而不是值内容层。一旦开始拼 "'" + value + "'",你就已经放弃了数据库驱动的类型校验、转义、NULL 意义区分能力——这不是灵活性,是给自己埋雷。











