企業級應用常將複雜業務邏輯封裝在數據庫存儲過程中,Spring Data JPA 提供了基於註解的優雅調用方式,避免手寫 JDBC 樣板代碼。
-- Oracle 示例
CREATE OR REPLACE PACKAGE test_pkg AS
PROCEDURE in_only_test (inParam1 IN VARCHAR2);
PROCEDURE in_and_out_test (inParam1 IN VARCHAR2, outParam1 OUT VARCHAR2);
END test_pkg;
/
CREATE OR REPLACE PACKAGE BODY test_pkg AS
PROCEDURE in_only_test(inParam1 IN VARCHAR2) AS
BEGIN
DBMS_OUTPUT.PUT_LINE('in_only_test: ' || inParam1);
END;
PROCEDURE in_and_out_test(inParam1 IN VARCHAR2, outParam1 OUT VARCHAR2) AS
BEGIN
outParam1 := 'Woohoo Im an outparam, and this is my inparam ' || inParam1;
END;
END test_pkg;
兩個過程:in_only_test 僅輸入;in_and_out_test 輸入+輸出。
@Entity
@Table(name = "MYTABLE")
@NamedStoredProcedureQueries({
@NamedStoredProcedureQuery(
name = "in_only_test", // JPA 內部調用名
procedureName = "test_pkg.in_only_test", // 數據庫全限定名
parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, name = "inParam1", type = String.class)
}
),
@NamedStoredProcedureQuery(
name = "in_and_out_test",
procedureName = "test_pkg.in_and_out_test",
parameters = {
@StoredProcedureParameter(mode = ParameterMode.IN, name = "inParam1", type = String.class),
@StoredProcedureParameter(mode = ParameterMode.OUT, name = "outParam1", type = String.class)
}
)
})
public class MyTable implements Serializable {
// 實體字段省略
}
關鍵映射規則:
name:Repository 方法 @Procedure(name=...) 引用的名稱procedureName:數據庫實際存儲過程名(含包名)mode:IN/OUT/INOUT/REF_CURSORtype:Java 類型對應數據庫類型public interface MyTableRepository extends CrudRepository<MyTable, Long> {
@Procedure(name = "in_only_test")
void inOnlyTest(@Param("inParam1") String inParam1);
@Procedure(name = "in_and_out_test")
String inAndOutTest(@Param("inParam1") String inParam1);
}
匹配規則:
@Procedure.name = @NamedStoredProcedureQuery.name@Param 值 = @StoredProcedureParameter.name@Service
public class StoredProcService {
@Autowired
private MyTableRepository repo;
public void demo() {
String input = "Hi Im an inputParam";
// 有輸出參數
String output = repo.inAndOutTest(input);
Assert.assertEquals(output, "Woohoo Im an outparam, and this is my inparam " + input);
// 無輸出參數
repo.inOnlyTest(input);
}
}
當註解方式不生效(如特定數據庫方言差異),可降級使用 EntityManager 原生調用:
// 1. 定義自定義接口
public interface MyTableRepositoryCustom {
void inOnlyTest(String inParam1);
}
// 2. 主接口繼承
public interface MyTableRepository extends CrudRepository<MyTable, Long>, MyTableRepositoryCustom {}
// 3. 實現類(命名規則:接口名 + Impl)
public class MyTableRepositoryImpl implements MyTableRepositoryCustom {
@PersistenceContext
private EntityManager em;
@Override
public void inOnlyTest(String inParam1) {
em.createNativeQuery("BEGIN test_pkg.in_only_test(:inParam1); END;")
.setParameter("inParam1", inParam1)
.executeUpdate();
}
}
優勢: 完全控制 SQL 語法,兼容性最強;劣勢: 丟失 JPA 類型映射優勢,維護成本高。
| 現象 | 可能原因 | 解決方案 |
|---|---|---|
| 報錯 "Procedure not found" | procedureName 大小寫/包名錯誤 | 確認數據庫實際對象名,Oracle 需包名.過程名 |
| OUT 參數為 null | mode 未設為 OUT 或類型不匹配 | 檢查 @StoredProcedureParameter.mode 及 type |
| 事務不提交 | 存儲過程隱式提交/回滾 | 避免在存儲過程中管理事務,交由 Spring 管理 |
Spring Data JPA 的存儲過程調用機制,將繁瑣的 JDBC 調用封裝為聲明式註解,配合自定義 Repository 降級方案,能滿足 99% 的企業級調用場景。