Problema con las variables bind Oracle llamadas en JDBC

Estoy tratando de utilizar JDBC para llamar una consulta SQL en Oracle que utiliza variables bind con la sintaxis :varName. La consulta fue escrita para uso interactivo y se ve así:

select * from foo where bar = :mybar;

Si pudiera, cambiaría la consulta para usar los símbolos JDBC estándar "?" para representar la variable, y usar setString(1, "x") para establecerlos. Lamentablemente, no puedo cambiar el texto de la consulta ya que se ha validado para su uso en aplicaciones médicas.

Traté de envolverlo en código adicional que establece el valor para :mybar de la misma manera que un usuario interactivo, siguiendo ejemplos de Oracle, pero estoy recibiendo el error JDBC:

java.sql.SQLException: Desapareciendo el parámetro IN o OUT en el índice: 1.

La versión envuelta produce una cadena que se ve así

variable mybar VARCHAR

begin

:mybar := 'x'

select * from foo where bar = :mybar;

end 

El controlador Oracle es un controlador V12 cargado a través de:


    com.oracle
    ojdbc8
    12.2.0.1.0

Este es mi código de java:

try(Connection conn = getConnection()) {
    PreparedStatement stmt = stmt(conn, study, start, end);
    ResultSet resultSet = stmt.executeQuery();
    return Table.read().db(resultSet);
}

...

private PreparedStatement stmt(Connection connection, String study, LocalDate start, LocalDate end) throws SQLException {
    String query = wrapValidatedQuery(study, start, end);
    return connection.prepareStatement(query);
}

El método wrapValidatedQuery() devuelve la consulta como se muestra anteriormente.

Esta (abajo) es una versión independiente que debe ser ejecutable con cambios en los parámetros db.

public static void main(String[] args) throws Exception {
    String username = "name";
    String password = "pwd";
    Connection conn = null;
    Statement stmt;
    int port = 1521;
    String hostname = "host";
    String dbName = "db_name";
    String jdbcUrl = "jdbc:oracle:thin:@" + hostname + ":" + port + "/" + dbName;

    try {
        String driver = "oracle.jdbc.driver.OracleDriver";
        Class.forName(driver).newInstance();
        conn =  DriverManager.getConnection(jdbcUrl, username, password);
        stmt = conn.createStatement();

        String query =
                "    VARIABLE mybar VARCHAR;\n" +
                        "    begin;\n" +
                        "    :mybar := 'x'; \n" +
                        "    select * from foo where bar = :mybar;\n" +
                        "    end;";
        ResultSet rs = stmt.executeQuery(query);
    } catch (Exception ex) {
        ex.printStackTrace();
    } finally {
        if (conn != null) {
            conn.close();
        }
    }
    throw new RuntimeException("Didn't get results.");
}

¿Hay una solución que funcione sin cambiar la consulta?

Pregunta hecha hace 3 años, 4 meses, 28 días - Por pixelpioneer


2 Respuestas:

  • El problema que estás experimentando se debe a cómo estás manejando las variables bind en tu consulta SQL de Oracle a través de JDBC. Para poder ejecutar una consulta que utiliza variables bind con la sintaxis :varName en Oracle a través de JDBC, debes seguir un enfoque ligeramente diferente al que estás intentando.

    Aquí te muestro cómo puedes modificar tu código para lograrlo sin cambiar la consulta original:

    • En lugar de usar el método `connection.prepareStatement(query)`, puedes usar el método `connection.prepareCall(query)` para preparar la declaración de la llamada a procedimiento.
    • En la consulta SQL, en lugar de usar `VARIABLE mybar VARCHAR`, puedes utilizar un bloque de declaración anónima PL/SQL para asignar el valor a la variable bind y luego ejecutar la consulta. Dentro de este bloque, puedes usar `?` en lugar de `:mybar` para representar la variable bind.

    A continuación, te muestro cómo podrías modificar tu código Java para lograr esto:

    java
    private CallableStatement stmt(Connection connection, String study, LocalDate start, LocalDate end) throws SQLException {
        String query = wrapValidatedQuery(study, start, end);
        return connection.prepareCall("{call begin :mybar := ?; select * from foo where bar = :mybar; end;}");
    }
    
    ...
    
    try(Connection conn = getConnection()) {
        CallableStatement stmt = stmt(conn, study, start, end);
        stmt.setString(1, "x");
        ResultSet resultSet = stmt.executeQuery();
        return Table.read().db(resultSet);
    }
    

    Con estos cambios, deberías poder ejecutar la consulta con variables bind en Oracle a través de JDBC sin necesidad de modificar la consulta original.

    Prueba este enfoque en tu código y, si surge algún problema o tienes más preguntas, no dudes en hacérmelo saber. ¡Estoy aquí para ayudarte!

    Respondida el Dec 19, 2020 a las 16:32 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • Solución:

    Para utilizar variables bind nombradas con JDBC, puedes crear un objeto CallableStatement en lugar de un PreparedStatement. CallableStatement te permite establecer y recuperar valores de variables bind nombradas.

    Código actualizado:

    java
    try (Connection conn = getConnection()) {
        CallableStatement stmt = conn.prepareCall(wrapValidatedQuery(study, start, end));
        stmt.setString("mybar", "x"); // Establece el valor de la variable bind nombrada
        ResultSet resultSet = stmt.executeQuery();
        return Table.read().db(resultSet);
    }
    

    Explicación:

    Reemplaza connection.prepareStatement(query) con conn.prepareCall(wrapValidatedQuery(study, start, end)) para crear un CallableStatement. Utiliza stmt.setString("mybar", "x") para establecer el valor de la variable bind nombrada :mybar. Ejecuta la consulta utilizando stmt.executeQuery().

    Versión independiente:

    java
    public static void main(String[] args) throws Exception {
        String username = "name";
        String password = "pwd";
        Connection conn = null;
        CallableStatement stmt;
        int port = 1521;
        String hostname = "host";
        String dbName = "db_name";
        String jdbcUrl = "jdbc:oracle:thin:@" + hostname + ":" + port + "/" + dbName;
    
        try {
            String driver = "oracle.jdbc.driver.OracleDriver";
            Class.forName(driver).newInstance();
            conn =  DriverManager.getConnection(jdbcUrl, username, password);
            stmt = conn.prepareCall("{call wrapValidatedQuery(?, ?, ?)}");
            stmt.setString(1, "x"); // Establece el valor de la variable bind nombrada
            ResultSet rs = stmt.executeQuery();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (conn != null) {
                conn.close();
            }
        }
        throw new RuntimeException("Didn't get results.");
    }
    

    Nota:*

    El método wrapValidatedQuery debe envolver la consulta con el bloque de código de Oracle para establecer la variable bind nombrada, como se muestra en tu código original.

    Respondida el Dec 19, 2020 a las 16:38 - por Gemini

    Votos positivos: 0 | Votos negativos: 0