Buscar..


Introducción

Esto describe cómo obtener directamente un SqlRowSet utilizando SimpleJdbcCall con un procedimiento almacenado en su base de datos que tiene un parámetro de salida del cursor ,

Estoy trabajando con una base de datos Oracle. Intenté crear un ejemplo que debería funcionar para otras bases de datos, mi ejemplo de Oracle detalla los problemas con Oracle.

Creación SimpleJdbcCall

Normalmente, deseará crear su SimpleJdbcCalls en un servicio.

Este ejemplo asume que su procedimiento tiene un único parámetro de salida que es un cursor; tendrá que ajustar sus parámetros de declaración para que coincidan con su procedimiento.

@Service
public class MyService() {

@Autowired
    private DataSource dataSource;
    
    // Autowire your configuration, for example
    @Value("${db.procedure.schema}")
    String schema;
    
    private SimpleJdbcCall myProcCall;
    
    // create SimpleJdbcCall after properties are configured
    @PostConstruct
    void initialize() {
        this.myProcCall = new SimpleJdbcCall(dataSource)
                        .withProcedureName("my_procedure_name")
                        .withCatalogName("my_package")
                        .withSchemaName(schema)
                        .declareParameters(new SqlOutParameter(
                            "out_param_name",
                            Types.REF_CURSOR, 
                            new SqlRowSetResultSetExtractor()));
    }

    public SqlRowSet myProc() {
        Map<String, Object> out = this.myProcCall.execute();
        return (SqlRowSet) out.get("out_param_name");
    }

}

Hay muchas opciones que puedes usar aquí:

  • withoutProcedureColumnMetaDataAccess () es necesario si ha sobrecargado los nombres de los procedimientos o simplemente no quiere que SimpleJdbcCall se valide en la base de datos.
  • withReturnValue () si el procedimiento tiene un valor de retorno. El primer valor dado a declareParameters define el valor de retorno. Además, si su procedimiento es una función, use withFunctionName y executeFunction al ejecutar.
  • withNamedBinding () si desea dar argumentos usando nombres en lugar de una posición.
  • useInParameterNames () define el orden de los argumentos. Creo que esto puede ser necesario si pasas tus argumentos como una lista en lugar de un mapa del nombre del argumento al valor. Aunque es posible que solo sea necesario si usas withoutProcedureColumnMetaDataAccess ()

Bases de datos Oracle

Hay una serie de problemas con Oracle. Aquí es cómo resolverlos.

Suponiendo que el parámetro de salida de su procedimiento es el ref cursor , obtendrá esta excepción.

java.sql.SQLException: tipo de columna no válida: 2012

Así que cambie Types.REF_CURSOR a OracleTypes.CURSOR en simpleJdbcCall.declareParameters ()


Apoyo a los tipos de Oracle

Es posible que solo necesite hacer esto si tiene ciertos tipos de columnas en sus datos.

El siguiente problema que encontré fue que los tipos propietarios como oracle.sql.TIMESTAMPTZ causaron este error en SqlRowSetResultSetExtractor:

Tipo de SQL no válido para la columna; la excepción anidada es java.sql.SQLException: tipo de SQL no válido para columna

Por lo tanto, necesitamos crear un ResultSetExtractor que admita los tipos de Oracle.
Voy a explicar la razón de la contraseña después de este código.

package com.boost.oracle;

import oracle.jdbc.rowset.OracleCachedRowSet;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet;
import org.springframework.jdbc.support.rowset.SqlRowSet;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * OracleTypes can cause {@link org.springframework.jdbc.core.SqlRowSetResultSetExtractor}
 * to fail due to a Oracle SQL type that is not in the standard {@link java.sql.Types}.
 *
 * Also, types such as {@link oracle.sql.TIMESTAMPTZ} require a Connection when processing
 * the ResultSet; {@link OracleCachedRowSet#getConnectionInternal()} requires a JNDI
 * DataSource name or the username and password to be set.
 *
 * For now I decided to just set the password since changing SpringBoot to a JNDI DataSource
 * configuration is a bit complicated.
 *
 * Created by Arlo White on 2/23/17.
 */
public class OracleSqlRowSetResultSetExtractor implements ResultSetExtractor<SqlRowSet> {

    private String oraclePassword;

    public OracleSqlRowSetResultSetExtractor(String oraclePassword) {
        this.oraclePassword = oraclePassword;
    }

    @Override
    public SqlRowSet extractData(ResultSet rs) throws SQLException, DataAccessException {
        OracleCachedRowSet cachedRowSet = new OracleCachedRowSet();
        // allows getConnectionInternal to get a Connection for TIMESTAMPTZ
        cachedRowSet.setPassword(oraclePassword);
        cachedRowSet.populate(rs);
        return new ResultSetWrappingSqlRowSet(cachedRowSet);
    }

}

Ciertos tipos de Oracle requieren una conexión para obtener el valor de columna de un ResultSet. TIMESTAMPTZ es uno de estos tipos. Entonces, cuando se rowSet.getTimestamp(colIndex) , obtendrás esta excepción:

Causado por: java.sql.SQLException: Una o más de las propiedades de autentificación de RowSet no establecidas en oracle.jdbc.rowset.OracleCachedRowSet.getConnectionInternal (OracleCachedRowSp.P.P.P.). : 3717) en org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet.getTimestamp

Si profundiza en este código, verá que OracleCachedRowSet necesita la contraseña o un nombre de JNDI DataSource para obtener una conexión. Si prefiere la búsqueda JNDI, simplemente verifique que OracleCachedRowSet tenga configurado DataSourceName.

Así que en mi Servicio, yo Autowire en la contraseña y declaro el parámetro de salida así:

new SqlOutParameter("cursor_param_name", OracleTypes.CURSOR, new OracleSqlRowSetResultSetExtractor(oraclePassword))


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow