PreparedSatement预编译对象
目标
能够理解什么是SQL注入
能够理解PreparedSatement的执行原理
讲解
SQL注入问题
在我们前一天JDBC实现登录案例中,当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了
请输入用户名:
hehe
请输入密码:
a' or '1'='1
问题分析:
// 代码中的SQL语句
"SELECT * FROM user WHERE name='" + name + "' AND password='" + password + "';";
// 将用户输入的账号密码拼接后
"SELECT * FROM user WHERE name='hehe' AND password='a' or '1'='1';"
我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。
要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。需要使用PreparedSatement类解决SQL注入。
PreparedSatement的执行原理
继承结构:
我们写的SQL语句让数据库执行,数据库不是直接执行SQL语句字符串。和Java一样,数据库需要执行编译后的SQL语句(类似Java编译后的字节码文件)。
Satement
对象每执行一条SQL语句都会先将这条SQL语句发送给数据库编译,数据库再执行。
Statement stmt = conn.createStatement();
stmt.executeUpdate("INSERT INTO users VALUES (1, '张三', '123456');");
stmt.executeUpdate("INSERT INTO users VALUES (2, '李四', '666666');");
上面2条SQL语句我们可以看到大部分内容是相同的,只是数据略有不一样。数据库每次执行都编译一次。如果有1万条类似的SQL语句,数据库需要编译1万次,执行1万次,显然效率就低了。
prepareStatement()
会先将SQL语句发送给数据库预编译。PreparedStatement
会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement
对象并执行。相当于调用方法多次传入不同的参数。
String sql = "INSERT INTO users VALUES (?, ?, ?);";
// 会先将SQL语句发送给数据库预编译。PreparedStatement会引用着预编译后的结果。
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, 1);
pstmt.setInt(2, "张三");
pstmt.setString(3, "123456");
pstmt.executeUpdate();
// 再次设置参数
pstmt.setString(1, 2);
pstmt.setInt(2, "李四");
pstmt.setString(3, "66666");
pstmt.executeUpdate();
上面预编译好一条SQL,2次传入了不同的参数并执行。如果有1万条类似的插入数据的语句。数据库只需要预编译一次,传入1万次不同的参数并执行。减少了SQL语句的编译次数,提高了执行效率。
示意图
PreparedSatement的好处
prepareStatement()
会先将SQL语句发送给数据库预编译。PreparedStatement
会引用着预编译后的结果。可以多次传入不同的参数给PreparedStatement
对象并执行。减少SQL编译次数,提高效率。- 安全性更高,没有SQL注入的隐患。
- 提高了程序的可读性## PreparedSatement的API介绍
目标
能够掌握PreparedSatement相应的API
讲解
获取PreparedSatement
在java.sql.Connection
有获取PreparedSatement
对象的方法
PreparedStatement prepareStatement(String sql)
会先将SQL语句发送给数据库预编译。PreparedStatement对象会引用着预编译后的结果。
PreparedSatement的API介绍
在java.sql.PreparedStatement
中有设置SQL语句参数,和执行参数化的SQL语句的方法
-
void setDouble(int parameterIndex, double x) 将指定参数设置为给定 Java double 值。
-
void setFloat(int parameterIndex, float x) 将指定参数设置为给定 Java REAL 值。
-
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 值。
-
ResultSet executeQuery() 在此 PreparedStatement 对象中执行 SQL 查询,并返回该查询生成的ResultSet对象。
-
int executeUpdate() 在此 PreparedStatement 对象中执行 SQL 语句,该语句必须是一个 SQL 数据操作语言DML语句,比如 INSERT、UPDATE 或 DELETE 语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。
PreparedSatement使用步骤
- 编写SQL语句,未知内容使用?占位:
"SELECT * FROM user WHERE name=? AND password=?;";
- 获得PreparedStatement对象
- 设置实际参数
- 执行参数化SQL语句
- 关闭资源## PreparedSatement实现增删查改
目标
能够掌握PreparedSatement实现增删查改
讲解
创建表结构
CREATE TABLE employee (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT,
address VARCHAR(50)
);
添加数据
向Employee表添加3条记录
// 添加数据: 向Employee表添加3条记录
public static void addEmployee() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "INSERT INTO employee VALUES (NULL, ?, ?, ?);";
// prepareStatement()会先将SQL语句发送给数据库预编译。
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置参数
pstmt.setString(1, "刘德华");
pstmt.setInt(2, 57);
pstmt.setString(3, "香港");
int i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
// 再次设置参数
pstmt.setString(1, "张学友");
pstmt.setInt(2, 55);
pstmt.setString(3, "澳门");
i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
// 再次设置参数
pstmt.setString(1, "黎明");
pstmt.setInt(2, 52);
pstmt.setString(3, "香港");
i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
JDBCUtils.close(conn, pstmt);
}
效果:
PreparedSatement修改数据
将id为2的学生地址改成台湾
// 修改数据: 将id为2的学生地址改成台湾
public static void updateEmployee() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "UPDATE employee SET address=? WHERE id=?;";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "台湾");
pstmt.setInt(2, 2);
int i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
JDBCUtils.close(conn, pstmt);
}
效果:
PreparedSatement删除数据
删除id为2的员工
// 删除数据: 删除id为2的员工
public static void deleteEmployee() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "DELETE FROM employee WHERE id=?;";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 2);
int i = pstmt.executeUpdate();
System.out.println("影响的行数:" + i);
JDBCUtils.close(conn, pstmt);
}
效果:
使用PreparedSatement改写登录案例
PreparedSatement使用步骤
- 编写SQL语句,未知内容使用?占位
- 获得PreparedStatement对象
- 设置实际参数
- 执行参数化SQL语句
- 关闭资源
案例代码
public class Demo02 {
public static void main(String[] args) throws Exception {
// 让用户输入账号和密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入账号: ");
String name = sc.nextLine();
System.out.println("请输入密码: ");
String password = sc.nextLine();
// 获取连接
Connection conn = JDBCUtils.getConnection();
// 编写SQL语句,未知内容使用?占位
String sql = "SELECT * FROM user WHERE name=? AND password=?;";
// prepareStatement()会先将SQL语句发送给数据库预编译。
PreparedStatement pstmt = conn.prepareStatement(sql);
// 指定?的值
// parameterIndex: 第几个?,从1开始算
// x: 具体的值
pstmt.setString(1, name);
pstmt.setString(2, password); // 正确的密码
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
String name = rs.getString("name");
System.out.println("name:" + name);
} else {
System.out.println("没有找到数据...");
}
JDBCUtils.close(conn, pstmt, rs);
}
}
PreparedSatement查询数据
目标
能够掌握PreparedSatement实现查询数据
查询id小于8的员工信息,并保存到员工类中
实现步骤
- 得到连接对象
- 得到Statement对象
- 编写SQL语句并执行,保存ResultSet
- 创建一个集合用于封装所有的记录
- 每次循环封装一个学生对象
- 把数据放到集合中
- 关闭连接
- 遍历集合,循环输出学生对象
代码
public class Employee {
private int id;
private String name;
private int age;
private String address;
public Employee() {
}
public Employee(int id, String name, int age, String address) {
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Employee2 [id=" + id + ", name=" + name + ", age=" + age + ", address=" + address + "]";
}
}
// 查询数据: 查询id小于8的员工信息,并保存到员工类中
public static void queryEmployee() throws Exception {
Connection conn = JDBCUtils.getConnection();
String sql = "SELECT * FROM employee WHERE id<?;";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 26);
ResultSet rs = pstmt.executeQuery();
// 创建集合存放多个Employee2对象
ArrayList<Employee> list = new ArrayList<>();
while (rs.next()) {
// 移动到下一行有数据,取出这行数据
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
String address = rs.getString("address");
// 创建Employee2对象
Employee e = new Employee(id, name, age, address);
// 将创建好的员工添加到集合中
list.add(e);
}
// 输出对象
for (Employee e : list) {
System.out.println(e);
}
JDBCUtils.close(conn, pstmt, rs);
}
效果: