在前面的一篇文章中,我們學習使用Statement、PreparedStatement來完成對數據表的增刪改查。而存儲過程作爲數據庫的重要組成部分(痛點,當時學的時候頭髮都掉了好幾根😭),那JDBC是如何執行存儲過程呢?今天我們就來看看如何使用CallableStatement接口來執行存儲過程。(本文使用的數據庫爲MySQL)。
興奮、緊張,迫不及待,又要學新知識了。
1.存儲過程簡介
在開始講如何執行存儲過程之前,我們先來簡單的看下存儲過程的相關概念。
存儲過程(Stored Procedure)是在大型數據庫系統中,一組爲了完成特定功能的SQL 語句集,它存儲在數據庫中,一次編譯後永久有效,用戶通過指定存儲過程的名字並給出參數(如果該存儲過程帶有參數)來執行它。存儲過程是數據庫中的一個重要對象。
上面的釋義爲百度百科上的詞條,講的也非常清晰了。我們在開發的過程中,經常在一個功能中,會執行多條SQL語句,這時就可以將這一組SQL語句封裝在一個存儲過程中,在應用端就可以通過調用存儲過程來獲取對應的結果,而不用執行許多SQL了。
這裏說明一件事,關於是否應使用存儲過程的爭論由來已久,我寫這篇文章也沒有任何誘導你的想法,是否使用存儲過程還需你自行評估。
這裏簡單的講下存儲過程的優缺點:
優點:1.執行速度快:存儲過程預先創建好的,並且預先編譯了,省去了每條SQL編譯的時間;2.安全性高:可以避免SQL注入問題,避免暴露表結構和字段。
缺點:1.對數據庫依賴性比較大,數據庫遷移改動會非常大;2.不適用於數據庫集羣,存儲過程依賴於具體的庫來實現的,如果數據是分佈存儲在多個庫中,存儲過程就很難處理了;3.可維護性差、可讀性差:程序員無法直接看到代碼,業務發生變更時不利於快速開發。
2.CallableStatement接口
我們來看一張圖:
從上圖可以看到,CallableStatement接口繼承(extends
)了PreparedStatement接口,也就是說,CallableStatement接口不僅可以執行存儲過程,還可以使用PreparedStatement接口提供方法的執行DQL、DDL和DML語句。
存儲過程可能會包含入參(IN),出參(OUT),出入參(INOUT,既可做入參又可做出參),執行的結果也會返回int或是ResultSet,下面我們一起來看下如何使用CallableStatement來執行存儲過程。
3.執行無參的存儲過程
環境搭建請參考前面的文章,這裏數據庫中有一個users表,我們就簡單的操作這張表來演示存儲過程的使用。
首先我們創建一個無參的存儲過程,SQL如下,邏輯很簡單,就是查詢users表中的所有用戶信息。
CREATE PROCEDURE SelectAllUsers()
BEGIN
SELECT * FROM users;
END;
下面我們來看下JDBC是怎麼執行無參的存儲過程的,代碼如下,其中的JDBCUtil代碼參考上文:
/**
* 調用無參的存儲過程,返回users表中的所有數據
*
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public List<User> callNoParamProcedure() throws ClassNotFoundException, SQLException {
Connection connection = null;
CallableStatement statement = null;
ResultSet resultSet = null;
try {
// 獲取數據庫連接
connection = JDBCUtil.getConnection();
// 構建Sql
String sql = "{ call SelectAllUsers() }";
// 創建CallableStatement對象
statement = connection.prepareCall(sql);
// 執行存儲過程
resultSet = statement.executeQuery();
// 迭代獲取所有的用戶
while (resultSet.next()) {
// new一個User實例
User user = new User();
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
user.setEmail(resultSet.getString("email"));
user.setBirthday(resultSet.getDate("birthday"));
// 輸出用戶信息
System.out.println("獲取的用戶信息爲:" + user);
}
return userList;
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
}
上述代碼執行後的運行結果如下:
4.執行有入參的存儲過程
上面我們講了,存儲過程可能會包含IN、OUT、INOUT參數,這裏我們需要注意下,當存儲過程中有OUT參數事,在執行存儲過程前需要通過CallableStatement中的registerOutParameter
方法先註冊。
這裏創建如下存儲過程:
CREATE PROCEDURE SelectNameByIdWithOut(IN in_id int, OUT out_name CHAR(50))
BEGIN
SELECT `name` into out_name from users where id = in_id;
SELECT * FROM users WHERE id = in_id;
END;
代碼如下,:
**
* 調用含參的存儲過程, 返回users表中id對應的數據
*
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public void callInOutParamProcedure(int id) throws ClassNotFoundException, SQLException {
Connection connection = null;
CallableStatement statement = null;
ResultSet resultSet = null;
try {
// 獲取數據庫連接
connection = JDBCUtil.getConnection();
// 構建Sql
String sql = "{call SelectNameByIdWithParam(?,?)}";
// 創建CallableStatement對象
statement = connection.prepareCall(sql);
// 設置IN參數的值
statement.setInt(1, id);
// 註冊OUT參數
statement.registerOutParameter(2, Types.VARCHAR);
// 執行存儲過程
resultSet = statement.executeQuery();
// 獲取上面註冊的OUT參數,這裏的index和入參順序一致
System.out.println("Out參數返回的結果爲:" + statement.getString(2));
// 迭代獲取用戶
while (resultSet.next()) {
User user = new User();
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
user.setEmail(resultSet.getString("email"));
user.setBirthday(resultSet.getDate("birthday"));
// 輸出用戶信息
System.out.println("獲取的用戶信息爲:" + user);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
}
執行測試代碼callInOutParamProcedure(2)
的運行結果如下圖所示:
5.執行返回多個結果集的存儲過程
在上面兩個例子中,執行查詢語句,查詢的結果就是一個結果集,但是如果一個存儲過程中有多個查詢語句呢?我們在程序中要如何將所有的結果集都獲取到呢?
這裏我們創建如下存儲過程:
CREATE PROCEDURE SelectUserWithMlutiRs(IN in_id int, OUT out_name CHAR(50))
BEGIN
SELECT `name` into out_name from users where id = in_id;
SELECT * FROM users WHERE id = in_id;
SELECT * FROM users;
END;
我們現在mysql中執行此存儲過程(可以在WorkBench或者Navicat中,執行SQL即可,或者在MySQL客戶端中執行),代碼如下:
set @in_out='test-2';
CALL SelectUserWithMlutiRs(2, @in_out);
SELECT @in_out
上述SQL的執行結果如下,其中結果1、結果1(2)爲執行存儲過程返回的結果集,可以看到,SelectUserWithMlutiRs
返回了兩個結果集。
那我們在應用程序中又要怎麼獲取呢?
這裏我們需要通過調Statement
對象中的getMoreResults
方法切換到下一個結果集,並通過getResult
方法獲取。我們來看下代碼:
/**
* 調用含參的存儲過程, 返回users表中id對應的數據
*
* @param id
* @throws ClassNotFoundException
* @throws SQLException
*/
public void callMultiRsProcedure(int id) throws ClassNotFoundException, SQLException {
Connection connection = null;
CallableStatement statement = null;
ResultSet resultSet = null;
try {
// 獲取數據庫連接
connection = JDBCUtil.getConnection();
// 構建Sql
String sql = "{ call SelectUserWithMlutiRs(?,?) }";
// 創建CallableStatement對象
statement = connection.prepareCall(sql);
// 設置IN參數的值
statement.setInt(1, id);
// 註冊OUT參數
statement.registerOutParameter(2, Types.VARCHAR);
// 執行存儲過程
resultSet = statement.executeQuery();
// 獲取上面註冊的OUT參數,這裏的index和入參順序一致
System.out.println("Out參數返回的結果爲:" + statement.getString(2));
int i = 1;
System.out.println("第" + i + "個結果集中的數據爲:");
while (resultSet.next()) {
User user = new User();
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
user.setEmail(resultSet.getString("email"));
user.setBirthday(resultSet.getDate("birthday"));
System.out.println("獲取的用戶信息爲:" + user);
}
i++;
// 判斷是否還有下一個結果集
// 注意,此處不能直接調用,需先獲取當前的ResultSet纔可調用,否則上個結果集會丟失
while (statement.getMoreResults()) {
resultSet = statement.getResultSet();
// 迭代獲取用戶
// 迭代獲取用戶
System.out.println("第" + i + "個結果集中的數據爲:");
// 判斷是否還有下一個結果集
// 注意,此處不能直接調用,需先獲取當前的ResultSet纔可調用,否則上個結果集會丟失
while (resultSet.next()) {
User user = new User();
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
user.setEmail(resultSet.getString("email"));
user.setBirthday(resultSet.getDate("birthday"));
System.out.println("獲取的用戶信息爲:" + user);
}
i++;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
}
執行測試代碼callMultiRsProcedure(3)
的運行結果如下如所示:
6.總結
本文是對如何使用CallableStatement接口調用存儲過程的簡單示例。存儲過程因爲其自身優缺點非常明顯,具體是否需要使用還需自己進行評估。
參考閱讀:
又到了分隔線以下,本文到此就結束了,本文內容全部都是由博主自己進行整理並結合自身的理解進行總結,如果有什麼錯誤,還請批評指正。
Java web這一專欄會是一個系列博客,喜歡的話可以持續關注,如果本文對你有所幫助,還請還請點贊、評論加關注。
有任何疑問,可以評論區留言。