CRainyDay

通过 PreparedStatement 来执行 SQL 语句

一.PreparedStatement初识

  • 之前我们都是通过 Statement 来执行的 SQL 语句
  • 可以通过调用 Connection 对象的 preparedStatement() 方法获取PreparedStatement 对象

  • PreparedStatement 接口是 Statement 的子接口, 它表示一条预编译过的 SQL 语句

  • PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示, 调用PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数, 第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始), 第二个是设置的 SQL 语句中的参数的值

1.为什么要用PreparedStatement

  1. 看之前用Statement的程序, SQL语句需要我们去拼接, 很麻烦, 而PreparedStatement 就不需要我们拼接.

  2. 使用 PreparedStatement 代码的可读性和可维护性好.

  3. PreparedStatement 能最大可能提高性能:

 – DBServer会对预编译语句提供性能优化. 因为预编译语句有可能被重复调用, 所以语句在被DBServer的编译器编译后的执行代码被缓存下来, 那么下次调用时只要是相同的预编译语句就不需要编译, 只要将参数直接传入编译过的语句执行代码中就会得到执行.

 – 在statement语句中, 即使是相同操作但因为数据内容不一样, 所以整个语句本身不能匹配, 没有缓存语句的意义. 事实是没有数据库会对普通语句编译后的执行代码缓存. 这样每执行一次都要对传入的语句编译一次.

 – (语法检查, 语义检查, 翻译成二进制命令, 缓存)

  1. PreparedStatement 可以有效防止 SQL 注入

2.SQL 注入

  • SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查, 而在用户输入数据中注入非法的 SQL 语句段或命令, 从而利用系统的 SQL 引擎完成恶意行为的做法

  • 对于 Java 而言, 要防范 SQL 注入, 只要用 PreparedStatement 取代 Statement 就可以了

  例, 在数据库的UserInfo表中查询, 是否有某个用户名及对应密码. 用户名为: a’
OR PASSWORD = ;密码为: OR ‘1’=’1’

用 Statement 方式, 拼接的 SQL 语句如下:

1
2
SELECT UserName,Password FROM UserInfo WHERE UserName = 'a'
OR PASSWORD = ' AND Password = ' OR '1'='1';

而 PreparedStatement 方式, 生成的 SQL 语句如下:

1
2
3
SELECT UserName,Password FROM UserInfo WHERE UserName = "a'
OR PASSWORD = "
AND Password = " OR '1'='1'";

二.使用PreparedStatement

1.executeUpdate()

在 JDBCTools类中, 重载之前用Statement写的update(String sql)方法, 重载函数为update(String sql,Object … args).

这里的sql是带有占位符的SQL语句, 后面利用可变个数形参的方法传递参数. 若用数组的话, 在传递参数的时候还要将那些参数封装为数组.

这里的SQL语句是 insert delete update 语句, 不能是select语句. 之后用 PreparedStatement 的 executeUpdate() 方法执行生成的 SQL 语句.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class JDBCTools {
/**
* 上述update()的重载,以PreparedStatement方式执行SQL
* @param sql
* @param args:SQL中占位符对应的值
*/
public static void update(String sql,Object ... args) {
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCTools.getConnection();
ps = conn.prepareStatement(sql);
for(int i = 0;i < args.length;i ++) {
ps.setObject(i+1, args[i]);
}
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCTools.release(null,ps, conn);
}
}
}

2.executeQuery()

使用 PreparedStatement 的executeQuery()方法, 执行带有 select 的SQL语句.

当然, 你也可以和执行 JDBCTools 里的update()方法一样, 将查询数据库的代码抽取成一个工具方法, 之后会讲如何写一个通用的查询方法, 放在 JDBCTools 类里面. 我就直接测试了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

import java.io.FilterInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Scanner;
import org.junit.Test;

public class TestJDBCTools {
@Test
public void testAddStudent() {
Student stu = getStudentFromConsole();
addStudent(stu);
}
private Student getStudentFromConsole() {
Student stu = new Student();
Scanner scanner = new Scanner(System.in);
System.out.print("请输入学生ID:");
stu.setId(scanner.nextInt());
System.out.print("请输入学生Name:");
stu.setName(scanner.next());
System.out.print("请输入学生Email:");
stu.setEmail(scanner.next());
System.out.print("请输入学生Birth:");
stu.setDate(Date.valueOf(scanner.next()));
System.out.println(stu.getDate());
scanner.close();
return stu;
}
public void addStudent(Student stu) {
String sql = "INSERT INTO Student VALUES(?,?,?,?)";
JDBCTools.update(sql,stu.getId(),stu.getName(),stu.getEmail(),stu.getDate());
}

//上边是测试的 PreparedStatement 实现的 update()方法

@Test
public void testSearchStudent() {
int type = getSearchType();
Student stu = searchStudent(type);
showMsg(stu);
}
private void showMsg(Student stu) {
if(stu == null) {
System.out.println("查无此人!");
}else {
System.out.println(stu);
}
}
private int getSearchType() {
System.out.print("请输入查询类型:1.id查询 2.姓名查询");
Scanner scanner = new Scanner(new FilterInputStream(System.in){
@Override
public void close() throws IOException {
}
});
int type = scanner.nextInt();
scanner.close();
if(type != 1 && type != 2) {
System.out.println("输入的查询类型有误!");
throw new RuntimeException();
}
return type;
}
public Student searchStudent(int type) {
String sql = null;
Student stu = null;
Scanner scanner = new Scanner(System.in);
if(type == 1) {
System.out.print("请输入要查询的id:");
int id = scanner.nextInt();
sql = "SELECT * FROM Student WHERE id = ?";
stu = getStudent(sql,id);
}else {
System.out.print("请输入要查询的Name:");
String name = scanner.next();
sql = "SELECT * FROM Student WHERE name = '?'";
stu = getStudent(sql,name);
}
scanner.close();
return stu;
}
private Student getStudent(String sql,Object ... args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
Student stu = null;
try {
conn = JDBCTools.getConnection();
ps = conn.prepareStatement(sql);
for(int i = 0;i < args.length;i ++) {
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
if(rs.next()) {
stu = new Student(
rs.getInt(1),
rs.getString(2).trim(),
rs.getString(3).trim(),
rs.getDate(4));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.release(rs, ps, conn);
}
return stu;
}
}

 评论



本博客所有文章除特别声明外,均采用[(CC BY-NC-SA 4.0)]许可协议。转载请注明出处!

本站使用 Material X 作为主题 , 总访问量为 次 。
Copyright © 2019 CRainyDay's Blog 版权所有