JDBC 学习笔记(六)—— PreparedStatement

时间:2022-12-17 23:41:36

1. 引入 PreparedStatement

PreparedStatement 通过 Connection.createPreparedStatement(String sql) 方法创建,主要用来反复执行一条结构相似的 SQL 语句。

例如:

INSERT INTO STUDENT (STUDENT_NAME, STUDENT_PASSWORD) VALUES ('van Nistelrooy', '666666');

INSERT INTO STUDENT (STUDENT_NAME, STUDENT_PASSWORD) VALUES ('van der Sar', '777777');

这两条 SQL 语句,除了插入的值不同,其他的基本语法没有任何区别。

针对这种情况,可以使用占位符 ?来代替:

INSERT INTO STUDENT (STUDENT_NAME, STUDENT_PASSWORD) VALUES (?, ?);

Statement 是不允许使用 ? 占位符的,而且这个占位符需要获得值之后才能够执行。PreparedStatement 就是针对这种场景才引入进来的。

PreparedStatement 是 Statement 的子接口,它支持以下4种执行 SQL 的方式:

  • execute()
  • executeUpdate()
  • executeQuery()
  • executeLargeUpdate()

同时,PreparedStatement 提供了一系列的 setXxx(int index, Xxx value) 来支持对于 ? 占位符的替换。Xxx 是占位符的数据类型。

如果不确定数据类型,可以通过 setObject() 方法来传入数据。

2. Demo

这里贴一个自己写的例子:

package com.gerrard.demo;

import com.gerrard.entity.Student;
import com.gerrard.util.Connector;
import com.gerrard.util.DriverLoader; import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; public final class PreparedStatementDemo { public static void main(String[] args) {
String sql = "INSERT INTO STUDENT (STUDENT_NAME, STUDENT_PASSWORD) VALUES (?, ?)"; Student student1 = new Student(0, "van Nistelrooy", "666666");
Student student2 = new Student(0, "van der Sar", "777777");
List<Student> studentList = new ArrayList<>(Arrays.asList(student1, student2)); DriverLoader.loadSqliteDriver();
try (Connection conn = Connector.getSqlConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) { for (Student student : studentList) {
pstmt.setString(1, student.getName());
pstmt.setObject(2, student.getPassword());
pstmt.executeUpdate();
}
System.out.println("Successfully executeUpdate using PreparedStatement."); } catch (SQLException e) {
e.printStackTrace();
}
}
}

3. PreparedStatement 的优势

PreparedStatement 的优势主要有以下3点:

  • 通过预编译 SQL 语句的方式(即创建 PreparedStatement 的时候,就将 SQL语句传递进去),大大降低了多次执行相似的 SQL 语句的效率。
  • 需要传递参数的时候,无需拼接 SQL 语句,降低了编程的复杂度。
  • 防止 SQL 注入。

4. SQL 注入

SQL 注入是一个常见的 Cracker 入侵方式,利用 SQL 语句的漏洞来入侵。

那么怎么去理解 PrepareStatement 能够防止 SQL 注入呢?

实际上,这是不使用 SQL 字符串拼接的副产品。

现在假设,有一个 GUI 界面,需要输入用户名和密码来登录。

在后台,我提供了一个登陆服务:

public interface StudentLoginService {

    Student login(String id, String password);
}

那么现在,我用 Statement 去实现这个服务:(有些自定义的类,详情见 JDBC 学习笔记(十)—— 使用 JDBC 搭建一个简易的 ORM 框架

package com.gerrard.service;

import com.gerrard.entity.Student;
import com.gerrard.executor.SqlExecutorStatement;
import com.gerrard.orm.FlexibleResultSetAdapter;
import com.gerrard.orm.ResultSetAdapter; import java.util.List; public final class StatementLoginService implements StudentLoginService { private static final String VALIDATE_SQL = "select * from STUDENT s where s.[STUDENT_ID] = ? and s.[STUDENT_PASSWORD] = ?"; @Override
public Student login(String id, String password) {
ResultSetAdapter<Student> adapter = new FlexibleResultSetAdapter<>(Student.class);
SqlExecutorStatement<Student> executor = new SqlExecutorStatement<>(adapter);
String sql = VALIDATE_SQL.replaceFirst("\\?", id).replaceFirst("\\?", "'" + password + "'");
List<Student> list = executor.executeQuery(sql);
return list.isEmpty() ? null : list.get(0);
}
}

这样写代码,看似没有问题,但是实际上有个致命的缺陷。

假设有人猜测到了这个登陆服务是用 Statement 实现的,那么他在密码一栏可以输入:

' or 1=1 or '

这样,整句 SQL 就变成了:

select * from STUDENT s where s.[STUDENT_ID] = 1 and s.[STUDENT_PASSWORD] = '' or 1=1 or ''

显然,所有的 Student 都被查到了,登陆成功。

但是,使用 PreparedStatement 就没有这种问题,Cracker 输入会被拒绝登陆:

package com.gerrard.service;

import com.gerrard.entity.Student;
import com.gerrard.executor.SqlExecutorPreparedStatement;
import com.gerrard.orm.FlexibleResultSetAdapter;
import com.gerrard.orm.ResultSetAdapter; import java.util.Arrays;
import java.util.LinkedList;
import java.util.List; public final class PreparedStatementLoginService implements StudentLoginService { private static final String VALIDATE_SQL = "select * from STUDENT s where s.[STUDENT_ID] = ? and s.[STUDENT_PASSWORD] = ?"; @Override
public Student login(String id, String password) {
List<Object> params = new LinkedList<>(Arrays.asList(id, password));
ResultSetAdapter<Student> adapter = new FlexibleResultSetAdapter<>(Student.class);
SqlExecutorPreparedStatement<Student> executor = new SqlExecutorPreparedStatement<>(adapter, params);
List<Student> list = executor.executeQuery(VALIDATE_SQL);
return list.isEmpty() ? null : list.get(0);
}
}

最后贴一下实验代码和输出:

package com.gerrard.demo;

import com.gerrard.entity.Student;
import com.gerrard.service.PreparedStatementLoginService;
import com.gerrard.service.StatementLoginService;
import com.gerrard.service.StudentLoginService; public final class InjectCase { public static void main(String[] args) { String id1 = "6";
String pass1 = "123456"; String id2 = "6";
String pass2 = "' or 1=1 or '"; // case 1, normal login with Statement
StudentLoginService service1 = new StatementLoginService();
Student student1 = service1.login(id1, pass1);
if (student1 == null) {
System.out.println("Login failure.");
} else {
System.out.println("Student [" + student1.getName() + "] login success.");
} // case 2, cracker login with PreparedStatement
StudentLoginService service2 = new StatementLoginService();
Student student2 = service2.login(id2, pass2);
if (student2 == null) {
System.out.println("Login failure.");
} else {
System.out.println("Student [" + student2.getName() + "] login success.");
} // case 3, normal login with Statement
StudentLoginService service3 = new PreparedStatementLoginService();
Student student3 = service3.login(id1, pass1);
if (student3 == null) {
System.out.println("Login failure.");
} else {
System.out.println("Student [" + student3.getName() + "] login success.");
} // case 4, cracker login with PreparedStatement
StudentLoginService service4 = new PreparedStatementLoginService();
Student student4 = service4.login(id2, pass2);
if (student4 == null) {
System.out.println("Login failure.");
} else {
System.out.println("Student [" + student4.getName() + "] login success.");
}
}
}

JDBC 学习笔记(六)—— PreparedStatement

JDBC 学习笔记(六)—— PreparedStatement的更多相关文章

  1. JDBC学习笔记&lpar;4&rpar;——PreparedStatement的使用

    PreparedStatement public interface PreparedStatement extends Statement;可以看到PreparedStatement是Stateme ...

  2. 【转】JDBC学习笔记&lpar;4&rpar;——PreparedStatement的使用

    转自:http://www.cnblogs.com/ysw-go/ PreparedStatement public interface PreparedStatement extends State ...

  3. JDBC 学习笔记(十)—— 使用 JDBC 搭建一个简易的 ORM 框架

    1. 数据映射 当我们获取到 ResultSet 之后,显然这个不是我们想要的数据结构. 数据库中的每一个表,在 Java 代码中,一定会有一个类与之对应,例如: package com.gerrar ...

  4. JDBC学习笔记二

    JDBC学习笔记二 4.execute()方法执行SQL语句 execute几乎可以执行任何SQL语句,当execute执行过SQL语句之后会返回一个布尔类型的值,代表是否返回了ResultSet对象 ...

  5. JDBC学习笔记一

    JDBC学习笔记一 JDBC全称 Java Database Connectivity,即数据库连接,它是一种可以执行SQL语句的Java API. ODBC全称 Open Database Conn ...

  6. java之jvm学习笔记六-十二&lpar;实践写自己的安全管理器&rpar;&lpar;jar包的代码认证和签名&rpar; &lpar;实践对jar包的代码签名&rpar; &lpar;策略文件&rpar;&lpar;策略和保护域&rpar; &lpar;访问控制器&rpar; &lpar;访问控制器的栈校验机制&rpar; &lpar;jvm基本结构&rpar;

    java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...

  7. Learning ROS for Robotics Programming Second Edition学习笔记&lpar;六&rpar; indigo xtion pro live

    中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...

  8. Typescript 学习笔记六:接口

    中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...

  9. python3&period;4学习笔记&lpar;六&rpar; 常用快捷键使用技巧,持续更新

    python3.4学习笔记(六) 常用快捷键使用技巧,持续更新 安装IDLE后鼠标右键点击*.py 文件,可以看到Edit with IDLE 选择这个可以直接打开编辑器.IDLE默认不能显示行号,使 ...

随机推荐

  1. Windows常用快捷方式

    总结了其他常用的快捷方式: Ctrl+C 复制 . Ctrl+V粘贴. Ctrl+X剪切. Delete删除. Alt+Tab 应用程序切换 Ctrl+Alt+Delete  Ctrl+shift+E ...

  2. XenServer pool 移除server 设置master

    如果因为Pool中Master主机由于某种原因导致失效,会引起整个Pool进入紧急模式,恢复步骤如下: 在成员服务器上输入如下命令 # xe host-emergency-ha-disable     ...

  3. iOS Block循环引用

    在介绍block循环引用前我们先了解一下typeof. typeof是什么??? typeof 是一个一元运算,放在一个运算数之前,运算数可以是任意类型. 它返回值是一个字符串,该字符串说明运算数的类 ...

  4. crack&period;vbs病毒,优盘里所有文件全变成快捷方式

    去了一趟学校打印店,用优盘copy打印了点东西,当时在打印店电脑里打开优盘的时候里面就变成了快捷方式,但没怎么在意.回来之后在自己电脑上居然也这样了.网上一搜是中了crack.vbs病毒了.格式化优盘 ...

  5. Linux root 密码重置与用户管理

    ---forget root password restart your linux system press 'e' when start. press 'e' again then choose ...

  6. Linux Shell编程三

    case分支条件语句. case "string" in pattern_1) commands ;; pattern_2) commands ;; *) commands ;; ...

  7. IDL 实现 EOF(经验正交函数分析)

    关于EOF详细介绍请wiki http://en.wikipedia.org/wiki/Empirical_orthogonal_functions或者Google之. 与PCA一样,EOF也是遥感多 ...

  8. C&num;之IComparable用法,实现List&lt&semi;T&gt&semi;&period;sort&lpar;&rpar;排序

    这篇文章主要介绍了C#的一些基础知识,主要是IComparable用法,实现List<T>.sort()排序,非常的实用,这里推荐给大家.   List<T>.sort()可以 ...

  9. eclipse中去掉py文件中烦人的黄色弹框

    eclipse中写py文件,当鼠标点击在参数上时总是出现黄线的弹框,影响人操作,感觉特别烦,如下: 解决方案: windows--preferences--hover--pydev--hover取消选 ...

  10. Django的安装

    ##pip pip是Python的包管理工具,用于快速安装配置所需要的拓展包,能够很好的解决包之间的依赖关系 当前ubuntu 系统上有两个Python环境,使用pip3 是指定Python3的环境 ...