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. hotspot编译

    "AA=="1",==", /usr/bin/make -s VERBOSE="-s" LOG_LEVEL="warn" ...

  2. Monkey框架(基础知识篇) - monkey启动与参数介绍

    一.monkey启动 直接PC启动:> adb shell monkey [options] <count> shell 端启动:> adb shell >monkey ...

  3. 去掉 vue 的 "You are running Vue in development mode" 提示

    去掉 vue 的 "You are running Vue in development mode" 提示 在项目的 main.js 中已经配置了 Vue.config.produ ...

  4. windows服务器卸载补丁命令【转】

    一.可以试用cmd命令 wusa.exe /uninstall /kb: wusa.exe /uninstall /kb: wusa.exe /uninstall /kb:   二.使用DISM卸载补 ...

  5. [转]Json字符串和map和HashMap之间的转换

    需要导入alibaba.fastJsonmaven中的依赖为 <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> ...

  6. [转]c++多线程编程之pthread线程深入理解

    多线程编程之pthread线程深入理解         Pthread是 POSIX threads 的简称,是POSIX的线程标准.           前几篇博客已经能给你初步的多线程概念.在进一 ...

  7. C# 实现HTTP的POST(完整可运行并且通过测试的代码)

    https://blog.csdn.net/qq_21381465/article/details/80016159 我是通过VS2010 ,新建一个winform窗体项目,然后写了一个测试软件,软件 ...

  8. k8s添加凭证

    请参照:https://www.cnblogs.com/effortsing/p/10013441.html

  9. vue -- router路由跳转错误 , NavigationDuplicated

    vue-router ≥3.0版本回调形式以及改成promise api的形式了,返回的是一个promise,如果路由地址跳转相同, 且没有捕获到错误,控制台始终会出现如图所示的警告 (注:3.0以下 ...

  10. 湖南省第6届程序大赛 Repairing a Road

    Problem G Repairing a Road You live in a small town with R bidirectional roads connecting C crossing ...