版權(quán)聲明:本文為小斑馬偉原創(chuàng)文章,轉(zhuǎn)載請注明出處!
上篇播客介紹了SQL注入問題。本播客將介紹PreparedStatement來解決該問題。PreparedStatement:
1 可以通過調(diào)用 Connection 對象的 preparedStatement() 方法獲取 PreparedStatement 對象。
2 PreparedStatement 接口是 Statement 的子接口,它表示一條預(yù)編譯過的 SQL 語句。
-
3 PreparedStatement 對象所代表的 SQL 語句中的參數(shù)用問號(?)來表示,調(diào)用 PreparedStatement 對象的 setXXX() 方法來設(shè)置這些參數(shù). setXXX() 方法有兩個參數(shù),第一個參數(shù)是要設(shè)置的 SQL 語句中的參數(shù)的索引(從 1 開始),第二個是設(shè)置的 SQL 語句中的參數(shù)的值。
/** * 使用 PreparedStatement 將有效的解決 SQL 注入問題. */ @Test public void testSQLInjection2() { String username = "a' OR PASSWORD = "; String password = " OR '1'='1"; String sql = "SELECT * FROM users WHERE username = ? " + "AND password = ?"; Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { connection = JDBCTools.getConnection(); preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1, username); preparedStatement.setString(2, password); resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { System.out.println("登錄成功!"); } else { System.out.println("用戶名和密碼不匹配或用戶名不存在. "); } } catch (Exception e) { e.printStackTrace(); } finally { JDBCTools.releaseDB(resultSet, preparedStatement, connection); } }
PreparedStatement vs Statement
代碼的可讀性和可維護(hù)性. (防止SQL語句寫錯)
PreparedStatement 能最大可能提高性能:
DBServer會對預(yù)編譯語句提供性能優(yōu)化。因為預(yù)編譯語句有可能被重復(fù)調(diào)用,所以語句在被DBServer的編譯器編譯后的執(zhí)行代碼被緩存下來,那么下次調(diào)用時只要是相同的預(yù)編譯語句就不需要編譯,只要將參數(shù)直接傳入編譯過的語句執(zhí)行代碼中就會得到執(zhí)行。
在statement語句中,即使是相同操作但因為數(shù)據(jù)內(nèi)容不一樣,所以整個語句本身不能匹配,沒有緩存語句的意義.事實是沒有數(shù)據(jù)庫會對普通語句編譯后的執(zhí)行代碼緩存.這樣每執(zhí)行一次都要對傳入的語句編譯一次.
(語法檢查,語義檢查,翻譯成二進(jìn)制命令,緩存)
PreparedStatement 可以防止 SQL 注入
PreparedStatement更新(插入 刪除 更新)操作
@Test
public void testPreparedStatement() {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JDBCTools.getConnection();
String sql = "INSERT INTO customers (name, email, birth) "
+ "VALUES(?,?,?)";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "ATGUIGU");
preparedStatement.setString(2, "simpleit@163.com");
preparedStatement.setDate(3,
new Date(new java.util.Date().getTime()));
preparedStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.releaseDB(null, preparedStatement, connection);
}
}
PreparedStatement查詢操作
/**
* 具體查詢學(xué)生信息的. 返回一個 Student 對象. 若不存在, 則返回 null
*
* @param searchType
* : 1 或 2.
* @return
*/
private Student searchStudent(int searchType) {
String sql = "SELECT flowid, type, idcard, examcard,"
+ "studentname, location, grade " + "FROM examstudent "
+ "WHERE ";
Scanner scanner = new Scanner(System.in);
// 1. 根據(jù)輸入的 searchType, 提示用戶輸入信息:
// 1.1 若 searchType 為 1, 提示: 請輸入身份證號. 若為 2 提示: 請輸入準(zhǔn)考證號
// 2. 根據(jù) searchType 確定 SQL
if (searchType == 1) {
System.out.print("請輸入準(zhǔn)考證號:");
String examCard = scanner.next();
sql = sql + "examcard = '" + examCard + "'";
} else {
System.out.print("請輸入身份證號:");
String examCard = scanner.next();
sql = sql + "idcard = '" + examCard + "'";
}
// 3. 執(zhí)行查詢
Student student = getStudent(sql);
// 4. 若存在查詢結(jié)果, 把查詢結(jié)果封裝為一個 Student 對象
return student;
}
/**
* 根據(jù)傳入的 SQL 返回 Student 對象
*
* @param sql
* @return
*/
public Student getStudent(String sql, Object... args) {
Student stu = null;
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCTools.getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
stu = new Student();
stu.setFlowId(resultSet.getInt(1));
stu.setType(resultSet.getInt(2));
stu.setIdCard(resultSet.getString(3));
// ...
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCTools.releaseDB(resultSet, preparedStatement, connection);
}
return stu;
}
/**
* 從控制臺讀入一個整數(shù), 確定要查詢的類型
*
* @return: 1. 用身份證查詢. 2. 用準(zhǔn)考證號查詢 其他的無效. 并提示請用戶重新輸入.
*/
private int getSearchTypeFromConsole() {
System.out.print("請輸入查詢類型: 1. 用身份證查詢. 2. 用準(zhǔn)考證號查詢 ");
Scanner scanner = new Scanner(System.in);
int type = scanner.nextInt();
if (type != 1 && type != 2) {
System.out.println("輸入有誤請重新輸入!");
throw new RuntimeException();
}
return type;
}
Student類:
public class Student {
// 流水號
private int flowId;
// 考試的類型
private int type;
// 身份證號
private String idCard;
// 準(zhǔn)考證號
private String examCard;
// 學(xué)生名
private String studentName;
// 學(xué)生地址
private String location;
// 考試分?jǐn)?shù).
private int grade;
public int getFlowId() {
return flowId;
}
public void setFlowId(int flowId) {
this.flowId = flowId;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getIdCard() {
return idCard;
}
public void setIdCard(String idCard) {
this.idCard = idCard;
}
public String getExamCard() {
return examCard;
}
public void setExamCard(String examCard) {
this.examCard = examCard;
}
public String getStudentName() {
return studentName;
}
public void setStudentName(String studentName) {
this.studentName = studentName;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
public Student(int flowId, int type, String idCard, String examCard,
String studentName, String location, int grade) {
super();
this.flowId = flowId;
this.type = type;
this.idCard = idCard;
this.examCard = examCard;
this.studentName = studentName;
this.location = location;
this.grade = grade;
}
public Student() {
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "Student [flowId=" + flowId + ", type=" + type + ", idCard="
+ idCard + ", examCard=" + examCard + ", studentName="
+ studentName + ", location=" + location + ", grade=" + grade
+ "]";
}
}