Ricerca…


introduzione

Descrive come ottenere direttamente un SqlRowSet utilizzando SimpleJdbcCall con una stored procedure nel database che ha un parametro di output del cursore ,

Sto lavorando con un database Oracle, ho tentato di creare un esempio che dovrebbe funzionare per altri database, i miei dettagli di esempio Oracle riguardano Oracle.

Creazione di SimpleJdbcCall

In genere, si desidera creare SimpleJdbcCalls in un servizio.

Questo esempio presuppone che la procedura abbia un singolo parametro di output che è un cursore; sarà necessario regolare i parametri declare in modo che corrispondano alla procedura.

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

}

Ci sono molte opzioni che puoi usare qui:

  • withoutProcedureColumnMetaDataAccess () necessario se si hanno nomi di procedura sovraccaricati o semplicemente non si desidera che SimpleJdbcCall convalidi con il database.
  • withReturnValue () se la procedura ha un valore di ritorno. Il primo valore assegnato a declareParameters definisce il valore restituito. Inoltre, se la procedura è una funzione, utilizzare withFunctionName e executeFunction durante l'esecuzione.
  • withNamedBinding () se vuoi dare argomenti usando i nomi invece della posizione.
  • useInParameterNames () definisce l'ordine degli argomenti. Penso che questo potrebbe essere richiesto se si passano i tuoi argomenti come una lista invece di una mappa del nome dell'argomento da valutare. Anche se può essere richiesto solo se si utilizza withoutProcedureColumnMetaDataAccess ()

Database Oracle

Ci sono una serie di problemi con Oracle. Ecco come risolverli.

Supponendo che il parametro di output della procedura sia ref cursor , otterrai questa eccezione.

java.sql.SQLException: tipo di colonna non valido: 2012

Quindi cambia Types.REF_CURSOR in OracleTypes.CURSOR in simpleJdbcCall.declareParameters ()


Supportando OracleTypes

Potresti doverlo fare solo se hai determinati tipi di colonna nei tuoi dati.

Il prossimo problema che ho riscontrato è stato che tipi proprietari come oracle.sql.TIMESTAMPTZ causato questo errore in SqlRowSetResultSetExtractor:

Tipo SQL non valido per la colonna; l'eccezione annidata è java.sql.SQLException: tipo SQL non valido per la colonna

Quindi abbiamo bisogno di creare un ResultSetExtractor che supporti i tipi di Oracle.
Spiegherò il motivo della password dopo questo codice.

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

}

Alcuni tipi Oracle richiedono una connessione per ottenere il valore della colonna da un ResultSet. TIMESTAMPTZ è uno di questi tipi. Quindi, quando viene chiamato rowSet.getTimestamp(colIndex) , otterrai questa eccezione:

Causato da: java.sql.SQLException: una o più proprietà di RowSet di autenticazione non impostate su oracle.jdbc.rowset.OracleCachedRowSet.getConnectionInternal (OracleCachedRowSet.java:560) su oracle.jdbc.rowset.OracleCachedRowSet.getTimestamp (OracleCachedRowSet.java : 3717) all'indirizzo org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet.getTimestamp

Se si digita questo codice, si vedrà che OracleCachedRowSet richiede la password o un nome DataSource JNDI per ottenere una connessione. Se si preferisce la ricerca JNDI, verificare che OracleCachedRowSet abbia impostato DataSourceName.

Quindi nel mio servizio, eseguo l'Autowire nella password e dichiaro il parametro di output in questo modo:

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


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow