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:

Java

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;
    }
}
Kotlin

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:

Java

private ActorMappingQuery actorMappingQuery;
@Autowired
public void setDataSource(DataSource dataSource) {
    this.actorMappingQuery = new ActorMappingQuery(dataSource);
}
public Customer getCustomer(Long id) {
    return actorMappingQuery.findObject(id);
}
Kotlin

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:

Java

public List<Actor> searchForActors(int age, String namePattern) {
    List<Actor> actors = actorSearchMappingQuery.execute(age, namePattern);
    return actors;
}
Kotlin

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:

Java

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);
    }
}
Kotlin

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:

Java

new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
Kotlin
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:

Java

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;
        }
    }
}
Kotlin

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):

Java

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>());
    }
}
Kotlin

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:

Java

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;
    }
}
Kotlin

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:

Java

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"));
    }
}
Kotlin

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:

Java

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);
    }
}
Kotlin

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))
}