2014-02-26 41 views
12

使用新的JPA 2.1 stored procedure調用,有沒有辦法傳遞一個空參數?是否可以在Java JPA 2.1中將空參數傳遞給存儲過程?

下面是一個例子用法:

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.<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

你被定義類型調用registerStoredProcedureParameter,以便您可能有提供程序錯誤或配置問題。你使用什麼提供者和版本? – Chris

+0

我開箱使用WildFly 8.0.0.Final,它在內部使用Hibernate 4.3.1。我更新了最新的適當的Postgres驅動程序,它是[9.3.1101](http://jdbc.postgresql.org/download.html)。 – Pool

+0

要使InputParameters類正常工作,您的過程需要採用結構類型,並且您的提供程序支持將Java類轉換爲結構。請參閱http://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Structured_Object-Relational_Data_Types。 – Chris

回答

6

這裏是我對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()); 
     } 
    } 
    } 

    .. 
} 

以上評論全文如下:

用戶沒有將值綁定到正在處理的參數。 可能正常如果數據庫中定義的過程定義該參數的默認值 。不幸的是,沒有辦法通過JDBC元數據可靠地知道過程參數 是否定義了默認值。所以我們只是允許程序執行 發生,假設數據庫會正確投訴,如果 沒有設置給定的參數綁定值是一個錯誤。

我正在解釋,因爲JPA(特別是Hibernate)根本不支持設置空參數。看起來他們正在努力支持默認參數值,而在適當的時候替換空值。他們選擇支持前者。它看起來像那些誰需要後者(空的值)的支持必須使用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;博士我們仍然不得不面對低級別的JDBC,因爲無論JPA或休眠似乎解決可空參數。它們支持用空值代替過程參數默認值。

+3

我開始了一個問題與休眠︰https://hibernate.atlassian.net/browse/HHH-9007 –

+0

是否有任何簡單的解決這個空值?或者我們需要檢查每個值,如果它的空值應該設置空字符串「」,但是這需要大量的if/else檢查並且代碼變得很難看。我的代碼就像... StoredProcedureQuery query = entityManager.createNamedStoredProcedureQuery(「createUpdate」); query.setParameter(「COLUMN」,someValue); – Jay

+0

這是不幸的,但如果你打算使用JPA 2.1,那麼你必須uglify你的代碼。我使用Hibernate的'doReturningWork'或'doWork',然後使用手動JDBC來使用存儲過程。我只是沒有看到使用新API的好處,因爲它是在Hibernate中實現的。 –

2

我遇到了同樣的問題。我無法控制存儲過程,因此我無法將其修改爲接受默認值。

但是,因爲我用的數據庫是Oracle數據庫,我能夠改變我的存儲過程參數的數據類型(在orm.xml)至java.lang.String,然後代入空String"")來解決這個問題,即本來應該使用NULL值。由於Oracle以相同的方式處理空的String"")和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> 

的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. 
} 
2

來繞去的問題,如果我們沒有很多可能的空參數eters到存儲過程,我們可以有多個@NamedStoredProcedureQuery映射到數據庫中的同一個存儲過程。然後我們可以準備適當的查詢。

例如,

@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");} 
0

我們可以使用空字符串,它也可以作爲null。 例如。從雙重選擇NVL('','XXX');返回XXX 嘗試將StringUtils.EMPTY作爲參數傳遞。

2

設置屬性 hibernate.proc.param_null_passing =真

例如:

<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> 
+0

提高你的問題,請 – ddb

0

這爲我工作

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

是的,這是可以傳遞null PARAMS存儲過程當使用JPA 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"); 
+0

謝謝你這是工作,有人需要accpet這個答案! – zt1983811

+1

這工作。謝謝!。這應該被標記爲答案 –

+0

如果有人正在尋找Java風格的配置:屬性jpaProperties = new Properties(){ { setProperty(「hibernate.proc.param_null_passing」,「true」); } }; – Ermintar

1

這是我mehtod執行查詢,以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; 
} 
相關問題