spring
Obtenir un SqlRowSet à partir de SimpleJdbcCall
Recherche…
Introduction
Cela décrit comment obtenir directement un SqlRowSet en utilisant SimpleJdbcCall avec une procédure stockée dans votre base de données qui a un paramètre de sortie du curseur ,
Je travaille avec une base de données Oracle, j'ai essayé de créer un exemple qui devrait fonctionner pour d'autres bases de données, mon exemple Oracle détaille les problèmes avec Oracle.
Création de SimpleJdbcCall
En règle générale, vous souhaiterez créer vos SimpleJdbcCalls dans un service.
Cet exemple suppose que votre procédure a un seul paramètre de sortie qui est un curseur; vous devrez ajuster vos declareParameters pour correspondre à votre procédure.
@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");
}
}
Il y a beaucoup d'options que vous pouvez utiliser ici:
- withoutProcedureColumnMetaDataAccess () est nécessaire si vous avez surchargé les noms de procédures ou si vous ne souhaitez pas que SimpleJdbcCall valide la base de données.
- withReturnValue () si la procédure a une valeur de retour. La première valeur donnée à declareParameters définit la valeur de retour. De plus, si votre procédure est une fonction, utilisez withFunctionName et executeFunction lors de l'exécution.
- withNamedBinding () si vous voulez donner des arguments en utilisant des noms au lieu de position.
- useInParameterNames () définit l'ordre des arguments. Je pense que cela peut être nécessaire si vous transmettez vos arguments sous forme de liste au lieu d'une carte de nom d'argument à valoriser. Bien que cela ne soit nécessaire que si vous utilisez withoutProcedureColumnMetaDataAccess ()
Bases de données Oracle
Il existe un certain nombre de problèmes avec Oracle. Voici comment les résoudre.
En supposant que votre paramètre de sortie de procédure soit le ref cursor
, vous obtiendrez cette exception.
java.sql.SQLException: Type de colonne non valide: 2012
Changez donc Types.REF_CURSOR
à OracleTypes.CURSOR
dans simpleJdbcCall.declareParameters ()
Prise en charge d'OracleTypes
Vous n'avez peut-être besoin de le faire que si vous avez certains types de colonnes dans vos données.
Le problème suivant est que les types propriétaires tels que oracle.sql.TIMESTAMPTZ
provoqué cette erreur dans SqlRowSetResultSetExtractor:
Type SQL non valide pour la colonne; l'exception imbriquée est java.sql.SQLException: type SQL non valide pour la colonne
Nous devons donc créer un ResultSetExtractor prenant en charge les types Oracle.
Je vais expliquer la raison du mot de passe après ce code.
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);
}
}
Certains types Oracle nécessitent une connexion pour obtenir la valeur de colonne à partir d'un ResultSet. TIMESTAMPTZ est l'un de ces types. Ainsi, lorsque rowSet.getTimestamp(colIndex)
est appelé, vous obtiendrez cette exception:
Causée par: java.sql.SQLException: une ou plusieurs propriétés RowSet d'authentification non définies sur oracle.jdbc.rowset.OracleCachedRowSet.getConnectionInternal (OracleCachedRowSet.java:560) sur oracle.jdbc.rowset.OracleCachedRowSet.getTimestamp (OracleCachedRowSet.java : 3717) at org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet.getTimestamp
Si vous creusez dans ce code, vous verrez que OracleCachedRowSet a besoin du mot de passe ou d'un nom de source de données JNDI pour obtenir une connexion. Si vous préférez la recherche JNDI, vérifiez simplement que OracleCachedRowSet a défini DataSourceName.
Donc, dans mon service, j'invoque le mot de passe et déclare le paramètre de sortie comme suit:
new SqlOutParameter("cursor_param_name", OracleTypes.CURSOR, new OracleSqlRowSetResultSetExtractor(oraclePassword))