是否可以将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();
这在给出所有参数时都有效,但是当c
为null
,它将因(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
以下步骤解决了该问题:
在springboot hibernate环境中的属性文件中全局设置一个参数spring.jpa.properties.hibernate.proc.param_null_passing = true
允许存储过程在hibernate java代码中传递null。
我们可以使用Empty string,它也可以作为null。 例如。 从双重选择NVL(”,’XXX’); 返回XXX尝试将StringUtils.EMPTY作为参数传递。