5单个编译总会编译全部_JDBC【5】 JDBC预编译和拼接Sql对比
- 在jdbc中,有三種方式執(zhí)行sql,分別是使用Statement(sql拼接),PreparedStatement(預(yù)編譯),還有一種CallableStatement(存儲過程),在這里我就不介紹CallableStatement了,我們來看看Statement與PreparedStatement的區(qū)別。
1. 創(chuàng)建數(shù)據(jù)庫,數(shù)據(jù)表
數(shù)據(jù)庫名字是test,數(shù)據(jù)表的名字是student,里面有四個字段,一個是id,也就是主鍵(自動遞增),還有名字,年齡,成績。最后先使用sql語句插入六個測試記錄。
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);
建立對應(yīng)的學(xué)生類:
/**?*?student類,字段包括id,name,age,score
?*?實現(xiàn)無參構(gòu)造,帶參構(gòu)造,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.測試代碼
先來看代碼,下面是獲取數(shù)據(jù)庫連接的工具類 DBUtil.class:
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");
???//?獲取數(shù)據(jù)庫連接
???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();
??}
?}
?//?返回數(shù)據(jù)庫連接
?public?static?Connection?getConnection(){
??return?connection;
?}
}
下面是根據(jù)id查詢學(xué)生信息的代碼片段,返回student對象就能輸出了:
?public?Student?selectStudentByStatement(int?id){?????//?拼接sql語句
??String?sql?="select?*?from?student?where?id?=?"+id;
??try?{
??????//?獲取statement對象
???Statement?statement?=?DBUtil.getConnection().createStatement();
???//?執(zhí)行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;
?}
我們可以看到整個流程是:
- 先獲取到數(shù)據(jù)庫的連接Class.forName("com.mysql.jdbc.Driver"); connection=DriverManager.getConnection(URL,USER,PASSWROD);
- 獲取到連接之后通過連接獲取statement對象,通過statement來執(zhí)行sql語句,返回resultset這個結(jié)果集,Statement statement = DBUtil.getConnection().createStatement();ResultSet resultSet = statement.executeQuery(sql);
- 值得注意的是,上面的sql是已經(jīng)拼接好,寫固定了的sql,所以很容易被注入,比如這句:
如果有人的name故意傳入很奇怪的字符串:
- name = "name' or '1'= '1"
- password = "password' or '1'='1",那么整個語句就會變成:
那么就會返回所有的信息,所以這是很危險的。還有更加危險的,是在后面加上刪除表數(shù)據(jù)的操作,不過一般我們都不會把這些權(quán)限開放的。
//?如果password?=?"?';drop?table?user;select?*?from?user?where?'1'=?'1"//?后面一句不會執(zhí)行,但是這已經(jīng)可以刪除表格了
sql?=?"select?*?from?user?where?name=?'name'?or?'1'='1'?and?password=?''?;drop?table?user;select?*?from?user?where?'1'=?'1'";
所以預(yù)編譯顯得尤為重要了。
3.PreparedStatement預(yù)編譯
我們先來看看預(yù)編譯的代碼:
?//?根據(jù)id查詢學(xué)生?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;
?}
預(yù)編譯也是同樣需要獲取到數(shù)據(jù)庫連接對象connection,但是sql語句拼接的時候使用了占位符 ?,將含有占位符的sql當(dāng)參數(shù)傳進去,獲取到PreparedStatement預(yù)編譯的對象,最后是通過set來綁定參數(shù),然后再去使用execute執(zhí)行預(yù)編譯過的代碼。這樣就避免了sql注入的問題,同時,由于sql已經(jīng)編譯過緩存q起來,所以執(zhí)行起來不用再編譯,速度就會比較快。
4.為什么預(yù)編譯可以防止sql注入
- 在使用占位符,或者說參數(shù)的時候,數(shù)據(jù)庫已經(jīng)將sql指令編譯過,那么查詢的格式已經(jīng)訂好了,也就是我們說的我已經(jīng)明白你要做什么了,你要是將不合法的參數(shù)傳進去,會有合法性檢查,用戶只需要提供參數(shù)給我,參數(shù)不會當(dāng)成指令部分來執(zhí)行,也就是預(yù)編譯已經(jīng)把指令以及參數(shù)部分區(qū)分開,參數(shù)部分不允許傳指令進來。這樣的好處查詢速度提高,因為有了預(yù)編譯緩存,方便維護,可讀性增強,不會有很多單引號雙引號,容易出錯,防止大部分的sql注入,因為參數(shù)和sql指令部分?jǐn)?shù)據(jù)庫系統(tǒng)已經(jīng)區(qū)分開。百度文庫里面提到:傳遞給PreparedStatement對象的參數(shù)可以被強制進行類型轉(zhuǎn)換,使開發(fā)人員可以確保在插入或查詢數(shù)據(jù)時與底層的數(shù)據(jù)庫格式匹配。要是理解不透徹可以這么來理解:
預(yù)編譯的時候是先把這句話編譯了,生成sql模板,相當(dāng)于生成了一個我知道你要查名字了,你把名字傳給我,你現(xiàn)在想耍點小聰明,把字符串'Jame' or '1=1'傳進去,你以為他會變成下面這樣么:
select?*?from?student?where?name=?'Jame'?or?'1=1'放心吧,不可能的,這輩子都不可能的啦,數(shù)據(jù)庫都知道你要干嘛了,我不是有sql模板了么,數(shù)據(jù)庫的心里想的是我叫你傳名字給我,行,這名字有點長,想害我,可以,我?guī)湍阏?#xff0c;那么數(shù)據(jù)庫去名字這一字段幫你找一個叫'Jame' or '1=1'的人,他心里想這人真逗,沒有這個人,沒有!!!而不會將你的語句執(zhí)行,預(yù)編譯大概就是提前知道了你大概是需要干什么?最后你只需要將參數(shù)傳遞過來,參數(shù)的地方是個占位符,而不會把參數(shù)解析成為具有語義的語句。
我理解的,這也就是為什么預(yù)編譯可以防止sql注入的解釋了,它是經(jīng)過了解釋器解釋過的,解釋的過程我就不啰嗦了,只要是對參數(shù)做轉(zhuǎn)義,轉(zhuǎn)義之后讓它在拼接時只能表示字符串,不能變成查詢語句。
此文章僅代表自己(本菜鳥)學(xué)習(xí)積累記錄,或者學(xué)習(xí)筆記,如有侵權(quán),請聯(lián)系作者刪除。人無完人,文章也一樣,文筆稚嫩,在下不才,勿噴,如果有錯誤之處,還望指出,感激不盡~
技術(shù)之路不在一時,山高水長,縱使緩慢,馳而不息。
公眾號:秦懷雜貨店
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的5单个编译总会编译全部_JDBC【5】 JDBC预编译和拼接Sql对比的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: labview linux 内核 不匹配
- 下一篇: 机械制造工艺基础_机械制造工艺基础知识,