預存過程是存儲在數據庫中的預編譯 SQL 程序,優點:
DELIMITER //
CREATE PROCEDURE sp_emps_in_dept(IN dept_id INT)
BEGIN
-- 結果集 1:部門信息
SELECT department_name, location
FROM departments WHERE department_id = dept_id;
-- 結果集 2:員工列表
SELECT employee_id, first_name, last_name
FROM employees WHERE department_id = dept_id;
END //
DELIMITER ;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class StoredProcedureCaller {
public void callEmpsInDept(Connection conn, int deptId) throws SQLException {
String sql = "{CALL sp_emps_in_dept(?)}";
try (CallableStatement cstmt = conn.prepareCall(sql)) {
// 1. 綁定輸入參數
cstmt.setInt(1, deptId);
// 2. 執行
boolean hasResults = cstmt.execute();
// 3. 處理多結果集
int resultSetIndex = 0;
do {
if (hasResults) {
try (ResultSet rs = cstmt.getResultSet()) {
resultSetIndex++;
System.out.println("=== 結果集 " + resultSetIndex + " ===");
processResultSet(rs, resultSetIndex);
}
}
// 獲取下一個結果集
hasResults = cstmt.getMoreResults();
} while (hasResults || cstmt.getUpdateCount() != -1);
// 4. 獲取輸出參數(如有)
// cstmt.registerOutParameter(2, Types.INTEGER); // 需預先註冊
// int outParam = cstmt.getInt(2);
}
}
private void processResultSet(ResultSet rs, int index) throws SQLException {
ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();
// 打印列名
for (int i = 1; i <= columnCount; i++) {
System.out.printf("%-20s", meta.getColumnLabel(i));
}
System.out.println();
// 打印數據
while (rs.next()) {
for (int i = 1; i <= columnCount; i++) {
System.out.printf("%-20s", rs.getObject(i));
}
System.out.println();
}
}
}
| MySQL 類型 | JDBC Types | Java 類型 | Setter/Getter |
|---|---|---|---|
| INT | Types.INTEGER | int / Integer | setInt / getInt |
| BIGINT | Types.BIGINT | long / Long | setLong / getLong |
| VARCHAR | Types.VARCHAR | String | setString / getString |
| DECIMAL | Types.DECIMAL | BigDecimal | setBigDecimal / getBigDecimal |
| DATE | Types.DATE | java.sql.Date | setDate / getDate |
| DATETIME | Types.TIMESTAMP | LocalDateTime / Timestamp | setObject / getObject |
| JSON | Types.OTHER | String / JsonNode | setString / getString |
-- MySQL 預存過程帶輸出參數
CREATE PROCEDURE sp_calc_stats(IN dept_id INT, OUT emp_count INT, OUT avg_salary DECIMAL(10,2))
BEGIN
SELECT COUNT(*), AVG(salary) INTO emp_count, avg_salary
FROM employees WHERE department_id = dept_id;
END
// Java 調用
String sql = "{CALL sp_calc_stats(?, ?, ?)}";
try (CallableStatement cstmt = conn.prepareCall(sql)) {
cstmt.setInt(1, deptId);
cstmt.registerOutParameter(2, Types.INTEGER);
cstmt.registerOutParameter(3, Types.DECIMAL);
cstmt.execute();
int count = cstmt.getInt(2);
BigDecimal avg = cstmt.getBigDecimal(3);
System.out.println("員工數: " + count + ", 平均薪資: " + avg);
}
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
@Procedure(name = "sp_emps_in_dept")
List<EmployeeProjection> callEmpsInDept(@Param("dept_id") Integer deptId);
// 輸出參數
@Procedure
Map<String, Object> calcStats(@Param("dept_id") Integer deptId);
}
<select id="callEmpsInDept" statementType="CALLABLE" resultMap="empResultMap">
{CALL sp_emps_in_dept(#{deptId, mode=IN, jdbcType=INTEGER})}
</select>
<select id="calcStats" statementType="CALLABLE">
{CALL sp_calc_stats(
#{deptId, mode=IN, jdbcType=INTEGER},
#{empCount, mode=OUT, jdbcType=INTEGER},
#{avgSalary, mode=OUT, jdbcType=DECIMAL}
)}
</select>
maxLifetime 避免連接失效SQLException 解析錯誤碼(MySQL 錯誤碼 13xx 為預存過程相關)CALL sp_emps_in_dept(10);log4j.logger.java.sql=DEBUGSHOW PROCESSLIST 觀察長時間運行的預存過程