spring
Ottenere un SqlRowSet da SimpleJdbcCall
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))