PreparedStatement和批处理

1、概述

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

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

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

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

设置参数:

1 PreparedStatement pstmt = con.prepareStatement("UPDATE EMPLOYEESSET SALARY = ? WHERE ID = ?");
2 pstmt.setBigDecimal(1, 153833.00);
3 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 根据用户名模糊查询用户信息

 1 public List<Map<String, Object>> getUsersInfoByName(String name) throws SQLException {
 2 
 3     // 拼接sql字符串
 4     String sql = "select id, username, role_id from t_user where username like '%" + name + "%'";
 5 
 6     System.out.println(sql); // 打印一下SQL
 7 
 8     Connection conn = null;
 9     Statement stmt = null;
10     ResultSet rs = null;
11 
12     try {
13         // 获取连接
14         conn = DBUtil.getConnection();
15 
16         stmt = conn.createStatement();
17         // 执行查询并获取结果集
18         rs = stmt.executeQuery(sql);
19 
20         List<Map<String, Object>> users = new ArrayList<Map<String, Object>>();
21 
22         // 遍历结果集,封装数据并返回
23         while (rs.next()) {
24             Map<String, Object> user = new HashMap<String, Object>();
25             user.put("id", rs.getInt(1));
26             user.put("username", rs.getString("username"));
27             user.put("role_id", rs.getInt(3));
28 
29             users.add(user);
30         }
31         return users;
32     } catch (SQLException e) {
33         // 可以把异常抛给业务层的调用者
34         throw e;
35     } finally {
36         // 关闭连接,释放资源
37         //
38     }
39 }

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

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

 1 public Map<String, Object> getUserInfoById(int id) throws SQLException {
 2 
 3     // sql字符串
 4     String sql = "select id, username, role_id from t_user where id = ?";
 5 
 6     Connection conn = null;
 7     PreparedStatement prep = null;
 8     ResultSet rs = null;
 9 
10     try {
11         // 获取连接
12         conn = DBUtil.getConnection();
13 
14         // 获取PreparedStatement
15         prep = conn.prepareStatement(sql);
16         // 设置参数
17         prep.setInt(1, id);
18 
19         // 执行查询并获取结果集
20         rs = prep.executeQuery();
21 
22         Map<String, Object> user = new HashMap<String, Object>();
23 
24         // 遍历结果集,封装数据并返回
25         if (rs.next()) {
26             user.put("id", id);
27             user.put("username", rs.getString("username"));
28             user.put("role_id", rs.getInt(3));
29         }
30         return user;
31     } catch (SQLException e) {
32         // 可以把异常抛给业务层的调用者
33         throw e;
34     } finally {
35         // 关闭连接,释放资源
36         //
37     }
38 }
39 
40 public List<Map<String, Object>> getUsersInfoByName(String name) throws SQLException {
41 
42     // sql字符串
43     String sql = "select id, username, role_id from t_user where username like ?";
44 
45     Connection conn = null;
46     PreparedStatement prep = null;
47     ResultSet rs = null;
48 
49     try {
50         // 获取连接
51         conn = DBUtil.getConnection();
52 
53         // 获取PreparedStatement
54         prep = conn.prepareStatement(sql);
55 
56         // 设置参数
57         prep.setString(1, "%" + name + "%");
58 
59         // 执行查询并获取结果集
60         rs = prep.executeQuery();
61 
62         List<Map<String, Object>> users = new ArrayList<Map<String, Object>>();
63 
64         // 遍历结果集,封装数据并返回
65         while (rs.next()) {
66             Map<String, Object> user = new HashMap<String, Object>();
67             user.put("id", rs.getInt(1));
68             user.put("username", rs.getString("username"));
69             user.put("role_id", rs.getInt(3));
70 
71             users.add(user);
72         }
73         return users;
74     } catch (SQLException e) {
75         // 可以把异常抛给业务层的调用者
76         throw e;
77     } finally {
78         // 关闭连接,释放资源
79         //
80     }
81 }

5、批处理

 1 String sql = "insert into t_user (username) values (?)";
 2 
 3 Connection conn = null;
 4 PreparedStatement prep = null;
 5 ResultSet rs = null;
 6 
 7 try {
 8     // 获取连接
 9     conn = DBUtil.getConnection();
10     // 设置手动提交
11     conn.setAutoCommit(false);
12     // 获取PreparedStatement
13     prep = conn.prepareStatement(sql);
14 
15     for (int i = 1; i <= 1000000; i++) {
16         prep.setString(1, String.format("%s%05d", "admin", i));
17         prep.addBatch();
18     }
19     // 执行批量插入操作
20     prep.executeBatch();
21     // 提交事务
22     conn.commit();
23 
24 } catch (SQLException e) {
25     // 可以把异常抛给业务层的调用者
26     throw e;
27 } finally {
28     // 关闭连接,释放资源
29     //
30 }
原文地址:https://www.cnblogs.com/xugf/p/9240141.html