这么写SQL语句,老板让我明天不用来了!
个人主页:一条泥憨鱼(欢迎各位大佬莅临)精选专栏:数据结构与算法Java ,苍穹外卖日记AI学习JavaWeb前言:设想一个很有意思的场景面试官问你知道公司为什么禁止拼接SQL吗很多人脱口而出因为会SQL注入面试官接着问那你见过真实的SQL注入事故吗你知道一次SQL注入可能给公司造成多大损失吗现场瞬间安静了。事实上在很多刚毕业的程序员眼里SQL注入似乎只是书上的知识点。但在真实项目中它可能意味着用户数据泄露订单数据丢失数据库被删公司被攻击老板半夜被电话叫醒严重一点第二天你可能就要更新简历了而这一切往往只是因为写了这样一行代码String sql select * from user where username username and password password ;今天我们就来聊聊为什么这种SQL千万别写什么是SQL注入为什么MyBatis默认不会出现这个问题预编译SQL到底厉害在哪里看完这篇文章你不仅能搞懂预编译SQL还能彻底理解为什么企业开发中几乎没人再手写字符串拼接SQL。一、这个SQL看起来有什么问题很多新手第一次写登录功能时,都是这么干的public boolean login( String username, String password) throws Exception { String sql select * from user where username username and password password ; Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql); return rs.next(); }看起来逻辑非常合理。用户输入admin 123456生成select * from user where usernameadmin and password123456查询成功--项目上线--老板满意,你也满意。然后第二天出事了。二、一个神秘用户登录了所有账号假设有个略懂点数据库的人。输入用户名admin密码 or 11最终SQL变成select * from user where usernameadmin and password or 11很多同学第一次看到这里还没意识到问题。我们拆开看11结果是什么永远成立。也就是true于是SQL变成条件A or true结果自然是true数据库直接返回数据攻击者成功登录。甚至连密码都不需要知道。三、这就是臭名昭著的SQL注入这个漏洞有一个专门的名字SQL Injection SQL注入本质上用户把原本应该输入的数据伪装成SQL代码让数据库执行了不该执行的语句。数据库根本不知道哪些是程序写的SQL 哪些是用户输入的数据因为在它看来String sql select * from user where id userInput;最终就是一段完整SQL。如果用户输入1 or 11数据库看到select * from user where id1 or 11然后老老实实执行。四、为什么大家害怕这种问题因为很多人觉得登录绕过而已没那么严重。实际上攻击者还能干很多事情。例如查询所有用户1 union select * from user删除数据1; delete from user甚至删除整个数据库drop database company当然现代数据库会有权限限制但核心问题仍然存在用户修改了SQL结构这是最危险的地方。五、解决方案预编译SQL企业里解决这个问题的方法只有一个预编译SQL也叫PreparedStatement六、什么叫预编译传统拼接SQLString sql select * from user where id id;数据库拿到的是select * from user where id1或者select * from user where id1 or 11数据库根本分不清。而预编译SQL写法String sql select * from user where id?;这里 叫占位符。数据库首先看到select * from user where id?注意此时SQL结构已经固定数据库先完成解析 检查语法 生成执行计划然后等待参数。七、第一个PreparedStatement实例普通写法Statement stmt conn.createStatement(); String sql select * from user where id id; stmt.executeQuery(sql);预编译写法String sql select * from user where id?; PreparedStatement ps conn.prepareStatement(sql); ps.setInt(1,1); ResultSet rs ps.executeQuery();是不是非常简单八、参数绑定到底发生了什么SQL模板select * from user where username? and password?绑定参数ps.setString(1,admin); ps.setString(2,123456);数据库执行时select * from user where usernameadmin and password123456如果攻击者输入 or 11数据库会把它当成一个普通字符而不是SQL代码。最终变成password or 11数据库只会去匹配这个字符串。不会修改SQL结构。这就是预编译SQL真正厉害的地方。九、企业级登录案例public boolean login( String username, String password) throws Exception { String sql select * from user where username? and password?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1,username); ps.setString(2,password); ResultSet rs ps.executeQuery(); return rs.next(); }十、预编译SQL还有一个隐藏技能很多人以为它只是安全其实它还有一个大优势性能优化例如for(int i1;i10000;i){ select * from user where idi }如果使用Statement解析SQL 编译SQL 执行SQL重复10000次。而PreparedStatement编译一次 执行10000次执行计划直接复用数据库压力明显下降。十一、为什么MyBatis默认防SQL注入很多同学学MyBatis时经常写select idgetUser select * from user where id #{id} /select这里的#{id}实际上底层就是最终调用PreparedStatement。所以#{} 默认安全十二、${}为什么容易出问题例如select * from user where id${id}用户输入1 or 11最终select * from user where id1 or 11又回到了字符串拼接时代因此规范通常要求能用 #{}绝不用 ${}面试官最爱问的几个问题什么是预编译SQLSQL结构先固定。参数后传递。用户无法修改SQL结构。PreparedStatement为什么能防SQL注入因为SQL结构和用户数据彻底分离PreparedStatement为什么性能更高执行计划复用减少重复编译。#{}和${}区别#{} → 预编译 ${} → 字符串拼接总结SQL注入并不可怕可怕的是你明明知道有风险还在项目里拼接SQL。在开发中❌ 不要这样写String sql select * from user where id id;✅ 要这样写PreparedStatement或者#{}因为预编译SQL不仅能防SQL注入还能提升性能这也是为什么几乎所有现代ORM框架底层都在使用预编译SQL。下次当你写SQL的时候记住一句话你拼接的可能不是SQL而是老板明天找你谈话的理由。