使用JPA和Hibernate呼叫儲存過程的最佳方法 - Vlad Mihalcea
在本文中,您將學習使用JPA和Hibernate時呼叫儲存過程的最佳方法,以便儘快釋放底層JDBC資源。
我決定寫這篇文章,因為Hibernate處理儲存過程的方式會導致ORA-01000: maximum open cursors exceededOracle 上出現問題,如本Hibernate論壇帖子或StackOverflow問題所述。
儲存過程呼叫如何與JPA和Hibernate一起使用
要使用JPA呼叫儲存過程或資料庫函式,可以使用StoredProcedureQuery如以下示例所示:
StoredProcedureQuery query = entityManager .createStoredProcedureQuery("count_comments") .registerStoredProcedureParameter( "postId", Long.class, ParameterMode.IN ) .registerStoredProcedureParameter( "commentCount", Long.class, ParameterMode.OUT ) .setParameter("postId", 1L); query.execute(); Long commentCount = (Long) query .getOutputParameterValue("commentCount"); |
在幕後,StoredProcedureQuery介面透過特定於Hibernate 的介面進行擴充套件ProcedureCall,因此我們可以像這樣重寫前面的示例:
ProcedureCall query = session .createStoredProcedureCall("count_comments"); query.registerParameter( "postId", Long.class, ParameterMode.IN ) .bindValue(1L); query.registerParameter( "commentCount", Long.class, ParameterMode.OUT ); Long commentCount = (Long) call .getOutputs() .getOutputParameterValue("commentCount"); |
在執行Hibernate的ProcedureCall的JPA的StoredProcedureQuery或outputs().getCurrent()時,Hibernate執行以下操作:
請注意,JDBC CallableStatement已準備好並儲存在關聯的ProcedureOutputsImpl物件中。在呼叫getOutputParameterValue方法時,Hibernate將使用底層CallableStatement來獲取OUT引數。
因此,CallableStatement即使在執行儲存過程並獲取OUT或REF_CURSOR引數之後,底層JDBC 仍保持開啟狀態。
現在,預設情況下,CallableStatement在當前正在執行的資料庫事務結束時將關閉,這是透過呼叫commit或rollback實現。
測試時間
要驗證此行為,請考慮以下測試用例:
StoredProcedureQuery query = entityManager .createStoredProcedureQuery("count_comments") .registerStoredProcedureParameter( "postId", Long.class, ParameterMode.IN ) .registerStoredProcedureParameter( "commentCount", Long.class, ParameterMode.OUT ) .setParameter("postId", 1L); query.execute(); Long commentCount = (Long) query .getOutputParameterValue("commentCount"); assertEquals(Long.valueOf(2), commentCount); ProcedureOutputs procedureOutputs = query .unwrap(ProcedureOutputs.class); CallableStatement callableStatement = ReflectionUtils .getFieldValue( procedureOutputs, "callableStatement" ); assertFalse(callableStatement.isClosed()); procedureOutputs.release(); assertTrue(callableStatement.isClosed()); |
請注意,CallableStatement即使在呼叫execute或獲取commentCount OUT引數後仍處於開啟狀態。只有呼叫完成後才釋放ProcedureOutputs的物件時,CallableStatement也會封閉。
儘快關閉JDBC語句
因此,要CallableStatement儘快關閉JDBC ,在儲存過程中獲取所需的所有資料後呼叫release:
StoredProcedureQuery query = entityManager .createStoredProcedureQuery("count_comments") .registerStoredProcedureParameter( "postId", Long.class, ParameterMode.IN ) .registerStoredProcedureParameter( "commentCount", Long.class, ParameterMode.OUT ) .setParameter("postId", 1L); try { query.execute(); Long commentCount = (Long) query .getOutputParameterValue("commentCount"); assertEquals(Long.valueOf(2), commentCount); } finally { query.unwrap(ProcedureOutputs.class).release(); } CallableStatement callableStatement = ReflectionUtils .getFieldValue( query.unwrap(ProcedureOutputs.class), "callableStatement" ); assertTrue(callableStatement.isClosed()); |
在finally塊中release的關聯ProcedureOutputs物件上呼叫方法可確保CallableStatement無論儲存過程呼叫的結果如何都關閉JDBC 。
現在,release手動呼叫有點乏味,所以我決定建立HHH-13215Jira問題,我將其整合到Hibernate ORM 6分支中。
因此,從Hibernate 6開始,您可以像這樣重寫前面的示例:
Long commentCount = doInJPA(entityManager -> { try(ProcedureCall query = entityManager .createStoredProcedureQuery("count_comments") .unwrap(ProcedureCall.class)) { return (Long) query .registerStoredProcedureParameter( "postId", Long.class, ParameterMode.IN ) .registerStoredProcedureParameter( "commentCount", Long.class, ParameterMode.OUT ) .setParameter("postId", 1L) .getOutputParameterValue("commentCount"); } }); |
好多了,對吧?
透過ProcedureCall擴充套件介面AutoClosable,我們可以使用try-with-resource Java語句,因此在解除分配JDBC資源時,呼叫資料庫儲存過程會更簡潔,更直觀。
結論
在CallableStatement使用JPA和Hibernate呼叫儲存過程時,儘快釋放底層JDBC 非常重要,否則,資料庫遊標將一直開啟,直到提交或回滾當前事務為止。
因此,從Hibernate ORM 6開始,您應該使用try-finally塊。同時,對於Hibernate 5和4,CallableStatement在完成獲取所需的所有資料後,應該使用try-finally塊關閉右側。
相關文章
- 使用JPA和Hibernate延遲載入實體屬性的最佳方法 - Vlad Mihalcea
- Hibernate呼叫oracle儲存過程的問題Oracle儲存過程
- vb呼叫儲存過程的方法儲存過程
- 使用 Spring Transactional 註釋的最佳方式 - Vlad MihalceaSpring
- 關於hibernate 和 儲存過程儲存過程
- 呼叫儲存過程儲存過程
- php呼叫mysql儲存過程和函式的方法(轉)PHPMySql儲存過程函式
- 批處理最佳實踐 - Vlad Mihalcea
- mssql 儲存過程呼叫另一個儲存過程中的結果的方法分享SQL儲存過程
- mybatis呼叫檢視和儲存過程MyBatis儲存過程
- 使用ADO呼叫儲存過程 (轉)儲存過程
- Winform呼叫儲存過程ORM儲存過程
- perl呼叫儲存過程儲存過程
- jdbc呼叫儲存過程JDBC儲存過程
- 在儲存過程A中呼叫儲存過程B的結果儲存過程
- Hibernate中怎麼使用儲存過程呢?儲存過程
- SQL 儲存過程裡呼叫另一個儲存過程SQL儲存過程
- Sqlsugar呼叫Oracle的儲存過程SqlSugarOracle儲存過程
- 儲存過程呼叫其他模式的儲存過程需要注意的地方儲存過程模式
- jdbc使用call呼叫儲存過程報錯JDBC儲存過程
- mysql如何呼叫儲存過程MySql儲存過程
- linux呼叫儲存過程Linux儲存過程
- PB中呼叫儲存過程儲存過程
- java 呼叫oracle 儲存過程JavaOracle儲存過程
- php呼叫mssql儲存過程PHPSQL儲存過程
- java中呼叫儲存過程Java儲存過程
- 如何啟用Hibernate慢查詢日誌? -Vlad Mihalcea
- 兩種SQL分頁方法儲存過程和遊標儲存過程SQL儲存過程
- MySQL儲存過程的建立和使用MySql儲存過程
- 完整的分頁儲存過程以及c#呼叫方法儲存過程C#
- jsp中呼叫儲存過程JS儲存過程
- Spring mybatis 呼叫儲存過程SpringMyBatis儲存過程
- 用PHP呼叫MySQL儲存過程PHPMySql儲存過程
- c / c + + 呼叫mysql儲存過程MySql儲存過程
- C#呼叫 oracle儲存過程C#Oracle儲存過程
- C#呼叫Oracle儲存過程C#Oracle儲存過程
- 動態呼叫儲存過程 sample:儲存過程
- java中呼叫ORACLE儲存過程JavaOracle儲存過程