即将发布的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() );
}
...