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. java.lang.NumberFormatException: Infinite or NaN原因之浮点类型除数为0结果探究

    背景 在对Double类型的数据进行计算操作,将结果转化为BigDecimal时抛出了下面的异常,进行了Debug才发现了问题原因,同时也暴露出了自己在一些基础知识上还有些欠缺. Exception ...

  2. Mac php 装imagick扩展 菜鸟教程

    1.2.直接brew install imagemagick@6完成安装 2.去http://pecl.php.net/package/imagick下载安装包 2.1.下载完后解压tar -xzvf ...

  3. JAVA学习网站分享

    好久没用博客园了 最近工作不忙了,可以花时间自己学习知识提升自己,所以开始查找各种资料 java资源查找网站:           http://www.java1234.com/ 前端学习网站:  ...

  4. Servlet快速入门及运行流程

    一.Servlet快速入门 1.创建一个web工程 2.在JavaResource中src下创建一个包名称为com.myxq.servlet 3.在创建的servlet包当中创建一个class文件起名 ...

  5. The fileSyncDll.ps1 is not digitally signed. You cannot run this script on the current system.

    https://www.opentechguides.com/how-to/article/powershell/105/powershel-security-error.html Unblockin ...

  6. java中JSON转含泛型对象

    public static void main(String[] args) { UserDto userDto=new UserDto("test","14" ...

  7. easyui 如何为datagrid添加自定义列属性(如:width,align,editor)

    我在实际业务需要为datagrid添加一个自定义属性 原先的datagrid列属性包括:title.width.align.formattter.editor等 我们可以通过datagrid的一个方法 ...

  8. 进行编译时提示'error: unrecognized command line option "-std=gnu11"'如何处理?

    答: 说明编译器不支持此选项,那么在Makefile中替换此选项-std=gnu11 替换成-std=gnu99或-std=c99或-std=c11等,主要看编译器都支持哪些编译选项,笔者的支持-st ...

  9. Oracle系列一 SQL语句基本概念和学习准备

    DML: Data Manipulation Language 数据操纵语言DDL:  Data Definition Language 数据定义语言DCL:  Data Control Langua ...

  10. [LeetCode] 387. First Unique Character in a String 字符串的第一个唯一字符

    Given a string, find the first non-repeating character in it and return it's index. If it doesn't ex ...