使用JPA和Hibernate呼叫儲存過程的最佳方法 - Vlad Mihalcea
在本文中,您將學習使用JPA和Hibernate時呼叫儲存過程的最佳方法,以便儘快釋放底層JDBC資源。
我決定寫這篇文章,因為Hibernate處理儲存過程的方式會導致ORA-01000: maximum open cursors exceeded Oracle 上出現問題,如本Hibernate論壇帖子 或StackOverflow問題所述 。
儲存過程呼叫如何與JPA和Hibernate一起使用
要使用JPA呼叫儲存過程或資料庫函式,可以使用StoredProcedureQuery 如以下示例所示:
StoredProcedureQuery query = entityManager .createStoredProcedureQuery(<font>"count_comments"</font><font>) .registerStoredProcedureParameter( </font><font>"postId"</font><font>, Long.<b>class</b>, ParameterMode.IN ) .registerStoredProcedureParameter( </font><font>"commentCount"</font><font>, Long.<b>class</b>, ParameterMode.OUT ) .setParameter(</font><font>"postId"</font><font>, 1L); query.execute(); Long commentCount = (Long) query .getOutputParameterValue(</font><font>"commentCount"</font><font>); </font>
在幕後,StoredProcedureQuery介面通過特定於Hibernate 的介面進行擴充套件ProcedureCall,因此我們可以像這樣重寫前面的示例:
ProcedureCall query = session .createStoredProcedureCall(<font>"count_comments"</font><font>); query.registerParameter( </font><font>"postId"</font><font>, Long.<b>class</b>, ParameterMode.IN ) .bindValue(1L); query.registerParameter( </font><font>"commentCount"</font><font>, Long.<b>class</b>, ParameterMode.OUT ); Long commentCount = (Long) call .getOutputs() .getOutputParameterValue(</font><font>"commentCount"</font><font>); </font>
在執行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(<font>"count_comments"</font><font>) .registerStoredProcedureParameter( </font><font>"postId"</font><font>, Long.<b>class</b>, ParameterMode.IN ) .registerStoredProcedureParameter( </font><font>"commentCount"</font><font>, Long.<b>class</b>, ParameterMode.OUT ) .setParameter(</font><font>"postId"</font><font>, 1L); query.execute(); Long commentCount = (Long) query .getOutputParameterValue(</font><font>"commentCount"</font><font>); assertEquals(Long.valueOf(2), commentCount); ProcedureOutputs procedureOutputs = query .unwrap(ProcedureOutputs.<b>class</b>); CallableStatement callableStatement = ReflectionUtils .getFieldValue( procedureOutputs, </font><font>"callableStatement"</font><font> ); assertFalse(callableStatement.isClosed()); procedureOutputs.release(); assertTrue(callableStatement.isClosed()); </font>
請注意,CallableStatement即使在呼叫execute或獲取commentCount OUT引數後仍處於開啟狀態。只有呼叫完成後才釋放ProcedureOutputs的物件時,CallableStatement也會封閉。
儘快關閉JDBC語句
因此,要CallableStatement儘快關閉JDBC ,在儲存過程中獲取所需的所有資料後呼叫release:
StoredProcedureQuery query = entityManager .createStoredProcedureQuery(<font>"count_comments"</font><font>) .registerStoredProcedureParameter( </font><font>"postId"</font><font>, Long.<b>class</b>, ParameterMode.IN ) .registerStoredProcedureParameter( </font><font>"commentCount"</font><font>, Long.<b>class</b>, ParameterMode.OUT ) .setParameter(</font><font>"postId"</font><font>, 1L); <b>try</b> { query.execute(); Long commentCount = (Long) query .getOutputParameterValue(</font><font>"commentCount"</font><font>); assertEquals(Long.valueOf(2), commentCount); } <b>finally</b> { query.unwrap(ProcedureOutputs.<b>class</b>).release(); } CallableStatement callableStatement = ReflectionUtils .getFieldValue( query.unwrap(ProcedureOutputs.<b>class</b>), </font><font>"callableStatement"</font><font> ); assertTrue(callableStatement.isClosed()); </font>
在finally塊中release的關聯ProcedureOutputs物件上呼叫方法可確保CallableStatement無論儲存過程呼叫的結果如何都關閉JDBC 。
現在,release手動呼叫有點乏味,所以我決定建立HHH-13215 Jira問題,我將其整合到Hibernate ORM 6分支中。
因此,從Hibernate 6開始,您可以像這樣重寫前面的示例:
Long commentCount = doInJPA(entityManager -> { <b>try</b>(ProcedureCall query = entityManager .createStoredProcedureQuery(<font>"count_comments"</font><font>) .unwrap(ProcedureCall.<b>class</b>)) { <b>return</b> (Long) query .registerStoredProcedureParameter( </font><font>"postId"</font><font>, Long.<b>class</b>, ParameterMode.IN ) .registerStoredProcedureParameter( </font><font>"commentCount"</font><font>, Long.<b>class</b>, ParameterMode.OUT ) .setParameter(</font><font>"postId"</font><font>, 1L) .getOutputParameterValue(</font><font>"commentCount"</font><font>); } }); </font>
好多了,對吧?
通過ProcedureCall擴充套件介面AutoClosable,我們可以使用try-with-resource Java語句,因此在解除分配JDBC資源時,呼叫資料庫儲存過程會更簡潔,更直觀。
結論
在CallableStatement使用JPA和Hibernate呼叫儲存過程時,儘快釋放底層JDBC 非常重要,否則,資料庫遊標將一直開啟,直到提交或回滾當前事務為止。
因此,從Hibernate ORM 6開始,您應該使用try-finally塊。同時,對於Hibernate 5和4,CallableStatement在完成獲取所需的所有資料後,應該使用try-finally塊關閉右側。