JDBC API 4.2(七):CallableStatement 接口源碼分析

1、簡述

CallableStatement 是用於執行SQL存儲過程的。JDBC API提供了存儲過程SQL轉義語法,該語法允許所有RDBMS以標準方式調用存儲過程。

Connection 接口提供了 prepareCall(String SQL)方法來創建 CallableStatement 對象,以調用數據庫存儲過程。 CallableStatement對象提供了用於設置其IN和OUT參數的方法,以及用於執行對存儲過程的調用的方法。

2、類圖

下面的類圖顯示了 CallableStatement 接口的繼承關係:

CallableStatement 使用從 PreparedStatement繼承的set方法設置IN參數值。 在執行存儲過程之前,必須先註冊所有OUT參數的類型。 它們的值在執行後通過此處提供的get方法檢索。

一個CallableStatement可以返回一個ResultSet對象或多個ResultSet對象。 使用從Statement繼承的操作來處理多個ResultSet對象。

在這裏插入圖片描述

3、示例

3.1、單個結果集

返回單個結果集的存儲過程

DELIMITER $$
USE `lkf_db`$$
CREATE PROCEDURE `proc_single_select` ()
BEGIN
 SELECT * FROM users;
END$$
DELIMITER ;

以下程序演示瞭如何調用 proc_single_select() 存儲過程並生成單個結果集。

 //數據庫連接地址
    private final String jdbcUrl = "jdbc:mysql://localhost:3306/lkf_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT";
    //用戶名
    private final String username = "root";
    //密碼
    private final String password = "root";
    //調用返回單個結果集存儲過程語句
    private final String proc_single_select_sql = "call proc_single_select()";
    //調用返回多個結果集存儲過程語句
    private final String proc_multi_select_sql = "call proc_multi_select()";

    public static void main(String[] args) {
        CallableStatementDemo callableStatementDemo = new CallableStatementDemo();
        //返回單個結果集
        callableStatementDemo.singleResultSet();
    }

    /**
     * 返回單個結果集
     */
    public void singleResultSet() {
        try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password);
             CallableStatement stmt = conn.prepareCall(proc_single_select_sql);
             ResultSet rs = stmt.executeQuery()) {
            while (rs.next()) {
                System.out.println("ID = " + rs.getInt(1) + ", NAME = " + rs.getString(2) + ", Email = " +
                        rs.getString(3) + ", Country = " + rs.getString(4) + ", Password = " + rs.getString(5));
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

結果:

ID = 1, NAME = 張三, Email = [email protected], Country = china, Password = 123456
ID = 2, NAME = 李四, Email = [email protected], Country = china, Password = 123456

3.2、多個結果集

返回多個結果集的存儲過程

DELIMITER $$
USE `lkf_db`$$
CREATE PROCEDURE `proc_multi_select` ()
BEGIN
    #查詢 id 爲 1,按名字去重後的名字
    SELECT DISTINCT NAME FROM users WHERE id = 1;
    #按郵箱去重,並獲取去重後的所有郵箱
    SELECT DISTINCT email FROM users;
    #根據id統計用戶數量
    SELECT COUNT(id) AS users_count FROM users;
END$$
DELIMITER ;

以下程序演示瞭如何調用 proc_multi_select() 存儲過程並生成多個結果集。

 //數據庫連接地址
    private final String jdbcUrl = "jdbc:mysql://localhost:3306/lkf_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT";
    //用戶名
    private final String username = "root";
    //密碼
    private final String password = "root";
    //調用返回單個結果集存儲過程語句
    private final String proc_single_select_sql = "call proc_single_select()";
    //調用返回多個結果集存儲過程語句
    private final String proc_multi_select_sql = "call proc_multi_select()";

    public static void main(String[] args) {
        CallableStatementDemo callableStatementDemo = new CallableStatementDemo();
        //返回單個結果集
//        callableStatementDemo.singleResultSet();
        //返回多個結果集
        callableStatementDemo.multipleResultSet();
    }

    /**
     * 返回多個結果集
     */
    public void multipleResultSet() {
        try (Connection conn = DriverManager.getConnection(jdbcUrl, username, password);
             CallableStatement stmt = conn.prepareCall(proc_multi_select_sql);) {
            //執行存儲過程
            boolean hasRs = stmt.execute();
            System.out.println();
            // 解析名字的結果集
            if (hasRs) {
                try (ResultSet rs = stmt.getResultSet()) {
                    while (rs.next()) {
                        System.out.println("NAME = " + rs.getString(1));
                    }
                }
            }

            // 解析郵箱的結果集
            if (stmt.getMoreResults()) {
                try (ResultSet rs = stmt.getResultSet()) {
                    if (rs.next()) {
                        System.out.println("Email = " + rs.getString(1));
                    }
                }
            }

            // 獲取統計的用戶數量
            if (stmt.getMoreResults()) {
                try (ResultSet rs = stmt.getResultSet()) {
                    if (rs.next()) {
                        System.out.println("Users count = " + rs.getInt(1));
                    }
                }
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

結果:

NAME = 張三
Email = [email protected]
Users count = 2

3.3、帶參數的存儲過程

存在三種類型的參數:IN,OUT和INOUT。

PreparedStatement對象只使用IN參數。 CallableStatement對象可以使用上面三種類型參數。

參數 描述
IN 表示調用者傳入值(傳入值可以是字面量或變量)。創建SQL語句時其參數值是未知的。 使用setXXX()方法將值綁定到IN參數。
OUT 表示過程向調用者傳出值(可以返回多個值)(傳出值只能是變量)。可以使用getXXX()方法從OUT參數中檢索值。
INOUT 既表示調用者向過程傳入值,又表示過程向調用者傳出值(值只能是變量)。使用setXXX()方法綁定變量並使用getXXX()方法檢索值。

使用 IN 參數,只需遵循適用 於PreparedStatement 對象的相同規則,與綁定的Java數據類型相對應的setXXX()方法。

使用 OUT 和 INOUT 參數時,必須使用 CallableStatement 對象的方法 registerOutParameter()。

registerOutParameter() 方法將JDBC數據類型綁定到存儲過程預期返回的數據類型。
當調用存儲過程後,可以使用適當的 getXXX() 方法從OUT參數中檢索該值。 此方法將檢索到的SQL類型的值轉換爲Java 數據類型。

3.3.1、帶有輸入參數(IN)的存儲過程

DELIMITER $$
USE `lkf_db`$$
CREATE PROCEDURE `proc_getUserByName` (IN p_name VARCHAR(30))
BEGIN
 SELECT * FROM users WHERE NAME=p_name;
END$$
DELIMITER ;

以下示例將調用存儲過程 proc_getUserByName 並傳入參數,獲取對應的記錄信息。

 private final String proc_in_select_sql = "call proc_getUserByName(?)";
/**
     * 帶有輸入參數(IN)的存儲過程
     */
    public void procInParam() throws SQLException {
        Connection conn = null;
        CallableStatement stmt = null;
        ResultSet rs = null;
        try {
            //獲取連接對象
            conn = DriverManager.getConnection(jdbcUrl, username, password);
            //預編譯
            stmt = conn.prepareCall(proc_in_select_sql);
            //傳遞輸入參數
            stmt.setString(1, "張三");
            //執行存儲過程
            rs = stmt.executeQuery();
            while (rs.next()) {
                System.out.println("ID = " + rs.getInt(1) + ", NAME = " + rs.getString(2) + ", Email = " +
                        rs.getString(3) + ", Country = " + rs.getString(4) + ", Password = " + rs.getString(5));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (rs != null) {
                rs.close();
                stmt.close();
                conn.close();
            }
        }
    }

結果:

ID = 1, NAME = 張三, Email = [email protected], Country = china, Password = 123456

3.3.2、帶有輸出參數(OUT)的存儲過程

DELIMITER $$
USE `lkf_db`$$
CREATE PROCEDURE `proc_getUserNumByName` (IN p_name VARCHAR(30),OUT p_userNum INT)
BEGIN
 SELECT COUNT(id) FROM users WHERE NAME=p_name INTO p_userNum;
END$$
DELIMITER ;

以下示例,將調用具有輸入和輸出參數的存儲過程 proc_getUserNumByName 獲取滿足指定條件的用戶數量。

    private final String proc_out_select_sql = "call proc_getUserNumByName(?,?)";
  /**
     * 帶有輸出參數(OUT)的存儲過程
     */
    public void procOutParam() throws SQLException {
        Connection conn = null;
        CallableStatement stmt = null;
        ResultSet rs = null;
        try {
            //獲取連接對象
            conn = DriverManager.getConnection(jdbcUrl, username, password);
            //預編譯
            stmt = conn.prepareCall(proc_out_select_sql);
            //傳遞輸入參數
            stmt.setString(1, "張三");
            //設置輸出參數(註冊輸出參數)
            /**
             * 參數一: 參數位置
             * 參數二: 存儲過程中的輸出參數的jdbc類型
             */
            stmt.registerOutParameter(2, Types.INTEGER);
            //執行存儲過程
            rs = stmt.executeQuery();
            /**
             * 得到輸出參數的值
             * 索引值: 預編譯sql中的輸出參數的位置
             * getXX方法專門用於獲取存儲過程中的輸出參數
             */
            Integer result = stmt.getInt(2);
            System.out.println(result);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (rs != null) {
                rs.close();
                stmt.close();
                conn.close();
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章