JDBC調用架構
Connection → prepareCall("{call proc_name(?,?)}") → CallableStatement
│
├─ setXxx(index, value) -- 綁定IN/IN OUT參數
├─ registerOutParameter(index, Types.XXX) -- 註冊OUT參數類型
├─ execute() -- 執行
└─ getXxx(index) -- 獲取OUT/IN OUT參數返回值
模式一:無返回參數(純執行)
import java.sql.*;
public class CallProcNoReturn {
public static void main(String[] args) throws Exception {
// 1. 加載驅動 (JDBC 4.0+ 可省略 Class.forName)
// Class.forName("oracle.jdbc.driver.OracleDriver");
// 2. 獲取連接
String url = "jdbc:oracle:thin:@dbhost:1521:ORCL";
String user = "app_user";
String pass = "AppPass#123";
Connection conn = DriverManager.getConnection(url, user, pass);
// 3. 準備調用:pro1(IN p_id NUMBER)
CallableStatement cs = conn.prepareCall("{call pro1(?)}");
// 4. 綁定輸入參數
cs.setInt(1, 188);
// 5. 執行
cs.execute();
System.out.println("Procedure executed.");
// 6. 關閉資源
cs.close();
conn.close();
}
}
模式二:帶返回參數(OUT/IN OUT)
import java.sql.*;
public class CallProcWithReturn {
public static void main(String[] args) throws Exception {
Connection conn = DriverManager.getConnection(
"jdbc:oracle:thin:@dbhost:1521:ORCL", "app_user", "AppPass#123");
// pro2(IN p_id NUMBER, OUT p_result NUMBER)
CallableStatement cs = conn.prepareCall("{call pro2(?, ?)}");
// 綁定輸入
cs.setInt(1, 188);
// 關鍵:註冊輸出參數類型 (索引從1開始)
cs.registerOutParameter(2, Types.INTEGER);
// 執行
cs.execute();
// 獲取返回值
int result = cs.getInt(2);
System.out.println("Return value: " + result);
cs.close();
conn.close();
}
}
Oracle端存儲過程定義對照
-- 模式一對應
CREATE OR REPLACE PROCEDURE pro1(p_id IN NUMBER) IS
BEGIN
INSERT INTO log_table(id, created) VALUES (p_id, SYSDATE);
COMMIT;
END;
/
-- 模式二對應
CREATE OR REPLACE PROCEDURE pro2(p_id IN NUMBER, p_result OUT NUMBER) IS
BEGIN
SELECT COUNT(*) INTO p_result FROM emp WHERE deptno = p_id;
END;
/
進階場景處理
| 場景 | Java處理 | Oracle定義 |
| IN OUT參數 | cs.setInt(1, val); cs.registerOutParameter(1, Types.INTEGER); 同一索引綁定+註冊 | p_val IN OUT NUMBER |
| 游標返回 (REF CURSOR) | cs.registerOutParameter(1, OracleTypes.CURSOR); ResultSet rs = (ResultSet) cs.getObject(1); | p_rc OUT SYS_REFCURSOR |
| 數組/集合參數 | ArrayDescriptor desc = ArrayDescriptor.createDescriptor("NUM_TAB", conn); ARRAY arr = new ARRAY(desc, conn, javaArray); cs.setArray(1, arr); | TYPE num_tab IS TABLE OF NUMBER; p_arr IN num_tab |
| 異常傳播 | 捕獲SQLException,解析getErrorCode()/getMessage() | RAISE_APPLICATION_ERROR(-20001, 'msg') |
連接池與資源管理最佳實踐
// 使用 HikariCP / Druid / Tomcat JDBC Pool
// 關鍵配置:
// maximumPoolSize=20
// connectionTimeout=30000
// idleTimeout=600000
// maxLifetime=1800000
// connectionTestQuery=SELECT 1 FROM DUAL
// 業務代碼模板
try (Connection conn = dataSource.getConnection();
CallableStatement cs = conn.prepareCall("{call pro2(?, ?)}")) {
cs.setInt(1, 188);
cs.registerOutParameter(2, Types.INTEGER);
cs.execute();
int result = cs.getInt(2);
return result;
} catch (SQLException e) {
log.error("Call pro2 failed", e);
throw new ServiceException("DB_ERROR", e);
}
關鍵提示:
• CallableStatement參數索引從1開始,與JDBC標準一致。
• registerOutParameter必須在execute()前調用,且類型需與Oracle定義精確匹配(Types.INTEGER對應NUMBER、Types.VARCHAR對應VARCHAR2、OracleTypes.CURSOR對應SYS_REFCURSOR)。
• 12c+支援Boolean類型參數,JDBC驅動需ojdbc8.jar+oracle.jdbc.OracleTypes.BOOLEAN。
• 生產環境禁用DriverManager直接獲取連接,強制使用連接池,配合try-with-resources確保資源釋放。
• 高併發場景考慮Statement Caching(conn.setImplicitCachingEnabled(true))減少準備開銷。