JDBC 学习笔记(六)—— PreparedStatement
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学习笔记(4)——PreparedStatement的使用
PreparedStatement public interface PreparedStatement extends Statement;可以看到PreparedStatement是Stateme ...
- 【转】JDBC学习笔记(4)——PreparedStatement的使用
转自:http://www.cnblogs.com/ysw-go/ PreparedStatement public interface PreparedStatement extends State ...
- JDBC 学习笔记(十)—— 使用 JDBC 搭建一个简易的 ORM 框架
1. 数据映射 当我们获取到 ResultSet 之后,显然这个不是我们想要的数据结构. 数据库中的每一个表,在 Java 代码中,一定会有一个类与之对应,例如: package com.gerrar ...
- JDBC学习笔记二
JDBC学习笔记二 4.execute()方法执行SQL语句 execute几乎可以执行任何SQL语句,当execute执行过SQL语句之后会返回一个布尔类型的值,代表是否返回了ResultSet对象 ...
- JDBC学习笔记一
JDBC学习笔记一 JDBC全称 Java Database Connectivity,即数据库连接,它是一种可以执行SQL语句的Java API. ODBC全称 Open Database Conn ...
- java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)
java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...
- Learning ROS for Robotics Programming Second Edition学习笔记(六) indigo xtion pro live
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS for Robotics Pr ...
- Typescript 学习笔记六:接口
中文网:https://www.tslang.cn/ 官网:http://www.typescriptlang.org/ 目录: Typescript 学习笔记一:介绍.安装.编译 Typescrip ...
- python3.4学习笔记(六) 常用快捷键使用技巧,持续更新
python3.4学习笔记(六) 常用快捷键使用技巧,持续更新 安装IDLE后鼠标右键点击*.py 文件,可以看到Edit with IDLE 选择这个可以直接打开编辑器.IDLE默认不能显示行号,使 ...
随机推荐
- Bezier(贝塞尔曲线)
CDC::PolyBezierBOOL PolyBezier( const POINT* lpPoints, int nCount ); 和 曲线原理及多段曲线连接处如何光滑连接:第一段曲线要有4个点 ...
- Codeforces Round #411 div2
A. Fake NP 题意:询问一个区间[L,R]出现次数最多的正整数因子(>1). 一个区间内一个因子P出现次数大概为[R/P]-[(L-1)/P],约等于(R-L+1)/P,P取2时最优.注 ...
- LIBCD.lib(crt0.obj) : error LNK2001: unresolved external symbol _main
在创建MFC项目时,如果没有设置好项目参数, 就会在编译时产生很多连接错误, 如我今天遇到的: LIBCD.lib(crt0.obj) : error LNK2001: unresolved exte ...
- oracle的clob转换varchar2
time: 2008/02/29 author: skate oracle的clob转换varchar2 今天在做一个表的数据转移的时候,发现要他通过比较clob字段,但大家都知道clob字段是无法比 ...
- python基础一 day15 作业
3.处理文件,用户指定要查找的文件和内容,将文件中包含要查找内容的每一行都输出到屏幕def check_file(filename,aim): with open(filename,encoding= ...
- MFC多文档无法显示可停靠窗格
当我们使用MFC多文档创建项目时,我们可停靠窗格关闭之后就无法显示了.即使重新编译项目也无法再次显示它们. 原因:因为MFC多文档把这些设置存储在注册表 “HKEY_CURRENT_USER \ SO ...
- 如何下载并安装 robomongo 到Ubuntu 系统
官网下载软件,https://robomongo.org/download wget https://download.robomongo.org/1.2.1/linux/robo3t-1.2.1-l ...
- PHP安装Xcache扩展
简述 XCache 是一个又快又稳定的 PHP opcode 缓存器. 经过良好的测试并在大流量/高负载的生产机器上稳定运行. 经过(在 linux 上)测试并支持所有现行 PHP 分支的最新发布 ...
- Altium Designer入门学习笔记3:关于各模块分开布线的理解( 1)
观看"杜洋AD的讲解视频",杜洋着重强调了"模块分开"布线的好处. ---------------------------------------------- ...
- Windows7_64位 NVIDIA 卡 OpenCl环境配置
序 最近做一个项目需要用到OpenCL,由于之前没有接触过,所以在环境配置第一关就遇到了一些问题,查阅很多资料才配置完成,现在记录如下,希望给一些童鞋一些帮助. 整个步骤也很简单: 了解系统配置,选择 ...