1. 程式人生 > >Mysql中的Statement、PreparedStatement 和 CallableStatement

Mysql中的Statement、PreparedStatement 和 CallableStatement

首先,官方對於這3個介面的定義是這樣的:
在這裡插入圖片描述
PreparedStatement是用來執行SQL查詢語句的API之一,Java提供了 Statement、PreparedStatement 和 CallableStatement三種方式來執行查詢語句,其中 Statement 用於通用查詢, PreparedStatement 用於執行引數化查詢,而 CallableStatement則是用於儲存過程。
1.Statement
1.1建立Statement物件

在使用Statement物件執行SQL語句之前,需要使用Connection物件的createStatement()方法建立一個Statement物件,如以下示例所示:

Statement stmt = null;
try {
   stmt = conn.createStatement( );
   . . .
}
catch (SQLException e) {
   . . .
}
finally {
   . . .
}

在建立Statement物件後,可以使用它來執行一個SQL語句,它有三個執行方法可以執行。它們分別是 -
• boolean execute (String SQL) : 如果可以檢索到ResultSet物件,則返回一個布林值true; 否則返回false。使用此方法執行SQLDDL語句或需要使用真正的動態SQL,可使用於執行建立資料庫,建立表的SQL語句等等。
• int executeUpdate (String SQL): 返回受SQL語句執行影響的行數。使用此方法執行預期會影響多行的SQL語句,例如:INSERT,UPDATE或DELETE語句。
• ResultSet executeQuery(String SQL):返回一個ResultSet物件。 當您希望獲得結果集時,請使用此方法,就像使用SELECT語句一樣。
1.2. 關閉Statement物件


就像關閉一個Connection物件一樣,以儲存資料庫資源一樣,由於同樣的原因,還應該關閉Statement物件。
一個簡單的呼叫close()方法將執行該作業(工作)。 如果先關閉Connection物件,它也會關閉Statement物件。 但是,應該始終顯式關閉Statement物件,以確保正確的清理順序。

Statement stmt = null;
try {
   stmt = conn.createStatement( );
   . . .
}
catch (SQLException e) {
   . . .
}
finally {
   stmt.close();
}

每次都會執行SQL語句,相關資料庫都要執行SQL語句的編譯。
Statement為一條Sql語句生成執行計劃,

舉例:如果要執行兩條sql語句

select colume from table where colume=1;
select colume from table where colume=2;

會生成兩個執行計劃
一千個查詢就生成一千個執行計劃!
為了更好的理解,建議學習Statment示例教程 。
2 PreparedStatement
a. PreparedStatement介面繼承Statement, PreparedStatement 例項包含已編譯的 SQL 語句,所以其執行速度要快於 Statement 物件;

b.作為 Statement 的子類,PreparedStatement 繼承了 Statement 的所有功能。三種方法
execute、 executeQuery 和 executeUpdate 已被更改以使之不再需要引數
PreparedStatement介面擴充套件了Statement介面,它添加了比Statement物件更好一些優點的功能。
此語句可以動態地提供/接受引數。
2.1 建立PreparedStatement物件

PreparedStatement pstmt = null;
try {
   String SQL = "Update Employees SET age = ? WHERE id = ?";
   pstmt = conn.prepareStatement(SQL);
   . . .
}
catch (SQLException e) {
   . . .
}
finally {
   . . .
}

JDBC中的所有引數都由 ? 符號作為佔位符,這被稱為引數標記。 在執行SQL語句之前,必須為每個引數(佔位符)提供值。
setXXX()方法將值繫結到引數,其中XXX表示要繫結到輸入引數的值的Java資料型別。 如果忘記提供繫結值,則將會丟擲一個SQLException。
每個引數標記是它其順序位置引用。第一個標記表示位置1,下一個位置2等等。 該方法與Java陣列索引不同(它不從0開始)。
所有Statement物件與資料庫互動的方法(a)execute(),(b)executeQuery()和©executeUpdate()也可以用於PreparedStatement物件。 但是,這些方法被修改為可以使用輸入引數的SQL語句。
2.2. 關閉PreparedStatement物件
就像關閉Statement物件一樣,由於同樣的原因(節省資料庫系統資源),也應該關閉PreparedStatement物件。
簡單的呼叫close()方法將執行關閉。 如果先關閉Connection物件,它也會關閉PreparedStatement物件。 但是,應該始終顯式關閉PreparedStatement物件,以確保以正確順序清理資源。

PreparedStatement pstmt = null;
try {
   String SQL = "Update Employees SET age = ? WHERE id = ?";
   pstmt = conn.prepareStatement(SQL);
   . . .
}
catch (SQLException e) {
   . . .
}
finally {
   pstmt.close();
}

2.3 PreparedStatement物件在初級開發中幾個常用的方法:
1). PreparedStatement.execute()方法:
該語句可以是任何種類的 SQL 語句
execute 方法返回一個 boolean 值,以指示第一個結果的形式。必須呼叫 getResultSet 或 getUpdateCount 方法來檢索結果,並且必須呼叫 getMoreResults 移動到任何後面的結果。
返回:
如果第一個結果是 ResultSet 物件,則返回 true;如果第一個結果是更新計數或者沒有結果,則返回 false
意思就是如果是查詢的話返回true,如果是更新或插入的話就返回false了;
2). PreparedStatement.executeQuery()方法:
返回執行查詢語句後得到的ResultSet結果集,注意:該結果集永遠不能為null。
3). PreparedStatement.executeUpdate()方法:
返回一個int型別的值,該值代表執行INSERT、DELETE以及UPDATE語句後的更新行數。
如果該值為0,則代表SQL語句沒有執行成功。
4).PreparedStatement.addBatch(); executeBatch()方法:
將一組引數新增到此 PreparedStatement 物件的批處理命令中。快取sql語句,然後批量執行!
將一批命令提交給資料庫來執行,如果全部命令執行成功,則返回更新計陣列成的陣列。
2.4關於PreparedStatement.addBatch()方法

PreparedStatement最重要的addbatch()結構的使用.

1建立連結,(打電話撥號 )    
   Connection    connection =getConnection();
2.不自動 Commit (瓜子不是一個一個吃,全部剝開放桌子上,然後一口舔了)
connection.setAutoCommit(false);   
3.預編譯SQL語句,只編譯一回哦,效率高啊.(發明一個剝瓜子的方法,以後不要總想怎麼剝瓜子好.就這樣剝.)
PreparedStatement statement = connection.prepareStatement("INSERT INTO TABLEX VALUES(?, ?)");   
4.來一個剝一個,然後放桌子上
//記錄1
statement.setInt(1, 1); 
statement.setString(2, "Cujo"); 
statement.addBatch();   
//記錄2
statement.setInt(1, 2); 
statement.setString(2, "Fred"); 
statement.addBatch();   
//記錄3
statement.setInt(1, 3); 
statement.setString(2, "Mark"); 
statement.addBatch();   
//批量執行上面3條語句. 一口吞了,很爽
int [] counts = statement.executeBatch();   
//Commit it 嚥下去,到肚子(DB)裡面
connection.commit();

2.5. PreparedStatement和statement的比較
a.程式碼的可讀性和可維護性.
雖然用PreparedStatement來代替Statement會使程式碼多出幾行,但這樣的程式碼無論從可讀性還是可維護性上來說.都比直接用Statement的程式碼高很多檔次:
第一種方法:

stmt.executeUpdate("insert into tb_name (col1,col2,col2,col4) values ('"+var1+"','"+var2+"',"+var3+",'"+var4+"')");//stmt是Statement物件例項

第二種方法:


perstmt = con.prepareStatement("insert into tb_name (col1,col2,col2,col4) values (?,?,?,?)");
perstmt.setString(1,var1);
perstmt.setString(2,var2);
perstmt.setString(3,var3);
perstmt.setString(4,var4);
perstmt.executeUpdate(); //prestmt是 PreparedStatement 物件例項 

對於第一種方法.別說其他人去讀你的程式碼,就是你自己過一段時間再去讀,都會覺得傷心.
b.PreparedStatement盡最大可能提高效能.
語句在被DB的編譯器編譯後的執行程式碼被快取下來,那麼下次呼叫時只要是相同的預編譯語句就不需要編譯,只要將引數直接傳入編譯過的語句執行程式碼中(相當 於一個涵數)就會得到執行.這並不是說只有一個Connection中多次執行的預編譯語句被快取,而是對於整個DB中,只要預編譯的語句語法和快取中匹 配.那麼在任何時候就可以不需要再次編譯而可以直接執行.而statement的語句中,即使是相同一操作,而由於每次操作的資料不同所以使整個語句相匹 配的機會極小,幾乎不太可能匹配.比如:

insert into tb_name (col1,col2) values ('11','22');
insert into tb_name (col1,col2) values ('11','23');

即使是相同操作但因為資料內容不一樣,所以整個個語句本身不能匹配,沒有快取語句的意義.事實是沒有資料庫會對普通語句編譯後的執行程式碼快取.

當然並不是所以預編譯語句都一定會被快取,資料庫本身會用一種策略,比如使用頻度等因素來決定什麼時候不再快取已有的預編譯結果.以儲存有更多的空間儲存新的預編譯語句.

c.安全性.

可以有效的防止一些sql注入攻擊
如果你是做Java web應用開發的,那麼必須熟悉那聲名狼藉的SQL注入式攻擊。去年Sony就遭受了SQL注入攻擊,被盜用了一些Sony play station(PS機)使用者的資料。在SQL注入攻擊裡,惡意使用者通過SQL元資料繫結輸入,比如:某個網站的登入驗證SQL查詢程式碼為:

strSQL = "SELECT * FROM users WHERE name = '" + userName + "' and pw = '"+ passWord +"';"

惡意填入:

userName = "1' OR '1'='1";
passWord = "1' OR '1'='1";

那麼最終SQL語句變成了:

strSQL = "SELECT * FROM users WHERE name = '1' OR '1'='1' and pw = '1' OR '1'='1';"

因為WHERE條件恆為真,這就相當於執行:strSQL = "SELECT * FROM users;"
更有甚者:
把[’;drop table tb_name;]作為varpasswd傳入進來,則:

select * from tb_name = '隨意' and passwd = '';drop table tb_name;

有些資料庫是不會讓你成功的,但也有很多資料庫就可以使這些語句得到執行.

而如果你使用預編譯語句.你傳入的任何內容就不會和原來的語句發生任何匹配的關係.只要全使用預編譯語句,你就用不著對傳入的資料做任何過慮.而如果使用普通的statement,有可能要對drop,;等做費盡心機的判斷和過慮.
2.6 PreparedStatement的侷限性
儘管PreparedStatement非常實用,但是它仍有一定的限制。

  1. 為了防止SQL注入攻擊,PreparedStatement不允許一個佔位符(?)有多個值,在執行有IN子句查詢的時候這個問題變得棘手起來。下面這個SQL查詢使用PreparedStatement就不會返回任何結果
SELECT * FROM loan WHERE loan_type IN (?)
preparedSatement.setString(1, "'personal loan', 'home loan', 'gold loan'");

為了更好的理解,建議學習PreparedStatement示例程式碼 。

3. CallableStatement物件
CallableStatement繼承自PreparedStatement.
類似Connection物件建立Statement和PreparedStatement物件一樣,它還可以使用同樣的方式建立CallableStatement物件,該物件將用於執行對資料庫儲存過程的呼叫。
3.1. 建立CallableStatement物件
假設需要執行以下Oracle儲存過程 -

CREATE OR REPLACE PROCEDURE getEmpName 
   (EMP_ID IN NUMBER, EMP_FIRST OUT VARCHAR) AS
BEGIN
   SELECT first INTO EMP_FIRST
   FROM Employees
   WHERE ID = EMP_ID;
END;

注意:上面的儲存過程是針對Oracle編寫的,但是如果您使用MySQL資料庫,可使用以下方式來編寫MySQL相同的儲存過程,如下在EMP資料庫中建立它 -

DELIMITER $$

DROP PROCEDURE IF EXISTS `EMP`.`getEmpName` $$
CREATE PROCEDURE `EMP`.`getEmpName` 
   (IN EMP_ID INT, OUT EMP_FIRST VARCHAR(255))
BEGIN
   SELECT first INTO EMP_FIRST
   FROM Employees
   WHERE ID = EMP_ID;
END $$

DELIMITER ;

存在三種類型的引數:IN,OUT和INOUT。 PreparedStatement物件只使用IN引數。CallableStatement物件可以使用上面三個引數型別。
以下是上面三種類型引數的定義 -
在這裡插入圖片描述
3.2關閉CallableStatement物件
就像關閉其他Statement物件一樣,由於同樣的原因(節省資料庫系統資源),還應該關閉CallableStatement物件。
簡單的呼叫close()方法將執行關閉CallableStatement物件。 如果先關閉Connection物件,它也會關閉CallableStatement物件。 但是,應該始終顯式關閉CallableStatement物件,以確保按正確順序的清理資源。

CallableStatement cstmt = null;
try {
   String SQL = "{call getEmpName (?, ?)}";
   cstmt = conn.prepareCall (SQL);
   . . .
}
catch (SQLException e) {
   . . .
}
finally {
   cstmt.close();
}

為了更好的理解,建議參考學習Callable示例程式碼。

參考文件:
https://www.yiibai.com/jdbc/jdbc-statements.html>
http://www.importnew.com/5006.html
http://www.cnblogs.com/zhizhuwang/p/3513372.html
https://blog.csdn.net/weixin_36586564/article/details/71213206?utm_source=blogxgwz0