使用新的JPA 2.1 stored procedure调用,有没有办法传递空参数?
以下是一个示例用法:
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.<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类型的类型。
有没有办法传递空参数?
答案 0 :(得分:29)
是的,使用JPA时可以将null参数传递给存储过程 StoredProcedureQuery。
您必须在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<ProblemCommentVO> pComments = q.getResultList();
Integer a = (Integer) q.getOutputParameterValue("Param4");
答案 1 :(得分:8)
以下是我对 JPA 2.1 相关的 Hibernate 4.3 的调查结果。
如果数据库不支持默认参数,则会抛出异常:
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似乎都没有解决可以为空的参数。它们支持过程参数默认值而不是替换空值。
答案 2 :(得分:3)
我遇到了同样的问题。我无法控制存储过程,因此无法修改它以接受默认值。
但是,因为我使用的数据库是Oracle数据库,所以我能够通过将我的存储过程参数的数据类型(在orm.xml
中)更改为java.lang.String
然后替换来解决此问题一个空的String
(""
),使用NULL
值是合适的。由于Oracle以相同的方式处理空String
s(""
)和NULL
,因此我能够有效地欺骗Hibernate将“null”值传递给Oracle存储过程。
如果您不想使用编写低级JDBC代码,那么您的存储过程不可修改,并且您的数据库是基于Oracle的,请尝试这种方法。只需确保所有实体管理器代码都将参数视为预期的数据类型(在我的情况下为java.math.BigDecimal
),并等到您将参数值设置为转换为java.lang.String
例如:
<强> orm.xml中:强>
<named-stored-procedure-query name="storedProcName" procedure-name="STORED_PROC_PACKAGE.STORED_PROC_NAME"> <!-- String so that empty string can be used for NULL value by Hibernate JPA. --> <parameter name="my_nullable_in_param" mode="IN" class="java.lang.String" /> <!-- was: <parameter name="my_nullable_in_param" mode="IN" class="java.math.BigDecimal" /> --> <!-- other IN and OUT params --> </named-stored-procedure-query>
<强>爪哇:强>
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. }
答案 3 :(得分:3)
设置属性 hibernate.proc.param_null_passing =真
示例:
small_icon
答案 4 :(得分:2)
要解决这个问题,如果我们没有很多可能的空参数,我们可以将多个@NamedStoredProcedureQuery映射到DB中的相同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)
现在对&#34; nullableparam&#34;进行空检查。我们可以在存储库中创建适当的命名proc。类似于
的东西if(MyEntity.nullableparam == null){StoredProcedureQuery storedProcedureQuery = em.createNamedStoredProcedureQuery("MyEntity.GetWithoutNullParam");}
else{StoredProcedureQuery storedProcedureQuery = em.createNamedStoredProcedureQuery("MyEntity.GetWithNullParam");}
答案 5 :(得分:2)
这是执行查询的方法,在Postgresql中使用setNull(i,Type.NULL)的“解决方法”。
解决方案是捕获特定异常并遍历所有可能类型的“java.sql.Types”,直到找到一些不会抛出异常的类型
private Connection rawConnection;
public ResultSet executeQuery(String sql, ArrayList<Object> params) throws SQLException
{
PreparedStatement stmt = null;
Iterator<Object> 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<Throwable> itWarn;
Throwable raise;
try
{
warns = stmt.getWarnings();
if ( warns != null )
{
itWarn = warns.iterator();
while ( itWarn.hasNext() )
{
raise = itWarn.next();
Log.core.debug("<DbMain Log> --> " + raise.getMessage());
}
}
}
catch (SQLException e1) {}
}
}
return rs;
}
答案 6 :(得分:2)
以下步骤解决了问题:
在springboot hibernate环境中的属性文件中设置全局参数 spring.jpa.properties.hibernate.proc.param_null_passing =真
允许存储过程在hibernate java代码中传递null。
答案 7 :(得分:1)
这对我有用
if (columnname.length() > 0) {
fSignUp.setString(3, columnname);
} else {
fSignUp.setNull(<position>, Types.NULL);
}
答案 8 :(得分:0)
您可以创建一种方法来强制启用所有调用参数,如下面的代码所示,如果您的StoredProcedureQuery
包含out
参数,该方法也可以正常工作。这可以帮助您在某处不会破坏null参数检查。
public void setStoreProcedureEnableNullParameters(StoredProcedureQuery storedProcedureQuery) {
if (storedProcedureQuery == null || storedProcedureQuery.getParameters() == null)
return;
for (Parameter parameter : storedProcedureQuery.getParameters()) {
((ProcedureParameterImpl) parameter).enablePassingNulls(true);
}
}
然后打电话
StoredProcedureQuery storedProcedure = entityManager.createStoredProcedureQuery("your_store_procedure")
.registerStoredProcedureParameter("Param_1", String.class, ParameterMode.OUT)
.registerStoredProcedureParameter("Param_2", String.class, ParameterMode.IN)
.registerStoredProcedureParameter("Param_3", String.class, ParameterMode.IN);
// Remember you must call before setting the value for the parameters
setStoreProcedureEnableNullParameters(storedProcedure);
storedProcedure
.setParameter("Param_2", null)
.setParameter("Param_3", "Some value");
storedProcedure.execute();
String outValue = (String) storedProcedure.getOutputParameterValue("Param_1");
答案 9 :(得分:0)
简单的回答是是,使用JPA StoredProcedureQuery时可以将空参数传递给存储过程。
有几种方法可以实现此目的。主要解决方案取决于您使用的休眠版本。
休眠4.3 +
Session session = em.unwrap(Session.class);
ProcedureCall procedure = session.createStoredProcedureCall(Constants.MY_PROCEDURE_NAME);
q.registerStoredProcedureParameter("Patient_ID", Long.class, ParameterMode.IN).enablePassingNulls(true);
q.registerStoredProcedureParameter("Param2", String.class, ParameterMode.IN).enablePassingNulls(true);
q.registerStoredProcedureParameter("Param3", Long.class, ParameterMode.IN).enablePassingNulls(true);
q.registerStoredProcedureParameter("Param4", Integer.class, ParameterMode.OUT);
q.setParameter("Param1", patientId);
q.setParameter("Param2", null);//passing null value to Param2
q.setParameter("Param3", null);//passing null value to Param3
List<> results = q.getResultList();
Integer a = (Integer) q.getOutputParameterValue("Param4");
旧版本
您必须在application.properties文件中添加以下属性,并使用其名称注册参数。
spring.jpa.properties.hibernate.proc.param_null_passing=true
示例:
StoredProcedureQuery q = em.createStoredProcedureQuery(Constants.MY_PROCEDURE_NAME, ResultData.class);
q.registerStoredProcedureParameter("Patient_ID", Long.class, ParameterMode.IN);
q.registerStoredProcedureParameter("Param2", String.class, ParameterMode.IN);
q.registerStoredProcedureParameter("Param3", Long.class, ParameterMode.IN);
q.registerStoredProcedureParameter("Param4", Integer.class, ParameterMode.OUT);
q.setParameter("Param1", patientId);
q.setParameter("Param2", null);//passing null value to Param2
q.setParameter("Param3", null);//passing null value to Param3
List<> results = q.getResultList();
Integer a = (Integer) q.getOutputParameterValue("Param4");
答案 10 :(得分:0)
将以下属性添加到您的配置持久性
hibernate.proc.param_null_passing=true
答案 11 :(得分:0)
spring.jpa.properties.hibernate.proc.param_null_passing=true
定义存储过程查询时, @NamedStoredProcedureQuery
不起作用。在这种情况下,输入空值的唯一方法是将可选参数定义为INOUT
而不是IN
。示例:
@NamedStoredProcedureQueries({
@NamedStoredProcedureQuery(name = "nameOfStoredProcedureCallingJpaMethod",
procedureName = "nameOfStoredProcedure",
parameters = {
@StoredProcedureParameter(name = "someParameter", type = String.class, mode = ParameterMode.IN),
@StoredProcedureParameter(name = "myOptionalParameter", type = String.class, mode = ParameterMode.INOUT)
})
)
答案 12 :(得分:-1)
我们可以使用Empty字符串,它也可以作为null。 例如。从双重选择NVL('','XXX');返回XXX 尝试将StringUtils.EMPTY作为参数传递。