PreparedStatement和批处理
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和批处理的更多相关文章
- JDBC的PreparedStatement启动事务使用批处理executeBatch()
JDBC使用MySQL处理大数据的时候,自然而然的想到要使用批处理, 普通的执行过程是:每处理一条数据,就访问一次数据库: 而批处理是:累积到一定数量,再一次性提交到数据库,减少了与数据库的交互次数, ...
- 使用JDBC进行批处理
在实际的项目开发中,有时候需要向数据库发送一批SQL语句执行,这时应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率. JDBC实现批处理有两种方式:statement和pr ...
- PreparedStatement与Statement的区别
PreparedStatement与statement的区别 1.PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程 2.使用 Statement 对象 ...
- preparedStatement和Statement 有什么不一样
1. PreparedStatement接口继承Statement, PreparedStatement 实例包含已编译的 SQL 语句,所以其执行速度要快于 Statement 对象. 2.作 ...
- JavaWeb学习总结(十一)--JDBC之批处理
一.批处理的介绍 在实际的项目开发中,有时候需要向数据库发送一批SQL语句执行,这时应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率.批处理只针对更新(增.删.改)语句,批 ...
- 转自“脚本之家”!!JDBC之PreparedStatement类中预编译的综合应用解析
JDK 文档:SQL 语句被预编译并存储在 PreparedStatement 对象中(PreparedStatement是存储在JDBC里的,初始化后,缓存到了JDBC里),然后可以使用此对象多次高 ...
- PreparedStatement是如何大幅度提高性能的
本文讲述了如何正确的使用prepared statements.为什么它可以让你的应用程序运行的更快,和同样的让数据库操作变的更快. 为什么Prepared Statements非常重要?如何正确的 ...
- javaweb学习总结(三十六)——使用JDBC进行批处理
在实际的项目开发中,有时候需要向数据库发送一批SQL语句执行,这时应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率. JDBC实现批处理有两种方式:statement和pr ...
- Java学习笔记——JDBC之PreparedStatement类中“预编译”的综合应用
预编译 SQL 语句被预编译并存储在 PreparedStatement 对象中.然后可以使用此对象多次高效地执行该语句. 预编译的优点 1.PreparedStatement是预编译的,对于批量处理 ...
随机推荐
- PL/SQL Developer插入数据到数据库出现数据中文乱码
问题描述: 使用PL/SQL Developer往Oracle数据库插入数据,出现中文乱码! 解决办法: 1.执行脚本 select userenv('language') from dual; 结果 ...
- Java Spring 使用 Redis
在 Java 中使用 Redis 需要使用 Jedis.jar github 页面 https://github.com/xetorthio/jedis javadocs http://xetorth ...
- Python3爬取王者官方网站英雄数据
爬取王者官方网站英雄数据 众所周知,王者荣耀已经成为众多人们喜爱的一款休闲娱乐手游,今天就利用python3 爬虫技术爬取官方网站上的几十个英雄的资料,包括官方给出的人物定位,英雄名称,技能名称,CD ...
- [Beta]第二次 Scrum Meeting
[Beta]第二次 Scrum Meeting 写在前面 会议时间 会议时长 会议地点 2019/5/6 22:00 30min 大运村公寓6F楼道 附Github仓库:WEDO 例会照片 工作情况总 ...
- 第07组 Beta冲刺(2/5)
队名:摇光 队长:杨明哲 组长博客:求戳 作业博客:求再戳 队长:杨明哲 过去两天完成了哪些任务 文字/口头描述:代码编辑器 展示GitHub当日代码/文档签入记录:(组内共用,已询问过助教小姐姐) ...
- QTP测试学习笔记
QuickTest Professional(简称QTP)功能自动化测试,原属于Mercury Interactive公司产品,2006年7月被惠普公司收购了,通过安装文件目录可以看到,都是默认放在C ...
- SDN实验---Ryu的应用开发(一)Hub实现
补充: (一)Ubuntu下安装Notepadqq 背景:为什么安装Notepadqq Notepad++ 不仅有语法高亮度显示,也有语法折叠功能,并且支持宏以及扩充基本功能的外挂模组.但是可惜的是N ...
- svg复用方式<g>, <defs>, <symbol>, <use>
svg复用元素的方式主要有 <g>, <defs>, <symbol>, <use> 1. <g> group, 分组,定义一组元素,初始不 ...
- Composer 国内加速:可用镜像列表大全
查看地址1:https://learnku.com/composer/wikis/30594 查看地址2:https://learnku.com/articles/30258
- oracle 应用程序调用存储函数
package com.founder.ec.common.lucene; import java.sql.CallableStatement; import java.sql.Connection; ...