即将发布的JPA 2.1规范增加了处理JDBC可调用语句(存储过程和函数调用)的标准支持。Arun Gupta在https://blogs.oracle.com/arungupta/entry/jpa_2_1_early_draft中对JPA 2.1的初始功能进行了合理的总结,包括对存储过程的支持。在这里,“标准化”意味着跨提供者和数据库供应商。非常不错。尽管我很喜欢处理可调用语句的标准支持的想法,但我对提议的JPA StoredProcedureQuery API的一些方面并不十分兴奋。我的担忧主要集中在如何访问输出,尤其是当预期多个结果时。让我们首先看看一个返回结果的简单示例
StoredProcedreQuery query = entityManager.createStoredProcedureQuery( "top10SalesmenByQuarter", Employee.class ); query.registerStoredProcedureParameter( "quarter", String.class, ParameterMode.IN ); query.setParameter( "quarter", "Q1-2000" ); List top10Salesmen = query.getResultList(); ...
这里没有什么令人讨厌的。
然而,想象一下,我们想调用一个既有更新计数又有结果的存储过程。在我看来,这就是StoredProcedureQuery变得有点可疑的地方。它很大程度上试图遵循JDBC访问混合返回的范例。当然,这种方法的优势是它对熟悉JDBC API的开发者来说很熟悉。让我们看看一个例子
StoredProcedreQuery query = entityManager.createStoredProcedureQuery( "mixedReturns" ); ... while( 1==1 ) { boolean isResult = query.hasMoreResults(); if ( isResult ) { handleResult( query.getResultList() ); } else { int updateCount = query.getUpdateCount(); // complete exit condition is ( ! query.hasMoreResults() && query.getUpdateCount != -1 ) if ( updateCount == -1 ) { break; } handleUpdateCount( updateCount ); } } ...
对我来说,这并不是非常用户友好。然而,我无法将该API的提议更改纳入。因此,我决定开发一个替代API;通过Session访问的Hibernate原生API。上述查询使用该API将看起来像
org.hibernate.procedure.ProcedureCall call = entityManager.unwrap( Session.class ).createStoredProcedureCall( "mixedReturns" ); ... org.hibernate.procedure.ProcedureResult callResult = call.getResult(); while ( callResult.hasMoreReturns() ) { final org.hibernate.result.Return return = callResult.getNextReturn(); if ( org.hibernate.result.ResultSetReturn.class.isInstance( return ) ) { handleResult( ( (org.hibernate.result.ResultSetReturn) return ).getResultList() ); } else { handleUpdateCount( (org.hibernate.result.UpdateCountReturn) return ).getUpdateCount() ); } } ...
这两个API都支持处理多个ResultSet。如果提供了返回类或结果集映射,则它们适用于所有处理过的ResultSet
StoredProcedreQuery query = entityManager.createStoredProcedureQuery( "top_and_bottom_salesmen_by_quarter", Employee.class ); query.registerStoredProcedureParameter( "quarter", String.class, ParameterMode.IN ); query.registerStoredProcedureParameter( "top_salesmen", String.class, ParameterMode.REF_CURSOR ); query.registerStoredProcedureParameter( "bottom_salesmen", String.class, ParameterMode.REF_CURSOR ); query.setParameter( "quarter", "Q1-2000" ); // we will end up with 2 result lists, where each list contains elements of type Employee. Pretty sweet! boolean isResult = query.hasMoreResults(); while( isResult ) { handleResult( query.getResultList() ); } ...
ProcedureCall call = entityManager.unwrap( Session.class ).createStoredProcedureCall( "top_and_bottom_salesmen_by_quarter", Employee.class ); call.registerParameter( "quarter", String.class, ParameterMode.IN ); call.registerParameter( "top_salesmen", String.class, ParameterMode.REF_CURSOR ); call.registerParameter( "bottom_salesmen", String.class, ParameterMode.REF_CURSOR ); call.setParameter( "quarter", "Q1-2000" ); // we will end up with 2 result lists, where each list contains elements of type Employee. Pretty sweet! ProcedureResult callResult = call.getResult(); while ( callResult.hasMoreReturns() ) { final ResultSetReturn rtn = (ResultSetReturn) callResult.getNextReturn(); handleResult( rtn.getResultList() ); } ...