首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JDBC 進階:CallableStatement 調用存儲過程與 SQL Escape 語法詳解

     編輯:Java 相關知識

前言

在企業級應用中,將複雜業務邏輯下沉到數據庫存儲過程,能減少網絡交互、利用數據庫引擎優化、封裝核心算法。JDBC 提供 CallableStatement 專門用於調用存儲過程。同時,JDBC 定義了 SQL Escape 語法,屏蔽不同數據庫在日期時間、標量函數、外連接、存儲過程調用等方面的方言差異,實現「寫一次,跑多庫」。

一、CallableStatement 核心用法

1. 存儲過程定義(MySQL 示例)

DELIMITER $$
CREATE PROCEDURE `get_emp_name`(
    IN  p_emp_id INT,
    OUT p_emp_name VARCHAR(100)
)
BEGIN
    SELECT first_name INTO p_emp_name FROM employees WHERE id = p_emp_id;
END$$
DELIMITER ;

2. 參數模式對照表

模式說明JDBC 方法
IN輸入參數,調用前綁定值setXxx(index, value)
OUT輸出參數,調用後獲取值registerOutParameter(index, sqlType) → getXxx(index)
INOUT雙向參數,調用前綁定、調用後獲取setXxx + registerOutParameter + getXxx

3. 標準調用流程

public String getEmployeeName(Connection conn, int empId) throws SQLException {
    String sql = "{call get_emp_name(?, ?)}"; // 標準 Escape 語法
    try (CallableStatement cstmt = conn.prepareCall(sql)) {
        // 1. 綁定 IN 參數
        cstmt.setInt(1, empId);
        // 2. 註冊 OUT 參數類型
        cstmt.registerOutParameter(2, Types.VARCHAR);
        // 3. 執行
        cstmt.execute();
        // 4. 獲取 OUT 參數
        return cstmt.getString(2);
    } // 自動關閉 cstmt
}

4. 關鍵注意點

  • 參數索引從 1 開始,與 PreparedStatement 一致。
  • OUT/INOUT 必須 registerOutParameter,否則拋出 SQLException。
  • 多結果集:若存儲過程返回多個 ResultSet,需調用 getResultSet() → 處理 → getMoreResults() 循環獲取。
  • 事務控制:默認自動提交,若存儲過程涉及多表寫入,建議在 Java 端開啟事務 conn.setAutoCommit(false),調用後 commit/rollback。

二、JDBC SQL Escape 語法詳解

Escape 語法格式:{keyword 'parameters'}。驅動程序解析後翻譯為目標數據庫原生語法。

1. 日期時間字面量:d、t、ts

關鍵字格式示例對應類型
dyyyy-MM-dd{d '2024-03-15'}DATE
tHH:mm:ss{t '14:30:00'}TIME
tsyyyy-MM-dd HH:mm:ss{ts '2024-03-15 14:30:00'}TIMESTAMP
// 插入日期示例
String sql = "INSERT INTO events (id, name, event_date) VALUES (1, 'Launch', {d '2024-01-01'})";
stmt.executeUpdate(sql);

2. 標量函數:fn

調用數據庫內置函數(如字符串、數學、轉換函數),驅動自動映射到目標 DB 函數名。

// 字符串長度
{fn length('Hello World')}  -- 返回 11
-- MySQL: LENGTH(), Oracle: LENGTH(), SQL Server: LEN()

-- 大小寫轉換
{fn upper('abc')}  -- 'ABC'
{fn lower('ABC')}  -- 'abc'

-- 截取子串(注意:參數從 1 開始)
{fn substring('Hello', 2, 3)}  -- 'ell'

-- 當前時間(數據庫服務端時間)
{fn now()}  或  {fn curdate()} / {fn curtime()}

3. LIKE 轉義字符:escape

-- 查詢包含字面量 '%' 的字符串,指定 '\' 為轉義字符
SELECT * FROM symbols WHERE symbol LIKE '\%' {escape '\'}
-- Java 字符串中需雙重轉義
String sql = "SELECT * FROM symbols WHERE symbol LIKE '\\\\%' {escape '\\\\'}";

4. 存儲過程調用:call

-- 無返回值
{call my_proc(?, ?)}

-- 有返回值(函數風格)
{? = call my_func(?, ?)}

5. 外連接:oj(舊語法,現代建議直接寫 ANSI SQL)

{oj table1 LEFT OUTER JOIN table2 ON table1.id = table2.fk}
-- 等價於標準 SQL
SELECT * FROM table1 LEFT JOIN table2 ON table1.id = table2.fk

三、完整實戰示例:批量調用存儲過程

public void batchUpdateSalaries(Connection conn, List<SalaryAdjustment> adjustments) throws SQLException {
    String sql = "{call adjust_salary(?, ?, ?)}"; // IN emp_id, IN percent, OUT new_salary
    conn.setAutoCommit(false);
    try (CallableStatement cstmt = conn.prepareCall(sql)) {
        for (SalaryAdjustment adj : adjustments) {
            cstmt.setInt(1, adj.getEmpId());
            cstmt.setDouble(2, adj.getPercent());
            cstmt.registerOutParameter(3, Types.DECIMAL);
            cstmt.addBatch(); // 批量處理
        }
        int[] results = cstmt.executeBatch(); // 執行批量
        for (int i = 0; i < results.length; i++) {
            if (results[i] == Statement.SUCCESS_NO_INFO) {
                BigDecimal newSal = cstmt.getBigDecimal(3);
                adjustments.get(i).setNewSalary(newSal);
            }
        }
        conn.commit();
    } catch (BatchUpdateException e) {
        conn.rollback();
        throw e;
    } finally {
        conn.setAutoCommit(true);
    }
}

四、避坑指南

  1. 驅動兼容性:確保使用的 JDBC 驅動版本支持 Escape 語法(主流驅動均支持)。
  2. 參數類型匹配:registerOutParameter 的 SQL 類型(Types.VARCHAR 等)必須與數據庫定義一致,否則獲取時可能報錯或數據截斷。
  3. 游標/結果集:Oracle 存儲過程常用 SYS_REFCURSOR 返回結果集,需註冊為 OracleTypes.CURSOR,獲取後轉型 ResultSet 處理。
  4. 性能考量:頻繁調用簡單存儲過程的開銷可能大於單條 SQL,權衡網絡往返 vs 數據庫 CPU。

總結

CallableStatement 是 JDBC 調用存儲過程的標準接口,配合 IN/OUT/INOUT 參數模式實現雙向數據交換。JDBC Escape 語法為日期時間、標量函數、存儲過程調用提供了跨數據庫的統一寫法,降低遷移成本。實戰中務必注意資源關閉(try-with-resources)、事務邊界、批量執行優化,以及驅動對特定 Escape 關鍵字的支持程度。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved