Est-il possible de passer un paramètre nul à une procédure stockée dans Java JPA 2.1?

utilisation de la nouvelle JPA 2.1 procédure stockée appel, est-il possible de passer un paramètre null?

Voici un exemple d'utilisation:

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();

Cela fonctionne lorsque tous les paramètres sont donnés, mais quand cnull il échouera avec une erreur du pilote (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

j'ai également envisagé d'utiliser ma propre classe pour passer les paramètres, par exemple:

storedProcedure.registerStoredProcedureParameter(0, InputParameters.class, ParameterMode.IN);
storedProcedure.setParameter(0, inputParameters);

échec avec:

Caused by: java.lang.IllegalArgumentException: Type cannot be null

je suppose parce qu'il a besoin d'un type qui peut être mappé sur un type SQL.

Est-il possible de passer un paramètre null?

15
demandé sur Pool 2014-02-26 19:18:06

9 réponses

Oui, il est possible de passer des paramètres nuls aux procédures stockées en utilisant JPA StoredProcedureQuery.

Vous devez ajouter la propriété suivante dans l'application.propriétés fichier et enregistrer les paramètres avec leur nom.

spring.jpa.properties.hibernate.proc.param_null_passing=true

Exemple:

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");
19
répondu Shaan 2017-05-18 14:56:16

Voici mes résultats pour hibernation 4.3 qui sont pertinents pour JPA 2.1.

ceci lancera une exception si la base de données ne supporte pas les paramètres par défaut:

ProcedureCall procedure = getSession().createStoredProcedureCall("my_procedure");

procedure.registerParameter("my_nullable_param", String.class, ParameterMode.IN)
         .bindValue(null);

// execute
procedure.getOutputs();

hibernation source pour lier le paramètre au sous-jacent 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() );
      }
    }
  }

  ..
}

Le commentaire ci-dessus se lit comme suit:

l'utilisateur n'a pas attaché de valeur au paramètre en cours de traitement. Que peut-être ok si la procédure telle que définie dans la base de données définit un la valeur par défaut pour ce paramètre. Malheureusement, il n'est pas un moyen pour savoir de manière fiable, grâce aux métadonnées JDBC, si un paramètre de procédure définit une valeur par défaut. Donc nous permettons simplement l'exécution de la procédure se produire en supposant que la base de données se plaindra de façon appropriée si ne pas définir la valeur de bind du paramètre donné est une erreur.

j'interprète cela en tant que JPA (spécifiquement Hibernate) ne supporte pas de définir des paramètres nuls. On dirait qu'ils ont du mal à supporter les valeurs des paramètres par défaut plutôt que de remplacer une valeur nulle quand c'est approprié. Ils choisissent de soutenir les anciens. Il semble que ceux qui ont besoin de support pour ce dernier (valeurs nulles) doivent utiliser 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 nous sommes toujours obligés de traiter avec des JDBC de bas niveau parce que ni L'App ni L'hibernation ne semblent traiter des paramètres nulles. Ils sont prise en charge des valeurs par défaut des paramètres de la procédure par rapport à la substitution d'une valeur nulle.

7
répondu John Strickler 2014-03-25 19:29:10

Définir la propriété hiberner.proc.param_null_passing=true

exemple:

 <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>
3
répondu Alexei Samoukin 2016-08-19 08:56:08

j'ai rencontré ce même problème. Je n'avais pas le contrôle de la procédure stockée, donc je ne pouvais pas la modifier pour accepter les valeurs par défaut.

cependant, comme la base de données que j'utilisais était une base de données Oracle, j'ai pu contourner ce problème en changeant le type de données de mon paramètre de procédure stocké (dans orm.xml)java.lang.String et puis la substitution d'un vide String ("") où il aurait été approprié d'utiliser un NULL valeur. Depuis Oracle traite vide Strings ("" et NULL s de façon identique, j'ai réussi à tromper efficacement Hibernate en passant une valeur "nulle" à la procédure stockée par Oracle.

si vous ne voulez pas avoir à recourir à l'écriture de code JDBC de bas niveau, votre procédure stockée n'est pas modifiable, et votre base de données est basée sur Oracle, essayez cette approche. Assurez-vous simplement que tout votre code de gestionnaire d'entity traite le paramètre comme s'il s'agissait du type de données recherché (java.math.BigDecimal dans mon cas), et attendre jusqu'à ce que vous valeur du paramètre à convertir en java.lang.String.

Par exemple:

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
répondu Christopher Parker 2014-06-27 20:17:50

pour contourner le problème, si nous n'avons pas beaucoup de paramètres possibles null au sproc nous pouvons avoir plusieurs @NamedStoredProcedureQuery mapping au même sproc en DB. Nous pouvons alors préparer la requête appropriée.

Par exemple,

  @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)

maintenant une vérification nulle sur "nullableparam"nous pouvons créer le proc approprié nommé dans le dépôt. Quelque chose de semblable à

if(MyEntity.nullableparam == null){StoredProcedureQuery storedProcedureQuery = em.createNamedStoredProcedureQuery("MyEntity.GetWithoutNullParam");}
else{StoredProcedureQuery storedProcedureQuery = em.createNamedStoredProcedureQuery("MyEntity.GetWithNullParam");}
2
répondu Aayush 2016-02-17 00:48:53

Cela a fonctionné pour moi

if (columnname.length() > 0) {
    fSignUp.setString(3, columnname); 
} else {
    fSignUp.setNull(<position>, Types.NULL);
}
1
répondu RaviPrakash 2017-04-25 08:18:06

c'est mon mehtod pour exécuter les requêtes, avec un "workaround" pour setNull(I, Type.NULL) in Postgresql.

La solution est d'attraper l'exception spécifique et itérer sur tous les types possibles de "java.SQL.Types " jusqu'à ce que vous trouviez un type qu'il ne jette pas exception

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;
}
1
répondu eskualo 2017-06-14 06:17:48

ci-dessous étape résout le problème :

définir globalement un paramètre dans le fichier de propriétés dans l'environnement d'hibernation springboot printemps.jpa.propriété.hiberner.proc.param_null_passing=true

pour permettre une procédure stockée pour passer la valeur null dans hibernate code java.

1
répondu SandeepJain 2018-03-23 09:09:02

nous pouvons utiliser des chaînes vides, qui fonctionneront aussi comme null. par exemple. select NVL ( " ,' XXX') from dual; returns XXX Essayez de passer des StringUtils.Vide comme paramètre.

0
répondu Rashmi singh 2016-03-21 06:44:34