根本问题是拼接后SQL字符串被Statement当作完整语句执行,数据库不区分逻辑与数据;PreparedStatement通过预编译分离结构与参数,使用户输入仅作为值处理,杜绝注入。

拼接SQL字符串会让数据库把用户输入当代码执行
根本问题不是“拼接”本身,而是拼接后整个字符串被Statement当作一条完整 SQL 语句交给数据库解析执行。数据库引擎不区分哪部分是逻辑、哪部分是数据——只要语法合法,就照单全收。
比如用户在登录框输入密码:' OR '1'='1,拼出的语句变成:
SELECT * FROM users WHERE username = 'admin' AND password = '' OR '1'='1'
这个OR '1'='1'直接让条件恒真,绕过认证。更糟的是,攻击者还能塞进;分隔符、--注释、甚至DROP TABLE,只要最终语句语法正确,数据库就执行。
PreparedStatement 并不是“转义字符”,而是分离数据与结构
很多人误以为PreparedStatement只是把单引号替换成两个单引号之类。其实它底层走的是预编译协议:先发SELECT * FROM users WHERE username = ? AND password = ?给数据库编译成执行计划,再把参数以二进制/独立协议字段方式传过去。数据库天然知道?位置填的只是值,绝不会参与 SQL 解析。
立即学习“Java免费学习笔记(深入)”;
-
Statement:一次发送完整字符串 → 数据库边解析边执行 -
PreparedStatement:先发模板(无变量),再发参数(纯数据)→ 数据库只执行已编译好的计划
所以即使你传入admin'; DROP TABLE users; --,它也只会被当成一个字符串值去匹配username字段,不会触发额外语句。
字符串拼接在 IN 查询中会彻底失效
想用拼接实现WHERE id IN (1,2,3)?这是典型误区。一旦你写成:
String ids = "1,2,3";<br>String sql = "SELECT * FROM users WHERE id IN (" + ids + ")";数据库收到的就是IN (1,2,3)——看似正常。但若ids来自用户输入(比如 URL 参数 ?ids=1,2,3),攻击者立刻可注入1,2,3); DROP TABLE users; --,拼出非法语句。
更隐蔽的问题是类型错配:如果id是整型字段,而你拼进去的是带引号的字符串'1','2','3',MySQL 可能隐式转换,PostgreSQL 直接报错ERROR: operator does not exist: integer = text。
真正安全的做法是动态生成占位符:IN (?,?,?),再用setInt()逐个绑定——这意味着你必须提前知道参数个数,不能靠字符串拼接“省事”。
别信“过滤单引号就能防住”的说法
手动替换'为''或删掉--、;,属于典型防御错位。现实中的绕过手段太多:
- 用
/**/代替空格(MySQL 支持) - 用
UNION SELECT配合information_schema查表结构 - 用十六进制编码绕过正则检测:
0x73656c656374→select - 大小写混用:
SeLeCt逃过简单关键字黑名单
这些都说明:只要还在用Statement + 字符串拼接,你就始终在和攻击者玩猫鼠游戏。而PreparedStatement从协议层切断了这条攻击链,是唯一被 JDBC 规范保证有效的防护手段。


















