在Java JPA 2.1中是否可以向存储过程传递null参数?
使用新的JPA2.1调用,有没有方法传递null参数 下面是一个示例用法:在Java JPA 2.1中是否可以向存储过程传递null参数?,java,jpa,stored-procedures,Java,Jpa,Stored Procedures,使用新的JPA2.1调用,有没有方法传递null参数 下面是一个示例用法: StoredProcedureQuery storedProcedure = em.createStoredProcedureQuery("get_item", Item.class); storedProcedure.registerStoredProcedureParameter(0, String.class, ParameterMode.IN); storedProcedure.registerStoredPro
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
这在以下情况下失败:
Caused by: java.lang.IllegalArgumentException: Type cannot be null
我猜是因为它需要一个可以映射到SQL类型的类型
有没有办法传递空参数?以下是我对Hibernate 4.3的发现,它们与JPA 2.1相关 如果数据库不支持默认参数,这将引发异常:
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() );
}
}
}
..
}
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();
}
});
上面的评论是:
<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.
}
用户未将值绑定到正在处理的参数。那个
如果数据库中定义的过程定义了
该参数的默认值。不幸的是,没有办法
通过JDBC元数据可靠地知道过程参数
定义默认值。所以我们只允许程序执行
假设数据库在以下情况下会适当地进行投诉:
未设置给定的参数绑定值是一个错误
我解释为JPA(特别是Hibernate)根本不支持设置空参数。看起来他们正在努力支持默认参数值,而不是在适当的时候替换空值。他们选择支持前者。看起来那些需要支持后者(可空值)的人必须使用java.sql.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() );
}
}
}
..
}
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似乎都没有处理可为空的参数。它们支持过程参数默认值而不是替换空值。我遇到了同样的问题。我无法控制存储过程,因此无法修改它以接受默认值 但是,因为我使用的数据库是Oracle数据库,所以我可以通过将存储过程参数(在
orm.xml
中)的数据类型更改为java.lang.String
,然后替换一个空的字符串(”
)如果使用NULL
值是合适的。由于Oracle将空的字符串s(”
)和NULL
s等同对待,因此我能够有效地欺骗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.
}
为了解决这个问题,如果存储过程没有太多可能的空参数,那么可以将多个@namedStoredProcedureRequesty映射到数据库中的同一存储过程。然后我们可以准备适当的查询
比如说,
@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”进行空检查,我们可以在存储库中创建适当的命名过程。类似于
if(MyEntity.nullableparam == null){StoredProcedureQuery storedProcedureQuery = em.createNamedStoredProcedureQuery("MyEntity.GetWithoutNullParam");}
else{StoredProcedureQuery storedProcedureQuery = em.createNamedStoredProcedureQuery("MyEntity.GetWithNullParam");}
我们可以使用空字符串,它也可以用作null。
例如,从dual中选择NVL(“”,'XXX');返回XXX
尝试将StringUtils.EMPTY作为参数传递。设置属性
hibernate.proc.param_null_passing=true
例如:
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="packagesToScan">
<array>
<value>my.entity.store.package</value>
</array>
</property>
<property name="persistenceUnitName" value="mainPersistenceUnit" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
<property name="jpaDialect" ref="jpaDialect" />
<property name="jpaPropertyMap">
<map>
<entry key="hibernate.hbm2ddl.auto" value="validate" />
<entry key="hibernate.show_sql" value="true" />
<entry key="hibernate.format_sql" value="true" />
<entry key="hibernate.proc.param_null_passing" value="true" />
</map>
</property>
</bean>
my.entity.store.package
这对我很有效
if (columnname.length() > 0) {
fSignUp.setString(3, columnname);
} else {
fSignUp.setNull(<position>, Types.NULL);
}
if(columnname.length()>0){
fSignUp.setString(3,columnname);
}否则{
setNull(,Types.NULL);
}
是的,在使用JPA时可以向存储过程传递null参数
仓库加工设备
您必须在application.properties文件中添加以下属性,并用它们的名称注册参数
spring.jpa.properties.hibernate.proc.param\u null\u 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");
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");
StoredProcedureQuery q=em.createStoredProcedureQuery(Globals.SPROC\u PROBLEM\u COMMENT2,ProblemCommentVO.class);
q、 registerStoredProcedureParameter(“患者ID”,Long.class,ParameterMode.IN);
q、 寄存器存储过程参数(“Param2”,Long.class,ParameterMode.IN);
q、 寄存器存储过程参数(“Param3”,Long.class,ParameterMode.IN);
q、 registerStoredProcedureParameter(“Param4”,Integer.class,ParameterMode.OUT);
q、 设置参数(“患者ID”,patientId);
q、 setParameter(“Param2”,null)//将null值传递给Param2
q、 setParameter(“参数3”,null);
List pComments=q.getResultList();
整数a=(整数)q.getOutputParameterValue(“Param4”);
这是我执行查询的方法,在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;
}
私密连接;
public ResultSet executeQuery(字符串sql,ArrayList参数)引发SQLException
{
PreparedStatement stmt=null;
迭代器;
对象项参数;
结果集rs=null;
int i=1;
Log.core.debug(“执行查询sql:\n”+sql);
stmt=rawConnection.prepareStatement(sql,ResultSet.TYPE\u SCROLL\u SENSITIVE,ResultSet.CONCUR\u updateable);
if(params!=null&¶ms.size()>0)
{
it=params.iterator();
while(it.hasNext())
{
itemParam=it.next();
if(itemParam==null){stmt.setNull(i,Types.null);}
hibernate.proc.param_null_passing=true
@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)
})
)