是否可以将null参数传递给Java JPA 2.1中的存储过程?

使用新的JPA 2.1 存储过程调用,有没有办法传递null参数?

以下是一个示例用法:

StoredProcedureQuery storedProcedure = em.createStoredProcedureQuery("get_item", Item.class); storedProcedure.registerStoredProcedureParameter(0, String.class, ParameterMode.IN); storedProcedure.registerStoredProcedureParameter(1, String.class, ParameterMode.IN); storedProcedure.registerStoredProcedureParameter(2, Timestamp.class, ParameterMode.IN); storedProcedure.setParameter(0, a); storedProcedure.setParameter(1, b); storedProcedure.setParameter(2, c); storedProcedure.execute(); 

这在给出所有参数时都有效,但是当cnull ,它将因(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.(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类型的类型。

有没有办法传递空参数?

是的,在使用JPA StoredProcedureQuery时,可以将null参数传递给存储过程。

您必须在application.properties文件中添加以下属性,并使用其名称注册参数。

spring.jpa.properties.hibernate.proc.param_null_passing=true

例:

 StoredProcedureQuery q = em.createStoredProcedureQuery(Globals.SPROC_PROBLEM_COMMENT2, ProblemCommentVO.class); q.registerStoredProcedureParameter("Patient_ID", Long.class, ParameterMode.IN); q.registerStoredProcedureParameter("Param2", Long.class, ParameterMode.IN); q.registerStoredProcedureParameter("Param3", Long.class, ParameterMode.IN); q.registerStoredProcedureParameter("Param4", Integer.class, ParameterMode.OUT); q.setParameter("Patient_ID", patientId); q.setParameter("Param2", null);//passing null value to Param2 q.setParameter("Param3", null); List pComments = q.getResultList(); Integer a = (Integer) q.getOutputParameterValue("Param4"); 

以下是我对Hibernate 4.3的发现,它与JPA 2.1相关。

如果数据库不支持默认参数,这将抛出exception:

 ProcedureCall procedure = getSession().createStoredProcedureCall("my_procedure"); procedure.registerParameter("my_nullable_param", String.class, 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() ); } } } .. } 

上述评论如下:

用户未将值绑定到正在处理的参数。 如果数据库中定义的过程定义了该参数的默认值,那么这可能没问题。 遗憾的是,没有办法通过JDBC元数据可靠地了解过程参数是否定义了默认值。 因此,如果没有设置给定参数绑定值是一个错误,我们只是允许程序执行发生,假设数据库会适当地抱怨。

我解释说,因为JPA(特别是Hibernate)根本不支持设置null参数。 看起来他们正在努力支持默认参数值,而在适当时替换空值。 他们选择支持前者。 看起来那些需要支持后者(可空值)的人必须使用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似乎都没有解决可以为空的参数。 它们支持过程参数默认值而不是替换空值。

设置属性hibernate.proc.param_null_passing = true

例:

     my.entity.store.package                

我遇到了同样的问题。 我没有控制存储过程,所以我无法修改它以接受默认值。

但是,因为我使用的数据库是一个Oracle数据库,所以我能够通过将我的存储过程参数(在orm.xml )的数据类型更改为java.lang.String然后替换一个空String来解决此问题( "" )使用NULL值是合适的。 由于Oracle以相同的方式处理空String"" )和NULL ,因此我能够有效地欺骗Hibernate将“null”值传递给Oracle存储过程。

如果您不想使用编写低级JDBC代码,那么您的存储过程不可修改,并且您的数据库是基于Oracle的,请尝试这种方法。 只需确保所有实体管理器代码都将参数视为预期的数据类型(在我的例子中为java.math.BigDecimal ),并等到您将参数值设置为转换为java.lang.String

例如:

orm.xml中:

       

Java的:

 public void callStoredProc(java.math.BigDecimal myNullableInParam) { EntityManager entityManager = EMF.createEntityManager(); StoredProcedureQuery query = entityManager.createNamedStoredProcedureQuery("storedProcName"); query.setParameter("my_nullable_in_param", (myNullableInParam == null ? "" : myNullableInParam.toString())); // set more parameters, execute query, commit transaction, etc. } 

要解决这个问题,如果我们没有很多可能的空参数,我们可以将多个@NamedStoredProcedureQuery映射到数据库中的相同sproc。 然后我们可以准备适当的查询。

例如,

  @NamedStoredProcedureQuery(name = "MyEntity.GetWithoutNullParam", procedureName = "dbo.GetProc", parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "id", type = Integer.class)}, resultClasses = MyEntity.class), @NamedStoredProcedureQuery(name = "MyEntity.GetWithNullParam", procedureName = "dbo.GetProc", parameters = { @StoredProcedureParameter(mode = ParameterMode.IN, name = "id", type = Integer.class), @StoredProcedureParameter(mode = ParameterMode.IN, name = "nullableparam", type = String.class), resultClasses = MyEntity.class) 

现在对“nullableparam”进行空检查,我们可以在存储库中创建适当的命名proc。 类似的东西

 if(MyEntity.nullableparam == null){StoredProcedureQuery storedProcedureQuery = em.createNamedStoredProcedureQuery("MyEntity.GetWithoutNullParam");} else{StoredProcedureQuery storedProcedureQuery = em.createNamedStoredProcedureQuery("MyEntity.GetWithNullParam");} 

这对我有用

 if (columnname.length() > 0) { fSignUp.setString(3, columnname); } else { fSignUp.setNull(, Types.NULL); } 

这是我执行查询的方法,在Postgresql中使用setNull(i,Type.NULL)的“解决方法”。

解决方案是捕获特定的exception并遍历所有可能类型的“java.sql.Types”,直到找到一些不会引发exception的类型

 private Connection rawConnection; public ResultSet executeQuery(String sql, ArrayList params) throws SQLException { PreparedStatement stmt = null; Iterator it; Object itemParam; ResultSet rs = null; int i = 1; Log.core.debug("Execute query sql:\n" + sql); stmt = rawConnection.prepareStatement(sql, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); if ( params != null && params.size() > 0 ) { it = params.iterator(); while ( it.hasNext() ) { itemParam = it.next(); if ( itemParam == null ) { stmt.setNull(i, Types.NULL); } else if ( itemParam instanceof String ) { stmt.setString(i, (String)itemParam); } else if ( itemParam instanceof Boolean ) { stmt.setBoolean(i, (Boolean)itemParam); } else if ( itemParam instanceof Integer ) { stmt.setInt(i, (Integer)itemParam); } else if ( itemParam instanceof Long ) { stmt.setLong(i, (Long)itemParam); } else if ( itemParam instanceof Float ) { stmt.setFloat(i, (Float)itemParam); } else if ( itemParam instanceof Double ) { stmt.setDouble(i, (Double)itemParam); } else if ( itemParam instanceof BigDecimal ) { stmt.setBigDecimal(i, (BigDecimal)itemParam); } else if ( itemParam instanceof Object[] ) // for postgresql, adapt for other dbs ?? { Class type = itemParam.getClass().getComponentType(); if ( type.equals(String.class) ) { stmt.setArray(i, rawConnection.createArrayOf("varchar", (Object[]) itemParam)); } else if ( type.equals(Boolean.class) ) { stmt.setArray(i, rawConnection.createArrayOf("bool", (Object[]) itemParam)); } else if ( type.equals(Integer.class) ) { stmt.setArray(i, rawConnection.createArrayOf("int4", (Object[]) itemParam)); } else if ( type.equals(Long.class) ) { stmt.setArray(i, rawConnection.createArrayOf("int8", (Object[]) itemParam)); } else if ( type.equals(Float.class) ) { stmt.setArray(i, rawConnection.createArrayOf("float4", (Object[]) itemParam)); } else if ( type.equals(Double.class) ) { stmt.setArray(i, rawConnection.createArrayOf("float8", (Object[]) itemParam)); } else if ( type.equals(BigDecimal.class) ) { stmt.setArray(i, rawConnection.createArrayOf("numeric", (Object[]) itemParam)); } } i++; } } infinite_loop: while ( true ) // fix for postgresql --> stmt.setNull(i, Types.NULL); // it's required set DataType in NULLs { try { rs = stmt.executeQuery(); break infinite_loop; } catch (SQLException e) { // fix for postgresql --> stmt.setNull(i, Types.NULL); // it's required set DataType in NULLs if ( IS_POSTGRESQL ) // adapt for other dbs ?? { String regexParNumber = "\\$([0-9]+)", sqlState = "42P18"; PSQLException pe = ((PSQLException)e), pe1; Pattern r, r1; Matcher m, m1; Field[] types; int paramNumber; if ( pe.getErrorCode() == 0 && sqlState.equals(pe.getSQLState()) ) { r = Pattern.compile(regexParNumber); m = r.matcher(pe.getMessage()); if ( m.find() ) { paramNumber = Integer.parseInt(m.group(1)); types = Types.class.getDeclaredFields(); Log.core.trace("Fix type for null sql argument[" + paramNumber + "] ..."); for ( Field type : types ) { if ( !"NULL".equals(type.getName()) ) { Log.core.trace("Fix type for null sql argument[" + paramNumber + "], trying type '" + type.getName() + "' ..."); try { stmt.setNull(paramNumber, type.getInt(null)); } catch (IllegalArgumentException | IllegalAccessException e1) { continue; } try { rs = stmt.executeQuery(); break infinite_loop; } catch (SQLException e1) { pe1 = ((PSQLException)e1); if ( pe1.getErrorCode() == 0 && sqlState.equals(pe1.getSQLState()) ) { r1 = Pattern.compile(regexParNumber); m1 = r1.matcher(pe1.getMessage()); if ( m1.find() ) { if ( paramNumber == Integer.parseInt(m1.group(1)) ) { continue; } else { continue infinite_loop; } } } throw e1; } } } } } } throw e; } finally { SQLWarning warns; Iterator itWarn; Throwable raise; try { warns = stmt.getWarnings(); if ( warns != null ) { itWarn = warns.iterator(); while ( itWarn.hasNext() ) { raise = itWarn.next(); Log.core.debug(" --> " + raise.getMessage()); } } } catch (SQLException e1) {} } } return rs; } 

以下步骤解决了该问题:

在springboot hibernate环境中的属性文件中全局设置一个参数spring.jpa.properties.hibernate.proc.param_null_passing = true

允许存储过程在hibernate java代码中传递null。

我们可以使用Empty string,它也可以作为null。 例如。 从双重选择NVL(”,’XXX’); 返回XXX尝试将StringUtils.EMPTY作为参数传递。