JDBC—03—SQL注入问题;PreparedStatement介绍;JDBC的事务处理
一、SQL 注入问题
(1)什么是 SQL 注入
所谓 SQL 注入,就是通过把 `含有 SQL 语句片段的参数` 插入到需要执行的 SQL 语句中,然后statement把SQL语句发送到数据库中, 数据库进行编译, 最终达到欺骗数据库服务器执行恶意操作的 SQL 命令。
(2)如何解决?
因为statement没有SQL语句预编译的能力, 所以会使数据库执行到恶意SQL命令;
我们换一个有预编译能力的statement对象就行了---PreparedStatement;
二、PreparedStatement 对象的使用(重点)
(1)PreparedStatement 特点:
- PreparedStatement 接口继承 Statement 接口
- PreparedStatement 效率高于 Statement
- PreparedStatement 具备动态绑定参数能力
- PreparedStatement 具备 SQL 语句预编译能力, 所以使用 PreparedStatement 可防止出现 SQL 注入问题
(2) 通过 PreparedStatement 对象向表中插入数据:
这个是需要拼接字符串的statement对象,我们可以看到,在拼接departmentName时,非常麻烦。
那我们使用preparedStatement对象,
向 Departments 表中插入一条数据: conn = JdbcUtil.getConnection(); //再也不用拼接字符串或者参数了, 参数用占位符`?`表示, 然后在单独对占位符赋值就好了; ps = conn.prepareStatement("insert into departments values(default,?,?)"); ps.setString(1, departmentName); ps.setInt(2, locationId); ps.execute();
三、 PreparedStatement 的预编译能力
1、什么是预编译
是先将带有占位符(即”?”)的sql模板发送至mysql服务器,由服务器对此无参数的sql进行编译后,将编译结果缓存,然后后面直接执行带有真实参数的sql,即允许数据库做参数化查询。
在使用参数化查询的情况下,数据库不会将参数的内容视为SQL执行的一部分,而是作为一个字段的属性值来处理,这样就算参数中包含破环性语句(or ‘1=1’)也不会被执行。
(1)SQL 语句的执行步骤
- 语法和语义解析
- 优化 sql 语句,制定执行计划
- 执行并返回结果
但是很多情况,我们的一条 sql 语句可能会反复执行,或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。
如果每次都需要经过硬解析----上面的语法语义解析、语句优化、制定执行计划等,那效率就明显不行了。
所谓预编译语句就是将这类语句中的值用占位符替代,可以视为将 sql 语句模板化或者说参数化;
预编译语句的优势在于:
- 不用麻烦的拼接字符串
- 一次编译、多次运行,省去了解析优化等过程(将完全硬解析变成了硬软都有);
- 此外预编译语句能防止 sql 注入;
(2)解析方式
- 硬解析:在不开启缓存执行计划的情况下,每次 SQL 的处理都要经过:语法和语义的解析,优化器处理 SQL,生成执行计划。整个过程我们称之为硬解析。
- 软解析如果开启了缓存执行计划,数据库在处理 sql 时会先查询缓存中是否含有与当前SQL语句相同的执行计划,如果有则直接执行该计划,如果没有先执行硬解析,然后将这次的硬解析加入缓存中。(我们所谓的预编译也就是软解析;)
2、预编译方式
- 开始数据库的日志
- show VARIABLES like '%general_log%'
- set GLOBAL general_log = on
- set GLOBAL log_output='table'
(1)依赖数据库驱动完成预编译
如果我们没有开启数据库服务端编译,那么默认的是使用数据库驱动完成 SQL 的预编
译处理。
(2)依赖数据库服务器完成预编译
我们可以通过修改连接数据库的 URL 信息,添加 useServerPrepStmts=true 信息开启服
务端预编译。
关于预编译,此作者写的挺好https://www.cnblogs.com/qiumingcheng/p/8060471.html
四、通过 PreparedStatement 对象完成数据的操作
1、对数据的更新:
更新数据 conn = JdbcUtil.getConnection(); ps = conn.prepareStatement("update departments set department_name = ?,location_id = ? where department_id = ?"); ps.setString(1, departmentName); ps.setInt(2, localhostId); ps.setInt(3, departmentId); ps.execute();
2、对数据的查询
(1)查询返回单条结果集
conn = JdbcUtil.getConnection(); ps = conn.prepareStatement("select * from departments where department_id = ?"); ps.setInt(1, departmentId); rs = ps.executeQuery(); while(rs.next()){ dept=new Departments(); dept.setDepartmentId(rs.getInt("department_id")); dept.setDepartmentName(rs.getString("department_name")); dept.setLocationId(rs.getInt("location_id")); }
(2)查询返回多条结果集
建立了一个集合而已, 然后不断地输出这个集合的信息就可以了;
上图有个bug, 大家可以试着找找看;😂
既然是模糊查询, 怎么能用`=`, 应该用 `like`;
3. PreparedStatement 批处理操作
批处理:在与数据库的一次连接中,批量的执行条 SQL 语句。
代码:
注意:addBatch()和executeBatch();
五、JDBC 中的事务处理
在 JDBC 操作中数据库事务默认为自动提交。
如果事务需要修改为手动提交,那么我们需要使用 Connection 对象中的 setAutoCommit 方法来关闭事务自动提交, 然后通过Connection 对象中的 commit 方法与 rollback 方法进行事务的提交与回滚。