spring
Uzyskiwanie SqlRowSet z SimpleJdbcCall
Szukaj…
Wprowadzenie
Opisuje w jaki sposób bezpośrednio uzyskać SqlRowSet korzystając SimpleJdbcCall z procedury przechowywane w bazie danych, która zawiera parametr wyjściowy kursora
Pracuję z bazą danych Oracle, próbowałem stworzyć przykład, który powinien działać dla innych baz danych, mój przykład Oracle szczegóły problemów z Oracle.
Tworzenie SimpleJdbcCall
Zazwyczaj będziesz chciał utworzyć SimpleJdbcCalls w usłudze.
W tym przykładzie założono, że procedura ma pojedynczy parametr wyjściowy, którym jest kursor; będziesz musiał dostosować parametry deklaracji do swojej procedury.
@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");
}
}
Istnieje wiele opcji, których możesz użyć tutaj:
- bezProcedureColumnMetaDataAccess () potrzebne, jeśli przeciążono nazwy procedur lub po prostu nie chcesz, aby SimpleJdbcCall sprawdzał poprawność względem bazy danych.
- withReturnValue (), jeśli procedura ma wartość zwracaną. Pierwsza wartość podana do deklaracjiParametry określa wartość zwracaną. Ponadto, jeśli twoja procedura jest funkcją, użyj withFunctionName i executeFunction podczas wykonywania.
- withNamedBinding (), jeśli chcesz podać argumenty przy użyciu nazw zamiast pozycji.
- useInParameterNames () definiuje kolejność argumentów. Myślę, że może to być wymagane, jeśli podasz argumenty jako listę zamiast mapy nazwy argumentu do wartości. Chociaż może to być wymagane tylko w przypadku korzystania bezProcedureColumnMetaDataAccess ()
Bazy danych Oracle
Istnieje wiele problemów z Oracle. Oto jak je rozwiązać.
Zakładając, że parametrem wyjściowym procedury jest ref cursor
, otrzymasz ten wyjątek.
java.sql.SQLException: Niepoprawny typ kolumny: 2012
Więc zmień Types.REF_CURSOR
na OracleTypes.CURSOR
w simpleJdbcCall.declareParameters ()
Wspieranie OracleTypes
Może to być konieczne tylko wtedy, gdy masz określone typy kolumn w swoich danych.
Następnym problemem, który napotkałem, było to, że zastrzeżone typy, takie jak oracle.sql.TIMESTAMPTZ
spowodowały ten błąd w SqlRowSetResultSetExtractor:
Niepoprawny typ SQL dla kolumny; wyjątek zagnieżdżony to java.sql.SQLException: Niepoprawny typ SQL dla kolumny
Musimy więc utworzyć ResultSetExtractor, który obsługuje typy Oracle.
Po tym kodzie wyjaśnię powód hasła.
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);
}
}
Niektóre typy Oracle wymagają połączenia, aby uzyskać wartość kolumny z zestawu wyników. TIMESTAMPTZ jest jednym z tych typów. Tak więc po rowSet.getTimestamp(colIndex)
otrzymasz następujący wyjątek:
Przyczyna: java.sql.SQLException: co najmniej jedna z uwierzytelniających właściwości RowSet nie jest ustawiona w pliku oracle.jdbc.rowset.OracleCachedRowSet.getConnectionInternal (OracleCachedRowSet.java:560) w witrynie oracle.jdbc.rowset.OracleCachedRowSet.getTimja : 3717) at org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet.getTimestamp
Jeśli zagłębisz się w ten kod, zobaczysz, że OracleCachedRowSet potrzebuje hasła lub nazwy JNDI DataSource, aby uzyskać połączenie. Jeśli wolisz wyszukiwanie JNDI, po prostu sprawdź, czy OracleCachedRowSet ma ustawiony DataSourceName.
Więc w mojej usłudze autowire w haśle i deklaruję parametr wyjściowy w następujący sposób:
new SqlOutParameter("cursor_param_name", OracleTypes.CURSOR, new OracleSqlRowSetResultSetExtractor(oraclePassword))