Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/318.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/jpa/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在Java JPA 2.1中是否可以向存储过程传递null参数?_Java_Jpa_Stored Procedures - Fatal编程技术网

在Java JPA 2.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

使用新的JPA2.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.<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)
                })
)