spring
Erhalten eines SqlRowSet von SimpleJdbcCall
Suche…
Einführung
Hier wird beschrieben, wie Sie ein SqlRowSet direkt über SimpleJdbcCall mit einer in Ihrer Datenbank gespeicherten Prozedur abrufen , die einen Cursor-Ausgabeparameter enthält
Ich arbeite mit einer Oracle-Datenbank. Ich habe versucht, ein Beispiel zu erstellen, das für andere Datenbanken geeignet sein sollte. In meinem Oracle-Beispiel werden Probleme mit Oracle beschrieben.
SimpleJdbcCall-Erstellung
Normalerweise möchten Sie Ihre SimpleJdbcCalls in einem Service erstellen.
In diesem Beispiel wird davon ausgegangen, dass Ihre Prozedur einen einzigen Ausgabeparameter hat, der ein Cursor ist. Sie müssen Ihre declareParameters an Ihre Prozedur anpassen.
@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");
}
}
Es gibt viele Optionen, die Sie hier verwenden können:
- withoutProcedureColumnMetaDataAccess () ist erforderlich, wenn Sie Prozedurnamen überlastet haben oder SimpleJdbcCall nicht mit der Datenbank überprüfen möchten.
- withReturnValue (), wenn die Prozedur einen Rückgabewert hat. Der erste Wert von declareParameters definiert den Rückgabewert. Wenn Ihre Prozedur eine Funktion ist, verwenden Sie bei der Ausführung withFunctionName und executeFunction .
- withNamedBinding (), wenn Sie Argumente mit Namen anstelle von Position angeben möchten.
- useInParameterNames () definiert die Reihenfolge der Argumente. Ich denke, dass dies erforderlich sein kann, wenn Sie Ihre Argumente als Liste anstelle einer Zuordnung von Argumentnamen zum Wert übergeben. Es kann jedoch nur erforderlich sein, wenn Sie ohneProcedureColumnMetaDataAccess () verwenden.
Oracle-Datenbanken
Es gibt eine Reihe von Problemen mit Oracle. So lösen Sie sie.
Angenommen, der Ausgabeparameter für die Prozedur ist der ref cursor
, wird diese Ausnahme angezeigt.
java.sql.SQLException: Ungültiger Spaltentyp: 2012
Ändern Sie also Types.REF_CURSOR
in OracleTypes.CURSOR
in simpleJdbcCall.declareParameters ().
Unterstützung von OracleTypes
Möglicherweise müssen Sie dies nur tun, wenn Sie bestimmte Spaltentypen in Ihren Daten haben.
Das nächste Problem, dem ich begegnete, war, dass proprietäre Typen wie oracle.sql.TIMESTAMPTZ
diesen Fehler in SqlRowSetResultSetExtractor verursacht haben:
Ungültiger SQL-Typ für Spalte; Geschachtelte Ausnahme ist java.sql.SQLException: Ungültiger SQL-Typ für Spalte
Daher müssen wir einen ResultSetExtractor erstellen, der Oracle-Typen unterstützt.
Nach diesem Code werde ich den Grund für das Passwort erläutern.
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);
}
}
Bestimmte Oracle-Typen erfordern eine Verbindung, um den Spaltenwert aus einem ResultSet zu erhalten. TIMESTAMPTZ ist einer dieser Typen. Wenn also rowSet.getTimestamp(colIndex)
aufgerufen wird, erhalten Sie diese Ausnahme:
Verursacht durch: java.sql.SQLException: Eine oder mehrere der authentifizierenden RowSet-Eigenschaften, die nicht unter oracle.jdbc.rowset.OracleCachedRowSet.getConnectionInternal (OracleCachedRowSet.java:560) unter oracle.jdbc.rowset.OracleCachedRowSet.. : 3717) unter org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet.getTimestamp
Wenn Sie in diesen Code einsteigen, werden Sie feststellen, dass OracleCachedRowSet das Kennwort oder einen JNDI-DataSource-Namen benötigt, um eine Verbindung zu erhalten. Wenn Sie die JNDI-Suche bevorzugen, vergewissern Sie sich, dass für OracleCachedRowSet DataSourceName festgelegt ist.
In meinem Dienst habe ich also automatisch das Passwort eingegeben und den Ausgabeparameter wie folgt deklariert:
new SqlOutParameter("cursor_param_name", OracleTypes.CURSOR, new OracleSqlRowSetResultSetExtractor(oraclePassword))