El paquete org.springframework.jdbc.object
contiene clases que le permiten acceder a la base de datos de
una manera más orientada a objetos. Por ejemplo, puede ejecutar consultas y obtener los resultados como una lista
que contiene objetos comerciales con datos de columnas relacionales asignados a las propiedades del objeto
comercial. También puede ejecutar procedimientos almacenados y ejecutar declaraciones de actualización, eliminación
e inserción.
Muchos desarrolladores de Spring creen que las diversas clases de
operaciones RDBMS se describe a continuación (excepto la clase StoredProcedure
),
la mayoría a menudo se pueden reemplazar llamadas directas a JdbcTemplate
. A menudo es más fácil
escribir un método DAO que llame a un método desde JdbcTemplate
directamente (en lugar de encapsular la
solicitud en una clase completa).
Sin embargo, si Si considera muy útil utilizar clases de operación RDBMS, debe continuar usando estas clases.
Comprensión de SqlQuery
SqlQuery
es una clase reutilizable y segura para
subprocesos, que encapsula la consulta SQL. Las subclases deben implementar el método newRowMapper(..)
para proporcionar una instancia de un RowMapper
que pueda crear un objeto para cada fila resultante de
atravesar el ResultSet
.que se crea durante la ejecución de la consulta. La clase
SqlQuery
rara vez se usa directamente porque la subclase MappingSqlQuery
proporciona
una implementación mucho más conveniente para asignar cadenas a clases Java. Otras implementaciones que amplían
SqlQuery
son MappingSqlQueryWithParameters
y UpdatableSqlQuery
.
Uso de MappingSqlQuery
MappingSqlQuery
es una consulta reutilizable en la que
subclases concretas deben implementar el método abstracto mapRow(..)
para asignar cada fila del
proporcionado ResultSet
en un objeto del tipo especificado. El siguiente ejemplo muestra una consulta
personalizada que
asigna datos de la relación t_actor a una instancia de la clase Actor:
public class ActorMappingQuery extends MappingSqlQuery<Actor> {
public ActorMappingQuery(DataSource ds) {
super(ds, "select id, first_name, last_name from t_actor where id = ?");
declareParameter(new SqlParameter("id", Types.INTEGER));
compile();
}
@Override
protected Actor mapRow(ResultSet rs, int rowNumber) throws SQLException {
Actor actor = new Actor();
actor.setId(rs.getLong("id"));
actor.setFirstName(rs.getString("first_name"));
actor.setLastName(rs.getString("last_name"));
return actor;
}
}
class ActorMappingQuery(ds: DataSource) : MappingSqlQuery<Actor>(ds, "select id, first_name, last_name from t_actor where id = ?") {
init {
declareParameter(SqlParameter("id", Types.INTEGER))
compile()
}
override fun mapRow(rs: ResultSet, rowNumber: Int) = Actor(
rs.getLong("id"),
rs.getString("first_name"),
rs.getString("last_name")
)
}
La clase extiende MappingSqlQuery
, parametrizado por el tipo Actor
. El constructor
de esta solicitud personalizada toma DataSource
como único parámetro. En este constructor, puede llamar
al constructor de la superclase con DataSource
y el SQL que debe ejecutarse para recuperar las filas
para esta consulta. Este SQL se utiliza para crear un PreparedStatement
, por lo que puede contener
marcadores de posición para cualquier parámetro que se pasará en tiempo de ejecución. Debes declarar cada parámetro
usando el método declareParameter
, pasándole un SqlParameter
. SqlParameter
toma el nombre y el tipo JDBC definidos en java.sql.Types
. Una vez definidos todos los parámetros,
puede llamar al método compile()
para preparar y posteriormente ejecutar la declaración. Una vez
compilada, esta clase es segura para subprocesos, por lo que siempre que estas instancias se creen cuando se
inicializa DAO, se pueden almacenar como variables de instancia y reutilizar. El siguiente ejemplo muestra cómo
definir dicha clase:
private ActorMappingQuery actorMappingQuery;
@Autowired
public void setDataSource(DataSource dataSource) {
this.actorMappingQuery = new ActorMappingQuery(dataSource);
}
public Customer getCustomer(Long id) {
return actorMappingQuery.findObject(id);
}
private val actorMappingQuery = ActorMappingQuery(dataSource)
fun getCustomer(id: Long) = actorMappingQuery.findObject(id)
El método del ejemplo anterior obtiene el cliente con id
pasado como único parámetro. Como solo
necesitamos devolver un objeto, llamamos al método auxiliar findObject
con id
como
parámetro. Si, en cambio, hubiera una consulta que devolviera una lista de objetos y tomara parámetros adicionales,
usaríamos uno de los métodos execute
, que toma una matriz de valores de parámetros pasados como
argumentos de longitud variable. El siguiente ejemplo muestra este método:
public List<Actor> searchForActors(int age, String namePattern) {
List<Actor> actors = actorSearchMappingQuery.execute(age, namePattern);
return actors;
}
fun searchForActors(age: Int, namePattern: String) =
actorSearchMappingQuery.execute(age, namePattern)
Uso de SqlUpdate
La clase SqlUpdate
encapsula un SQL actualizar. Al igual
que una consulta, el objeto de actualización es reutilizable y, como todas las clases RdbmsOperation
,
la actualización puede tener parámetros y está definida en SQL. Esta clase proporciona una serie de métodos update(..)
similares a los métodos execute(..)
de los objetos de solicitud. La clase SqlUpdate
es
concreta. Se puede subclasificar, por ejemplo, para agregar un método de actualización personalizado. Sin embargo,
no es necesario crear una subclase de la clase SqlUpdate
porque se puede parametrizar fácilmente
especificando SQL y declarando parámetros. El siguiente ejemplo crea un método de actualización personalizado
llamado execute
:
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;
public class UpdateCreditRating extends SqlUpdate {
public UpdateCreditRating(DataSource ds) {
setDataSource(ds);
setSql("update customer set credit_rating = ? where id = ?");
declareParameter(new SqlParameter("creditRating", Types.NUMERIC));
declareParameter(new SqlParameter("id", Types.NUMERIC));
compile();
}
/**
* @param id para el Cliente que se actualiza
* @param rating el nuevo valor de calificación crediticia
* @return el número de filas actualizadas
*/
public int execute(int id, int rating) {
return update(rating, id);
}
}
import java.sql.Types
import javax.sql.DataSource
import org.springframework.jdbc.core.SqlParameter
import org.springframework.jdbc.object.SqlUpdate
class UpdateCreditRating(ds: DataSource) : SqlUpdate() {
init {
setDataSource(ds)
sql = "update customer set credit_rating = ? where id = ?"
declareParameter(SqlParameter("creditRating", Types.NUMERIC))
declareParameter(SqlParameter("id", Types.NUMERIC))
compile()
}
/**
* @param id para el cliente que se actualiza
* @param rating nuevo valor de calificación crediticia
* @return número de filas actualizadas
*/
fun execute(id: Int, rating: Int): Int {
return update(rating, id)
}
}
Uso de StoredProcedure
La clase StoredProcedure
es una superclase abstracta
para el objeto de procedimiento almacenado RDBMS abstracciones.
La
propiedad heredada sql
es el nombre del procedimiento almacenado en el RDBMS.
Definir un
parámetro para el StoredProcedure
clase, puede usar SqlParameter
o una de sus subclases.
Debe establecer el nombre del parámetro y el tipo de SQL en el constructor, como se muestra en el siguiente
fragmento de código:
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
S
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
El tipo de SQL se especifica utilizando java.sql.Tipos
constantes.
La primera línea (con
SqlParameter
) declara el parámetro IN. Puede utilizar parámetros IN tanto para llamadas a
procedimientos almacenados como para consultas utilizando SqlQuery
y sus subclases.
La segunda
línea (con SqlOutParameter
) declara parámetro out
que se utilizará en la llamada al
procedimiento almacenado. También hay un SqlInOutParameter
para los parámetros InOut
(parámetros que proporcionan un valor in
a un procedimiento y también devuelven un valor).
Para
los parámetros in
, además del nombre y el tipo de SQL, puede configurar la escala para datos numéricos
o el nombre del tipo para tipos de bases de datos creados de forma personalizada. Para los parámetros
out
, puede especificar un RowMapper
para manejar la visualización de las filas devueltas
por el cursor REF
. Otra opción es establecer SqlReturnType
, que le permite definir un
manejo personalizado de los valores de retorno.
El siguiente ejemplo simple de DAO utiliza StoredProcedure
para llamar al SqlReturnType
(sysdate)()
function), que viene con cualquier base de datos
Oracle. Para utilizar la funcionalidad del procedimiento almacenado, debe crear una clase que extienda StoredProcedure
.
En este ejemplo, la clase StoredProcedure
es una clase interna. Sin embargo, si necesita reutilizar
StoredProcedure
, puede declararlo como una clase de nivel superior. Este ejemplo no tiene parámetros de
entrada, pero el parámetro de salida se declara como un tipo de fecha utilizando la clase
SqlOutParameter
. El método execute()
ejecuta el procedimiento y extrae la fecha devuelta
del Map
resultante. El Map
resultante tiene una entrada para cada parámetro de salida
declarado (en este caso solo uno), utilizando el nombre del parámetro como clave. La siguiente lista muestra nuestra
clase StoredProcedure personalizada:
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
public class StoredProcedureDao {
private GetSysdateProcedure getSysdate;
@Autowired
public void init(DataSource dataSource) {
this.getSysdate = new GetSysdateProcedure(dataSource);
}
public Date getSysdate() {
return getSysdate.execute();
}
private class GetSysdateProcedure extends StoredProcedure {
private static final String SQL = "sysdate";
public GetSysdateProcedure(DataSource dataSource) {
setDataSource(dataSource);
setFunction(true);
setSql(SQL);
declareParameter(new SqlOutParameter("date", Types.DATE));
compile();
}
public Date execute() {
La cadena "sysdate" no tiene parámetros de entrada, por lo que devuelve un mapa vacío...
Map<String, Object> results = execute(new HashMap<String, Object>());
Date sysdate = (Date) results.get("date");
return sysdate;
}
}
}
import java.sql.Types
import java.util.Date
import java.util.Map
import javax.sql.DataSource
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.object.StoredProcedure
class StoredProcedureDao(dataSource: DataSource) {
private val SQL = "sysdate"
private val getSysdate = GetSysdateProcedure(dataSource)
val sysdate: Date
get() = getSysdate.execute()
private inner class GetSysdateProcedure(dataSource: DataSource) : StoredProcedure() {
init {
setDataSource(dataSource)
isFunction = true
sql = SQL
declareParameter(SqlOutParameter("date", Types.DATE))
compile()
}
fun execute(): Date {
// La cadena "sysdate" no tiene parámetros de entrada, por lo que se devuelve un mapa vacío...
val results = execute(mutableMapOf<String, Any>())
return results["date"] as Date
}
}
}
El siguiente ejemplo de StoredProcedure
tiene dos parámetros de salida (en este caso Cursores REF
de Oracle):
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.object.StoredProcedure;
public class TitlesAndGenresStoredProcedure extends StoredProcedure {
private static final String SPROC_NAME = "AllTitlesAndGenres";
public TitlesAndGenresStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
compile();
}
public Map<String, Object> execute() {
// nuevamente, este procedimiento almacenado no tiene parámetros de entrada, por lo que devuelve un mapa vacío
return super.execute(new HashMap<String, Object>());
}
}
import java.util.HashMap
import javax.sql.DataSource
import oracle.jdbc.OracleTypes
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.object.StoredProcedure
class TitlesAndGenresStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) {
companion object {
private const val SPROC_NAME = "AllTitlesAndGenres"
}
init {
declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper()))
declareParameter(SqlOutParameter("genres", OracleTypes.CURSOR, GenreMapper()))
compile()
}
fun execute(): Map<String, Any> {
// nuevamente, este procedimiento almacenado no tiene parámetros de entrada, por lo que devuelve un mapa vacío
return super.execute(HashMap<String, Any>())
}
}
Observe cómo las sobrecargas del método declareParameter(..)
que se usaron en el constructor
TitlesAndGenresStoredProcedure
se pasan a instancias del RowMapper
implementación. Esta
es una manera muy conveniente y eficiente de reutilizar la funcionalidad existente. Los siguientes dos ejemplos
contienen código para dos implementaciones de RowMapper
.
La clase TitleMapper
asigna una ResultSet
con un objeto de dominio Título
para cada fila en el
ResultSet
proporcionado de la siguiente manera:
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Title;
import org.springframework.jdbc.core.RowMapper;
public final class TitleMapper implements RowMapper<Title> {
public Title mapRow(ResultSet rs, int rowNum) throws SQLException {
Title title = new Title();
title.setId(rs.getLong("id"));
title.setName(rs.getString("name"));
return title;
}
}
import java.sql.ResultSet
import com.foo.domain.Title
import org.springframework.jdbc.core.RowMapper
class TitleMapper : RowMapper<Title> {
override fun mapRow(rs: ResultSet, rowNum: Int) =
Title(rs.getLong("id"), rs.getString("name"))
}
La clase GenreMapper
asigna un ResultSet
a un objeto de dominio Genre
para cada fila en el ResultSet
proporcionado. de la siguiente manera:
import java.sql.ResultSet;
import java.sql.SQLException;
import com.foo.domain.Genre;
import org.springframework.jdbc.core.RowMapper;
public final class GenreMapper implements RowMapper<Genre> {
public Genre mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Genre(rs.getString("name"));
}
}
import java.sql.ResultSet
import com.foo.domain.Genre
import org.springframework.jdbc.core.RowMapper
class GenreMapper : RowMapper<Genre> {
override fun mapRow(rs: ResultSet, rowNum: Int): Genre {
return Genre(rs.getString("name"))
}
}
Para pasar parámetros a un procedimiento almacenado que contiene uno o más parámetros de entrada en su
definición RDBMS, puede escribir un método execute(..)
fuertemente tipado que se delegará al execute(Map)
sin tipo método en la superclase, como se muestra en el siguiente ejemplo:
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import oracle.jdbc.OracleTypes;
import org.springframework.jdbc.core.SqlOutParameter;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.StoredProcedure;
public class TitlesAfterDateStoredProcedure extends StoredProcedure {
private static final String SPROC_NAME = "TitlesAfterDate";
private static final String CUTOFF_DATE_PARAM = "cutoffDate";
public TitlesAfterDateStoredProcedure(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE);
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
compile();
}
public Map<String, Object> execute(Date cutoffDate) {
Map<String, Object> inputs = new HashMap<String, Object>();
inputs.put(CUTOFF_DATE_PARAM, cutoffDate);
return super.execute(inputs);
}
}
import java.sql.Types
import java.util.Date
import javax.sql.DataSource
import oracle.jdbc.OracleTypes
import org.springframework.jdbc.core.SqlOutParameter
import org.springframework.jdbc.core.SqlParameter
import org.springframework.jdbc.object.StoredProcedure
class TitlesAfterDateStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) {
companion object {
private const val SPROC_NAME = "TitlesAfterDate"
private const val CUTOFF_DATE_PARAM = "cutoffDate"
}
init {
declareParameter(SqlParameter(CUTOFF_DATE_PARAM, Types.DATE))
declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper()))
compile()
}
fun execute(cutoffDate: Date) = super.execute(
mapOf<String, Any>(CUTOFF_DATE_PARAM to cutoffDate))
}