使用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
- 使用 Spring Transactional 註釋的最佳方式 - Vlad MihalceaSpring
- 呼叫儲存過程儲存過程
- mssql 儲存過程呼叫另一個儲存過程中的結果的方法分享SQL儲存過程
- 如何啟用Hibernate慢查詢日誌? -Vlad Mihalcea
- 批處理最佳實踐 - Vlad Mihalcea
- linux呼叫儲存過程Linux儲存過程
- Winform呼叫儲存過程ORM儲存過程
- SQL 儲存過程裡呼叫另一個儲存過程SQL儲存過程
- Sqlsugar呼叫Oracle的儲存過程SqlSugarOracle儲存過程
- jdbc使用call呼叫儲存過程報錯JDBC儲存過程
- 如何透過Hibernate/JPA在MySQL中儲存UTC時區?MySql
- jsp中呼叫儲存過程JS儲存過程
- mysql如何呼叫儲存過程MySql儲存過程
- MySQL儲存過程的建立和使用MySql儲存過程
- mysql多次呼叫儲存過程的問題MySql儲存過程
- Mysql 儲存過程的使用MySql儲存過程
- MySQL儲存過程語句及呼叫MySql儲存過程
- Oracle 儲存過程分頁 + Sqlsugar呼叫Oracle儲存過程SqlSugar
- mysql和orcale的儲存過程和儲存函式MySql儲存過程儲存函式
- Hibernate/JPA中@OneToOne和@MapsId的使用
- 在JPA中請優先使用sequence策略生成實體識別符號的值 - Vlad Mihalcea符號
- Springboot呼叫Oracle儲存過程的幾種方式Spring BootOracle儲存過程
- ibatis呼叫oracle儲存過程(極簡版)BATOracle儲存過程
- SQL 搜尋方法或鍵集分頁 - Vlad MihalceaSQL
- COBOL六十週年紀念:過去,現在和未來 -Vlad Mihalcea
- SqlServer儲存過程的建立與使用SQLServer儲存過程
- 適合用於資料庫主鍵的最佳UUID工具庫 - Vlad Mihalcea資料庫UI
- .NET Core中使用Dapper操作Oracle儲存過程最佳實踐APPOracle儲存過程
- Hibernate/JPA中避免save()冗餘呼叫
- Laravel 中使用 MySQL 儲存過程LaravelMySql儲存過程
- MySQL 儲存過程和函式MySql儲存過程函式
- MySQL儲存過程和函式MySql儲存過程函式
- oracle的儲存過程Oracle儲存過程
- 低權使用者呼叫高權使用者建立的儲存過程出錯儲存過程
- MySQL儲存過程的異常處理方法MySql儲存過程
- 解決Spring Data JPA Hibernate的N+1問題的最佳方法Spring
- java使用mybatis 呼叫儲存過程返回一個遊標結果集JavaMyBatis儲存過程