以下是一个示例用法:
StoredProcedureQuery storedProcedure = em.createStoredProcedureQuery("get_item",Item.class); storedProcedure.registerStoredProcedureParameter(0,String.class,ParameterMode.IN); storedProcedure.registerStoredProcedureParameter(1,ParameterMode.IN); storedProcedure.registerStoredProcedureParameter(2,Timestamp.class,ParameterMode.IN); storedProcedure.setParameter(0,a); storedProcedure.setParameter(1,b); storedProcedure.setParameter(2,c); storedProcedure.execute();
当所有参数都被给出时,这个工作起作用,但是当c为空时,它会失败并出现(Postgresql)JDBC驱动程序的错误.
Caused by: org.postgresql.util.PsqlException: No value specified for parameter 2 at org.postgresql.core.v3.SimpleParameterList.checkAllParameteRSSet(SimpleParameterList.java:216) [postgresql-9.3-1101.jdbc41.jar:] at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:244) [postgresql-9.3-1101.jdbc41.jar:] at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:559) [postgresql-9.3-1101.jdbc41.jar:] at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:417) [postgresql-9.3-1101.jdbc41.jar:] at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:410) [postgresql-9.3-1101.jdbc41.jar:] at org.jboss.jca.adapters.jdbc.WrappedPreparedStatement.execute(WrappedPreparedStatement.java:404) at org.hibernate.result.internal.OutputsImpl.<init>(OutputsImpl.java:69) [hibernate-core-4.3.1.Final.jar:4.3.1.Final] ... 244 more
我也考虑过使用我自己的类传递参数,例如:
storedProcedure.registerStoredProcedureParameter(0,InputParameters.class,ParameterMode.IN); storedProcedure.setParameter(0,inputParameters);
这失败了:
Caused by: java.lang.IllegalArgumentException: Type cannot be null
我猜是因为它需要一个可以映射到sql类型的类型.
有没有办法传递null参数?
解决方法
如果DB不支持默认参数,则会抛出异常:
ProcedureCall procedure = getSession().createStoredProcedureCall("my_procedure"); procedure.registerParameter("my_nullable_param",ParameterMode.IN) .bindValue(null); // execute procedure.getOutputs();
从Hibernate的源码将参数绑定到底层CallableStatement:
public abstract class AbstractParameterRegistrationImpl { .. @Override public void prepare(CallableStatement statement,int startIndex) throws sqlException { if ( mode == ParameterMode.INOUT || mode == ParameterMode.IN ) { if ( bind == null || bind.getValue() == null ) { // the user did not bind a value to the parameter being processed. That might be ok *if* the // procedure as defined in the database defines a default value for that parameter. // Unfortunately there is not a way to reliably know through JDBC Metadata whether a procedure // parameter defines a default value. So we simply allow the procedure execution to happen // assuming that the database will complain appropriately if not setting the given parameter // bind value is an error. log.debugf("Stored procedure [%s] IN/INOUT parameter [%s] not bound; assuming procedure defines default value",procedureCall.getProcedureName(),this); } else { typeToUse.nullSafeSet( statement,bind.getValue(),startIndex,session() ); } } } .. }
以上评论如下:
The user did not bind a value to the parameter being processed. That
might be ok if the procedure as defined in the database defines a
default value for that parameter. Unfortunately there is not a way to
reliably know through JDBC Metadata whether a procedure parameter
defines a default value. So we simply allow the procedure execution
to happen assuming that the database will complain appropriately if
not setting the given parameter bind value is an error.
我正在解释为JPA(特别是Hibernate)不支持设置空参数.看起来他们正在努力支持默认参数值,而在适当时替换空值.他们选择支持前者.看起来像后者需要支持(可空值)必须使用java.sql.CallableStatement:
getSession().doWork(new Work() { @Override public void execute(Connection conn) throws sqlException { CallableStatement stmt = conn.prepareCall("{ call my_prodecure(:my_nullable_param) }"); if(stringVariableThatIsNull != null) { stmt.setString("my_nullable_param",stringVariableThatIsNull); } else { stmt.setNull("my_nullable_param",Types.VARCHAR); } stmt.execute(); stmt.close(); } });
tl; dr我们仍然被迫处理低级JDBC,因为JPA或Hibernate似乎没有处理可空参数.它们支持过程参数默认值,而不是替换空值.