JDBC【4】-- jdbc預編譯與拼接sql對比

  • 在jdbc中,有三種方式執行sql,分別是使用Statement(sql拼接),PreparedStatement(預編譯),還有一種CallableStatement(存儲過程),在這裏我就不介紹CallableStatement了,咱們來看看Statement與PreparedStatement的區別。
1. 建立數據庫,數據表

數據庫名字是test,數據表的名字是student,裏面有四個字段,一個是id,也就是主鍵(自動遞增),還有名字,年齡,成績。最後先使用sql語句插入六個測試記錄。java

CREATE DATABASE `test` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
CREATE TABLE `student` ( `id` INT NOT NULL AUTO_INCREMENT , `name` VARCHAR(20) NOT NULL , 
`age` INT NOT NULL , `score` DOUBLE NOT NULL , PRIMARY KEY (`id`)) ENGINE = MyISAM; 
INSERT INTO `student` VALUES (1, '小紅', 26, 83);
INSERT INTO `student` VALUES (2, '小白', 23, 93);
INSERT INTO `student` VALUES (3, '小明', 34, 45);
INSERT INTO `student` VALUES (4, '張三', 12, 78);
INSERT INTO `student` VALUES (5, '李四', 33, 96);
INSERT INTO `student` VALUES (6, '魏紅', 23, 46);

創建對應的學生類:mysql

/**
 * student類,字段包括id,name,age,score
 * 實現無參構造,帶參構造,toString方法,以及get,set方法
 * @author 秦懷
 */
public class Student {
	private int id;
	private String name;
	private int age;
	private double score;
	
	public Student() {
		super();
		// TODO Auto-generated constructor stub
	}
	public Student(String name, int age, double score) {
		super();
		this.name = name;
		this.age = age;
		this.score = score;
	}
	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 double getScore() {
		return score;
	}
	public void setScore(double score) {
		this.score = score;
	}
	@Override
	public String toString() {
		return "Student [id=" + id + ", name=" + name + ", age=" + age
				+ ", score=" + score + "]";
	}
	
}
2.Statement

先來看代碼,下面是獲取數據庫鏈接的工具類 DBUtil.classsql

public class DBUtil {
	private static String URL="jdbc:mysql://127.0.0.1:3306/test";
	private static String USER="root";
	private static String PASSWROD ="123456";
	private static Connection connection=null;
	static{
		try {
			Class.forName("com.mysql.jdbc.Driver");
			// 獲取數據庫鏈接
			connection=DriverManager.getConnection(URL,USER,PASSWROD);
			System.out.println("鏈接成功");
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	// 返回數據庫鏈接
	public static Connection getConnection(){
		return connection;
	}
}

下面是根據id查詢學生信息的代碼片斷,返回student對象就能輸出了:數據庫

public Student selectStudentByStatement(int id){
	    // 拼接sql語句
		String sql ="select * from student where id = "+id;
		try {
		    // 獲取statement對象
			Statement statement = DBUtil.getConnection().createStatement();
			// 執行sql語句,返回 ResultSet
			ResultSet resultSet = statement.executeQuery(sql);
			Student student = new Student();
			// 一條也只能使用resultset來接收
			while(resultSet.next()){
				student.setId(resultSet.getInt("id"));
				student.setName(resultSet.getString("name"));
				student.setAge(resultSet.getInt("age"));
				student.setScore(resultSet.getDouble("score"));
			}
			return student;
		} catch (SQLException e) {
			// TODO: handle exception
		}
		return null;
	}

咱們能夠看到整個流程是先獲取到數據庫的鏈接Class.forName("com.mysql.jdbc.Driver"); connection=DriverManager.getConnection(URL,USER,PASSWROD);獲取到鏈接以後經過鏈接獲取statement對象,經過statement來執行sql語句,返回resultset這個結果集,Statement statement = DBUtil.getConnection().createStatement();ResultSet resultSet = statement.executeQuery(sql);,值得注意的是,上面的sql是已經拼接好,寫固定了的sql,因此很容易被注入,好比這句:緩存

sql = "select * from user where name= '" + name + "' and password= '" + password+"'";

若是有人ide

  • name = "name' or '1'= `1"
  • password = "password' or '1'='1",那麼整個語句就會變成:
sql = "select * from user where name= 'name' or '1'='1' and password= 'password' or '1'='1'";

那麼就會返回全部的信息,因此這是很危險的。
還有更加危險的,是在後面加上刪除表格的操做,不過通常咱們都不會把這些權限開放的。工具

// 若是password = " ';drop table user;select * from user where '1'= '1"
// 後面一句不會執行,可是這已經能夠刪除表格了
sql = "select * from user where name= 'name' or '1'='1' and password= '' ;drop table user;select * from user where '1'= '1'";

因此預編譯顯得尤其重要了。學習

3.PreparedStatement預編譯

咱們先來看看預編譯的代碼:測試

// 根據id查詢學生
	public Student selectStudent(int id){
		String sql ="select * from student where id =?";
		try {
			PreparedStatement preparedStatement = DBUtil.getConnection()..prepareStatement(sql);
			preparedStatement.setInt(1, id);
			ResultSet resultSet = preparedStatement.executeQuery();
			Student student = new Student();
			// 一條也只能使用resultset來接收
			while(resultSet.next()){
				student.setId(resultSet.getInt("id"));
				student.setName(resultSet.getString("name"));
				student.setAge(resultSet.getInt("age"));
				student.setScore(resultSet.getDouble("score"));
			}
			return student;
		} catch (SQLException e) {
			// TODO: handle exception
		}
		return null;
	}

預編譯也是一樣須要獲取到數據庫鏈接對象connection,可是sql語句拼接的時候使用了佔位符?,將含有佔位符的sql當參數傳進去,獲取到PreparedStatement預編譯的對象,最後是經過set來綁定參數,而後再去使用execute執行預編譯過的代碼。這樣就避免了sql注入的問題,同時,因爲sql已經編譯過緩存在數據庫中,因此執行起來不用再編譯,速度就會比較快。this

4.爲何預編譯能夠防止sql注入
  • 在使用佔位符,或者說參數的時候,數據庫已經將sql指令編譯過,那麼查詢的格式已經訂好了,也就是咱們說的我已經明白你要作什麼了,你要是將不合法的參數傳進去,會有合法性檢查,用戶只須要提供參數給我,參數不會當成指令部分來執行,也就是預編譯已經把指令以及參數部分區分開,參數部分不容許傳指令進來。
    這樣的好處查詢速度提升,由於有了預編譯緩存,方便維護,可讀性加強,不會有不少單引號雙引號,容易出錯,防止大部分的sql注入,由於參數和sql指令部分數據庫系統已經區分開。百度文庫裏面提到:傳遞給PreparedStatement對象的參數能夠被強制進行類型轉換,使開發人員能夠確保在插入或查詢數據時與底層的數據庫格式匹配。
    要是理解不透徹能夠這麼來理解:
select * from student where name= ?

預編譯的時候是先把這句話編譯了,生成sql模板,至關於生成了一個我知道你要查名字了,你把名字傳給我,你如今想耍點小聰明,把字符串'Jame' or '1=1'傳進去,你覺得他會變成下面這樣麼:

select * from student where name= 'Jame' or '1=1'

放心吧,不可能的,這輩子都不可能的啦,數據庫都知道你要幹嗎了,我不是有sql模板了麼,數據庫的內心想的是我叫你傳名字給我,行,這名字有點長,想害我,能夠,我幫你找,那麼數據庫去名字這一字段幫你找一個叫'Jame' or '1=1'的人,他內心想這人真逗,沒有這我的,沒有!!!
因此這也就是爲何預編譯能夠防止sql注入的解釋了,它是通過了解釋器解釋過的,解釋的過程我就不囉嗦了,只要是對參數作轉義,轉義以後讓它在拼接時只能表示字符串,不能變成查詢語句。

此文章僅表明本身(本菜鳥)學習積累記錄,或者學習筆記,若有侵權,請聯繫做者刪除。人無完人,文章也同樣,文筆稚嫩,在下不才,勿噴,若是有錯誤之處,還望指出,感激涕零~

技術之路不在一時,山高水長,縱使緩慢,馳而不息。

公衆號:秦懷雜貨店

相關文章
相關標籤/搜索