1、概述

PreparedStatement 接口继承了 Statement,并与之在两方面有所不同,它表示预编译的 SQL 语句对象。

首先,数据库会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句被数据库编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。这并不是说只有一个 Connection 中多次执行的预编译语句被缓存,而是对于整个数据库,只要预编译的语句语法和缓存中匹配,在任何时候都可以不需要再次编译而直接执行。而 statement 的语句即使是相同一操作,而由于每次操作的数据不同所以使整个语句相匹配的机会极小,几乎不太可能匹配。

其次,PreparedStatement 对象中的 SQL 语句可具有一个或多个 IN 参数IN 参数的值在 PreparedStatement 创建时未被指定,而是为每个IN参数保留一个问号(“?”)作为占位符。设置IN参数值的设置方法(setInt、setString等等)必须指定与输入参数的已定义 SQL 类型兼容的类型。如果IN参数具有 SQL 类型 INTEGER,那么应该使用 setInt 方法。

如果需要任意参数类型转换,使用 setObject 方法时应该将目标 SQL 类型作为其参数。

设置参数:

 PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEESSET SALARY = ? WHERE ID = ?");
pstmt.setBigDecimal(1, 153833.00);
pstmt.setInt(2, 110592);

再次,PreparedStatement 可以防止 SQL 注入。由于 SQL 预先在数据库中编译成了类似“函数”的可执行程序,而为占位符赋值相当于为函数传参,这个过程中就避免了字符串拼接的问题,从而可以防止 SQL 注入。

2、核心方法

void addBatch() 将一组SQL添加到此PreparedStatement对象的批处理命令中
boolean execute() 在此PreparedStatement对象中执行SQL语句,该语句可以是任何种类的SQL语句
ResultSet executeQuery() 在此PreparedStatement对象中执行SQL查询,并返回该查询生成的ResultSet对象
int executeUpdate() 在此PreparedStatement对象中执行SQL语句,该语句必须是一个SQL数据操作语言(Data Manipulation Language,DML)语句,比如INSERT、UPDATE或DELETE语句;或者是无返回内容的SQL语句,比如DDL语句
void setDate(int parameterIndex, Date x) 使用默认时区将指定参数设置为给定java.sql.Date值。parameterIndex - 第一个参数是1,第二个参数是2,……
void setDouble(int parameterIndex, double x) 将指定参数设置为给定Java double值
void setInt(int parameterIndex, int x) 将指定参数设置为给定Java int值
void setLong(int parameterIndex, long x) 将指定参数设置为给定Java long值
void setObject(int parameterIndex, Object x) 使用给定对象设置指定参数的值
void setString(int parameterIndex, String x) 将指定参数设置为给定Java String值
void setTimestamp(int parameterIndex, Timestamp x) 将指定参数设置为给定java.sql.Timestamp值

3、SQL 注入

就是攻击者恶意的利用字符串拼接和 SQL 逻辑运算的特点对数据库数据、服务器配置的试探性的攻击。

这种攻击使用网站页面的输入框,或者使用程序发起 http 请求即可实现,这种方式不能被服务器的防火墙拦截,在分析服务器日志的时候才有可能发现,需要进行客户端 IP 限制才有可能在一定程度上防止。

下面看几个简单的 SQL 注入攻击的例子。

首先,为 UserDao 类加一个方法 getUsersInfoByName 根据用户名模糊查询用户信息

 public List<Map<String, Object>> getUsersInfoByName(String name) throws SQLException {

     // 拼接sql字符串
String sql = "select id, username, role_id from t_user where username like '%" + name + "%'"; System.out.println(sql); // 打印一下SQL Connection conn = null;
Statement stmt = null;
ResultSet rs = null; try {
// 获取连接
conn = DBUtil.getConnection(); stmt = conn.createStatement();
// 执行查询并获取结果集
rs = stmt.executeQuery(sql); List<Map<String, Object>> users = new ArrayList<Map<String, Object>>(); // 遍历结果集,封装数据并返回
while (rs.next()) {
Map<String, Object> user = new HashMap<String, Object>();
user.put("id", rs.getInt(1));
user.put("username", rs.getString("username"));
user.put("role_id", rs.getInt(3)); users.add(user);
}
return users;
} catch (SQLException e) {
// 可以把异常抛给业务层的调用者
throw e;
} finally {
// 关闭连接,释放资源
// 略
}
}

在演示之前,我们再分析一下上面这段代码的运行环境。

1)页面上的用户信息展示,可以使用用户名进行搜索

2)使用者输入用户名后进行查询,WEB 服务器找到控制层获取到参数用户名,再调用业务层,业务层再调用上面的这个 DAO 方法进行数据查询

3)查询到的数据层层返回,最后返回给浏览器进行展示

为了方便,我们使用 main 方法模仿业务层调用 DAO 方法

示例1:获取登陆用户

场景就是攻击者输入!@#$%^%' or user() like '%root

拼接成的 SQL 字符串就是这样的:

select id, username, role_id from t_user where username like '%!@#$%^%' or user() like '%root%'

而返回的数据是全部用户信息,这样攻击者就可以确定服务器使用的是 MySQL 数据库,而且是 root 用户连接

示例2:获取库

场景就是攻击者输入!@#$%^%' or database() like '%test

拼接成的 SQL 字符串就是这样的:

select id, username, role_id from t_user where username like '%!@#$%^%' or database() like '%test%'

攻击者就可以确定服务器使用的是 test 库

示例3:获取 MySQL 版本

场景就是攻击者输入!@#$%^%' or version() like '%5.5

拼接成的 SQL 字符串就是这样的:

select id, username, role_id from t_user where username like '%!@#$%^%' or version() like '%5.5%'

攻击者就可以确定数据库版本是5.5

我们的例子比较简单,真实的场景更加复杂、曲折,后果也更加惊心动魄

4、优化的 UserDao

 public Map<String, Object> getUserInfoById(int id) throws SQLException {

     // sql字符串
String sql = "select id, username, role_id from t_user where id = ?"; Connection conn = null;
PreparedStatement prep = null;
ResultSet rs = null; try {
// 获取连接
conn = DBUtil.getConnection(); // 获取PreparedStatement
prep = conn.prepareStatement(sql);
// 设置参数
prep.setInt(1, id); // 执行查询并获取结果集
rs = prep.executeQuery(); Map<String, Object> user = new HashMap<String, Object>(); // 遍历结果集,封装数据并返回
if (rs.next()) {
user.put("id", id);
user.put("username", rs.getString("username"));
user.put("role_id", rs.getInt(3));
}
return user;
} catch (SQLException e) {
// 可以把异常抛给业务层的调用者
throw e;
} finally {
// 关闭连接,释放资源
// 略
}
} public List<Map<String, Object>> getUsersInfoByName(String name) throws SQLException { // sql字符串
String sql = "select id, username, role_id from t_user where username like ?"; Connection conn = null;
PreparedStatement prep = null;
ResultSet rs = null; try {
// 获取连接
conn = DBUtil.getConnection(); // 获取PreparedStatement
prep = conn.prepareStatement(sql); // 设置参数
prep.setString(1, "%" + name + "%"); // 执行查询并获取结果集
rs = prep.executeQuery(); List<Map<String, Object>> users = new ArrayList<Map<String, Object>>(); // 遍历结果集,封装数据并返回
while (rs.next()) {
Map<String, Object> user = new HashMap<String, Object>();
user.put("id", rs.getInt(1));
user.put("username", rs.getString("username"));
user.put("role_id", rs.getInt(3)); users.add(user);
}
return users;
} catch (SQLException e) {
// 可以把异常抛给业务层的调用者
throw e;
} finally {
// 关闭连接,释放资源
// 略
}
}

5、批处理

 String sql = "insert into t_user (username) values (?)";

 Connection conn = null;
PreparedStatement prep = null;
ResultSet rs = null; try {
// 获取连接
conn = DBUtil.getConnection();
// 设置手动提交
conn.setAutoCommit(false);
// 获取PreparedStatement
prep = conn.prepareStatement(sql); for (int i = 1; i <= 1000000; i++) {
prep.setString(1, String.format("%s%05d", "admin", i));
prep.addBatch();
}
// 执行批量插入操作
prep.executeBatch();
// 提交事务
conn.commit(); } catch (SQLException e) {
// 可以把异常抛给业务层的调用者
throw e;
} finally {
// 关闭连接,释放资源
// 略
}

PreparedStatement和批处理的更多相关文章

  1. JDBC的PreparedStatement启动事务使用批处理executeBatch()

    JDBC使用MySQL处理大数据的时候,自然而然的想到要使用批处理, 普通的执行过程是:每处理一条数据,就访问一次数据库: 而批处理是:累积到一定数量,再一次性提交到数据库,减少了与数据库的交互次数, ...

  2. 使用JDBC进行批处理

    在实际的项目开发中,有时候需要向数据库发送一批SQL语句执行,这时应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率. JDBC实现批处理有两种方式:statement和pr ...

  3. PreparedStatement与Statement的区别

    PreparedStatement与statement的区别 1.PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程 2.使用 Statement 对象 ...

  4. preparedStatement和Statement 有什么不一样

    1. PreparedStatement接口继承Statement, PreparedStatement 实例包含已编译的 SQL 语句,所以其执行速度要快于 Statement 对象.    2.作 ...

  5. JavaWeb学习总结(十一)--JDBC之批处理

    一.批处理的介绍 在实际的项目开发中,有时候需要向数据库发送一批SQL语句执行,这时应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率.批处理只针对更新(增.删.改)语句,批 ...

  6. 转自“脚本之家”!!JDBC之PreparedStatement类中预编译的综合应用解析

    JDK 文档:SQL 语句被预编译并存储在 PreparedStatement 对象中(PreparedStatement是存储在JDBC里的,初始化后,缓存到了JDBC里),然后可以使用此对象多次高 ...

  7. PreparedStatement是如何大幅度提高性能的

    本文讲述了如何正确的使用prepared statements.为什么它可以让你的应用程序运行的更快,和同样的让数据库操作变的更快.  为什么Prepared Statements非常重要?如何正确的 ...

  8. javaweb学习总结(三十六)——使用JDBC进行批处理

    在实际的项目开发中,有时候需要向数据库发送一批SQL语句执行,这时应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率. JDBC实现批处理有两种方式:statement和pr ...

  9. Java学习笔记——JDBC之PreparedStatement类中“预编译”的综合应用

    预编译 SQL 语句被预编译并存储在 PreparedStatement 对象中.然后可以使用此对象多次高效地执行该语句. 预编译的优点 1.PreparedStatement是预编译的,对于批量处理 ...

随机推荐

  1. Js 之正则验证手机号、QQ、身份证等

    // 常见的 正则表达式 校验 // QQ号.手机号.Email.是否是数字.去掉前后空格.是否存在中文.邮编.身份证.URL.日期格式.IP let myRegExp = { // 检查字符串是否为 ...

  2. 软件工程 “校园汇” 个人IDEA竞选分析与总结

    IDEA竞选 19/10/8软件工程课上举行了一次IDEA竞选: 我的竞选IDEA是"校友汇",大学生的在线活动中心. 投票结果: 可以看到,校友会(汇)IDEA竞选结果十分惨淡, ...

  3. CTF SSTI(服务器模板注入)

    目录 基础 一些姿势 1.config 2.self 3.[].() 3.url_for, g, request, namespace, lipsum, range, session, dict, g ...

  4. 【2019.09.19】数独(Sudoku)游戏之我见(软工实践第三次作业)

    Github项目地址:https://github.com/MokouTyan/suduku_131700101 [2019.09.20]更新:代码经过Code Quality Analysis工具的 ...

  5. Java-内存模型 synchronized 的内存语义

    synchronized 具有使每个线程依次排队操作共享变量的功能.这种同步机制效率很低,但 synchronized 是其它并发容器实现的基础. 一.锁对象及 synchronized 的使用 sy ...

  6. JAVA从服务器下载文件根据Url把多文件打包成ZIP下载

    注意: 1. String filename = new String(“xx.zip”.getBytes(“UTF-8”), “ISO8859-1”);包装zip文件名不发生乱码.  2.一定要注意 ...

  7. SDM439平台出现部分机型SD卡不能识别mmc1: error -110 whilst initialising SD card【学习笔记】

    SDM439平台出现部分机型SD卡不能识别mmc1: error -110 whilst initialising SD card 打印了如下的log: - ::>[ after ms - :: ...

  8. Python自动化脚本-运维人员宝典

    文章地址: https://alanhou.org/basic-networking-socket-programming/ 第一章 Python脚本概述 第二章 Python脚本调试和性能测试 第三 ...

  9. tf.image.crop_and_resize

    https://blog.csdn.net/m0_38024332/article/details/81779544 将图片剪切下来,池化为固定大小.可以快速的对proposal进行池化

  10. Java12新特性 -- JVM 常量 API

    Java 12 中引入 JVM 常量 API,用来更容易地对关键类文件 (key class-file) 和运行时构件(artefact)的名义描述 (nominal description) 进行建 ...