JDBC 預存程序與事務控制實戰指南
一、MySQL 預存程序基礎
1. 常用指令
-- 查看現有預存程序
SHOW PROCEDURE STATUS \G
-- 建立預存程序 (修改結束符避免衝突)
DELIMITER $
CREATE PROCEDURE proc_name(參數列表)
BEGIN
-- 程序體:可使用 IF/ELSE、WHILE、變數宣告等
DECLARE 變數名 型別;
SET 變數名 = 值;
SELECT 變數名; -- 輸出/除錯
INSERT ...; -- DML 操作
END$
DELIMITER ;
-- 呼叫預存程序
CALL proc_name();
CALL proc_name(參數);
-- 刪除預存程序
DROP PROCEDURE proc_name;
2. 範例:計算 1+2+...+n 並寫入表
DELIMITER $
CREATE PROCEDURE calc_sum(IN p_name CHAR(10), IN p_n SMALLINT)
BEGIN
DECLARE i INT DEFAULT 1;
DECLARE s INT DEFAULT 0;
WHILE i <= p_n DO
SET s = s + i;
SET i = i + 1;
END WHILE;
INSERT INTO test(name, age, score) VALUES (p_name, 0, s);
END$
DELIMITER ;
二、JDBC 呼叫預存程序:CallableStatement
| 方法 | 說明 |
conn.prepareCall("{call proc(?,?)}") | 建立 CallableStatement,大括號為 JDBC Escape 語法 |
cs.setString(1, "值") | 綁定 IN 參數 (索引從 1 起) |
cs.registerOutParameter(2, Types.INTEGER) | 註冊 OUT 參數型別 |
cs.execute() | 執行預存程序 |
cs.getInt(2) | 取得 OUT 參數值 |
完整範例
package com.example.jdbc;
import java.sql.*;
public class JdbcCallableDemo {
private static final String URL = "jdbc:mysql://localhost:3306/jdbc_test_db";
private static final String USER = "root", PASS = "111111";
public static void main(String[] args) {
try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
CallableStatement cs = conn.prepareCall("{call calc_sum(?, ?)}")) {
cs.setString(1, "proc_call"); // p_name
cs.setInt(2, 100); // p_n
cs.execute();
System.out.println("預存程序執行完成,結果已寫入 test 表");
} catch (SQLException e) { e.printStackTrace(); }
}
}
三、JDBC 事務控制核心 API
| 方法 | 說明 |
conn.setAutoCommit(false) | 關閉自動提交,開啟手動事務控制 |
conn.commit() | 提交事務,所有操作持久化 |
conn.rollback() | 回滾事務,撤銷所有未提交操作 |
conn.setTransactionIsolation(level) | 設定隔離級別 (READ_COMMITTED 等) |
stmt.addBatch(sql) | 將 SQL 加入批次 |
stmt.executeBatch() | 批次執行,回傳各條影響列數陣列 |
四、事務四大特性 (ACID) 速查
- 原子性:全做或全不做
- 一致性:事務前後資料庫滿足所有約束
- 隔離性:並發事務互不干擾
- 持久性:提交後永久生效
五、事務處理完整範本:批次插入 + 手動提交/回滾
package com.example.jdbc;
import java.sql.*;
public class JdbcTransactionDemo {
private static final String URL = "jdbc:mysql://localhost:3306/jdbc_test_db";
private static final String USER = "root", PASS = "896013";
public static void main(String[] args) {
try (Connection conn = DriverManager.getConnection(URL, USER, PASS)) {
conn.setAutoCommit(false); // 1. 關閉自動提交
try (Statement stmt = conn.createStatement()) {
stmt.addBatch("INSERT INTO test(name,age,score) VALUES('haha',10,33)");
stmt.addBatch("INSERT INTO test(name,age,score) VALUES('heihei',11,44)");
stmt.addBatch("INSERT INTO test(name,age,score) VALUES('xixi',14,55)");
int[] results = stmt.executeBatch(); // 2. 批次執行
System.out.println("批次執行完成,影響列數: " + results.length);
conn.commit(); // 3. 全部成功 → 提交
System.out.println("事務提交成功");
} catch (BatchUpdateException e) {
System.err.println("批次執行失敗,執行回滾");
conn.rollback(); // 4. 任一失敗 → 回滾
}
conn.setAutoCommit(true); // 恢復預設
} catch (SQLException e) { e.printStackTrace(); }
}
}
六、驗證事務行為
| 場景 | 程式碼修改 | 資料庫結果 |
| 正常提交 | 保留 conn.commit() | 3 筆資料全部寫入 |
| 模擬失敗回滾 | 註解 conn.commit() 或拋出例外 | 0 筆資料寫入 (全數回滾) |
七、MySQL 事務注意事項
- 引擎支援:僅
InnoDB 支援事務,MyISAM 不支援
- 隱式提交:DDL (
CREATE, DROP, ALTER) 會隱式提交當前事務
- 鎖等待超時:
innodb_lock_wait_timeout (預設 50 秒) 超時自動回滾當前陳述式
- 死鎖:InnoDB 自動偵測並回滾其中一個事務
八、最佳實踐總結
- 業務邏輯方法接收
Connection 參數,確保同一事務共用連線
- 使用
try-with-resources 管理 Statement/ResultSet,Connection 手動控制關閉時機
catch 區塊務必呼叫 rollback(),finally 關閉連線
- 批次操作搭配
addBatch/executeBatch 大幅提升寫入效能
- 長事務會持有鎖資源,盡量縮短事務範圍,避免併發衝突
結語:預存程序將商業邏輯下沉至資料庫層,減少網路往返;事務控制保障多步操作的原子性。兩者結合是企業級應用資料一致性的核心保障。掌握 CallableStatement 與手動提交/回滾模式,即可應對大多數複雜業務場景。