Las clases SimpleJdbcInsert
y SimpleJdbcCall
proporcionan una configuración simplificada
utilizando metadatos de la base de datos que se pueden obtener a través del controlador JDBC. Esto significa menos
trabajo de configuración, aunque puede anular o deshabilitar el procesamiento de metadatos si prefiere proporcionar
detalles en su código.
Insertar datos usando SimpleJdbcInsert
Vamos Comience mirando la
clase SimpleJdbcInsert
con un número mínimo de parámetros de configuración. Debe crear una instancia de
SimpleJdbcInsert
en el método de inicialización de la capa de acceso a datos. En este ejemplo, el
método de inicialización es el método setDataSource
. No es necesario crear una subclase de la clase
SimpleJdbcInsert
. En su lugar, puede crear una nueva instancia y establecer el nombre de la tabla
usando el método withTableName
. Los métodos de configuración para esta clase siguen el estilo fluid
,
que devuelve una instancia de SimpleJdbcInsert
, lo que permite encadenar todos los métodos de
configuración. El siguiente ejemplo utiliza solo un método de configuración (más adelante se mostrarán ejemplos de
múltiples métodos):
public class JdbcActorDao implements ActorDao {
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
insertActor.execute(parameters);
}
// ... métodos adicionales
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource).withTableName("t_actor")
fun add(actor: Actor) {
val parameters = mutableMapOf<String, Any>()
parameters["id"] = actor.id
parameters["first_name"] = actor.firstName
parameters["last_name"] = actor.lastName
insertActor.execute(parameters)
}
// ... métodos adicionales
}
Usado aquí el método execute
toma el habitual java.util.Map
como único parámetro. Es
importante tener en cuenta que las claves utilizadas para el Map
deben coincidir con los nombres de las
columnas de la tabla tal como se definen en la base de datos. Esto ocurre porque los metadatos se leen para crear la
declaración de inserción real.
Obtener claves generadas automáticamente usando SimpleJdbcInsert
El siguiente ejemplo utiliza la misma inserción que Igual que en el ejemplo anterior, pero en lugar de pasar
id
, recupera automáticamente la clave generada y luego la configura para un nuevo objeto
Actor
. Cuando crea un SimpleJdbcInsert
, además de especificar el nombre de la tabla, la
inserción especifica el nombre de la columna de clave generada utilizando el método
usingGeneratedKeyColumns
. La siguiente lista muestra cómo se hace esto:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... métodos adicionales
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor").usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters);
return actor.copy(id = newId.toLong())
}
// ... métodos adicionales
}
La principal diferencia al realizar una inserción usando el El segundo enfoque es que no agrega
id
a Map
, sino que llama al método executeAndReturnKey
. Esto devuelve un
objeto java.lang.Number
que se puede utilizar para crear una instancia del tipo numérico utilizado en
la clase de dominio. Aquí no puede confiar en que todas las bases de datos devuelvan una clase Java específica.
java.lang.Number
es la clase principal que se puede utilizar. Si hay varias columnas generadas
automáticamente o los valores generados no son numéricos, entonces puede usar el KeyHolder
devuelto por
el método executeAndReturnKeyHolder
.
Establezca las columnas para
SimpleJdbcInsert
Puede limitar las columnas que se insertarán especificando una lista de
nombres de columnas usando el método usingColumns
, como se muestra en la siguiente ejemplo:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
Map<String, Object> parameters = new HashMap<String, Object>(2);
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... métodos adicionales
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingColumns("first_name", "last_name")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = mapOf(
"first_name" to actor.firstName,
"last_name" to actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters);
return actor.copy(id = newId.toLong())
}
// ... métodos adicionales
}
El proceso de inserción es el mismo que si recurrió a metadatos para determinar qué columnas usar.
Usar SqlParameterSource
para especificar valores de parámetros
Usar Map
para
especificar valores de parámetros Funciona bastante bien, pero no es la clase más fácil de usar. Spring contiene
varias implementaciones de la interfaz SqlParameterSource
que se pueden usar en su lugar. La primera es
BeanPropertySqlParameterSource
, que es una clase muy útil si tiene una clase compatible con JavaBean
que contenga sus valores. Utiliza el captador apropiado para recuperar los valores de los parámetros. El siguiente
ejemplo muestra cómo utilizar BeanPropertySqlParameterSource
:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... métodos adicionales
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = BeanPropertySqlParameterSource(actor)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
// ... métodos adicionales
}
Otra opción es MapSqlParameterSource
, que es similar a Map
pero proporciona un
método addValue
más conveniente que se puede encadenar. El siguiente ejemplo muestra cómo usarlo:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcInsert insertActor;
public void setDataSource(DataSource dataSource) {
this.insertActor = new SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id");
}
public void add(Actor actor) {
SqlParameterSource parameters = new MapSqlParameterSource()
.addValue("first_name", actor.getFirstName())
.addValue("last_name", actor.getLastName());
Number newId = insertActor.executeAndReturnKey(parameters);
actor.setId(newId.longValue());
}
// ... métodos adicionales
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val insertActor = SimpleJdbcInsert(dataSource)
.withTableName("t_actor")
.usingGeneratedKeyColumns("id")
fun add(actor: Actor): Actor {
val parameters = MapSqlParameterSource()
.addValue("first_name", actor.firstName)
.addValue("last_name", actor.lastName)
val newId = insertActor.executeAndReturnKey(parameters)
return actor.copy(id = newId.toLong())
}
// ... métodos adicionales
}
Como puede ver, la configuración es la misma. Para utilizar estas clases de entrada alternativas, solo necesita cambiar el código de ejecución.
Llamar a un procedimiento almacenado con SimpleJdbcCall
El La clase SimpleJdbcCall
utiliza metadatos en la base de datos para buscar los nombres de los
parámetros in
y out
, por lo que no es necesario declararlos explícitamente. Puede
declarar parámetros si prefiere hacerlo o si tiene parámetros (como ARRAY
o STRUCT
)
que no se asignan automáticamente a la clase Java. El primer ejemplo muestra un procedimiento simple que
devuelve solo valores escalares en el formato VARCHAR
y DATE
de una base de datos
MySQL. El procedimiento de ejemplo lee el registro de actor especificado y devuelve las columnas
first_name
,
last_name
y birth_date
como parámetros out
. El siguiente listado muestra
el último ejemplo:
CREATE PROCEDURE read_actor (
IN in_id INTEGER,
OUT out_first_name VARCHAR(100),
OUT out_last_name VARCHAR(100),
OUT out_birth_date DATE)
BEGIN
SELECT first_name, last_name, birth_date
INTO out_first_name, out_last_name, out_birth_date
FROM t_actor where id = in_id;
END;
El parámetro in_id
contiene el id
del actor que está buscando. Los parámetros
out
devuelven datos leídos de la tabla.
Puede declarar SimpleJdbcCall
de manera similar a SimpleJdbcInsert
. Debe crear una
instancia y configurar la clase en el método de inicialización de su capa de acceso a datos. En comparación con
la clase StoredProcedure
, no es necesario crear subclases y declarar parámetros que se pueden
buscar en los metadatos de la base de datos. El siguiente ejemplo de configuración SimpleJdbcCall
utiliza el procedimiento almacenado anterior (el único parámetro de configuración distinto de
DataSource
es el nombre del procedimiento almacenado):
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadActor;
public void setDataSource(DataSource dataSource) {
this.procReadActor = new SimpleJdbcCall(dataSource)
.withProcedureName("read_actor");
}
public Actor readActor(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
Map out = procReadActor.execute(in);
Actor actor = new Actor();
actor.setId(id);
actor.setFirstName((String) out.get("out_first_name"));
actor.setLastName((String) out.get("out_last_name"));
actor.setBirthDate((Date) out.get("out_birth_date"));
return actor;
}
// ... métodos adicionales
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadActor = SimpleJdbcCall(dataSource)
.withProcedureName("read_actor")
fun readActor(id: Long): Actor {
val source = MapSqlParameterSource().addValue("in_id", id)
val output = procReadActor.execute(source)
return Actor(
id,
output["out_first_name"] as String,
output["out_last_name"] as String,
output["out_birth_date"] as Date)
}
// ... métodos adicionales
}
El código que escribe para realizar la llamada implica la creación de un SqlParameterSource
que
contiene el parámetro IN. Debe hacer coincidir el nombre especificado para el valor de entrada con el nombre del
parámetro declarado en el procedimiento almacenado. No es necesario que coincidan las mayúsculas y minúsculas
porque
se utilizan metadatos para determinar cómo se debe hacer referencia a los objetos de la base de datos en el
procedimiento almacenado. Lo que se especifica en el código fuente de un procedimiento almacenado no es
necesariamente lo mismo que se almacena en la base de datos. Algunas bases de datos convierten los nombres a
mayúsculas, mientras que otras usan minúsculas o usan un caso específico.
El método execute
toma
parámetros IN y devuelve un Map
que contiene cualquier parámetro out
, siendo los clave
los nombres especificados en el procedimiento almacenado. En este caso, estos son out_first_name
,
out_last_name
y out_birth_date
.
La última parte de la execute
El método crea una instancia Actor
que se utilizará
para devolver los datos recibidos. Nuevamente, es
importante utilizar los nombres de los parámetros out
tal como están declarados en el procedimiento
almacenado. Además, el caso de los nombres de los parámetros out
almacenados en el mapa resultante
es
el mismo que el caso de los nombres de los parámetros out
en la base de datos, que pueden diferir
entre
bases de datos. Para hacer que el código sea más independiente de la plataforma, puede realizar búsquedas que no
distingan entre mayúsculas y minúsculas o decirle a Spring que use LinkedCaseInSENSENTIMap
. Para
lograr
esto último, puede crear su propio JdbcTemplate
y establecer la propiedad setResultsMapCaseInSENSITIVE
en true
. Luego puede pasar esta instancia JdbcTemplate
personalizada al constructor de
su
SimpleJdbcCall
. El siguiente ejemplo muestra esta configuración:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadActor;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor");
}
// ... métodos adicionales
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private var procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_actor")
// ... métodos adicionales
}
Al hacer esto, evitará conflictos en el caso utilizado para los nombres de sus parámetros
return
.
Declarar explícitamente parámetros para su uso en SimpleJdbcCall
Anteriormente en este capítulo describimos cómo se infieren los parámetros a partir de los metadatos, pero
puedes
declararlos explícitamente si lo deseas. Puede hacer esto creando y configurando un SimpleJdbcCall
usando el método declareParameters
, que toma un número variable de objetos
SqlParameter
como entrada. Para obtener más información sobre cómo definir SqlParameter
, consulte la siguiente sección.
Puede declarar explícitamente uno, más o todos los parámetros. Los metadatos de parámetros todavía se utilizan
cuando los parámetros no se declaran explícitamente. Para omitir todo el procesamiento de búsqueda de metadatos
para
parámetros potenciales y usar solo los parámetros declarados, puede llamar al método
withoutProcedureColumnMetaDataAccess
como parte de la declaración. Supongamos que se declaran dos o
más
firmas de llamada diferentes para una función de base de datos. En este caso, se llama a
useInParameterNames
para especificar una lista de nombres de parámetros IN que se incluirán en una
firma determinada.
El siguiente ejemplo muestra una llamada a procedimiento completamente declarada y utiliza información del ejemplo anterior:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadActor;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
new SqlOutParameter("out_last_name", Types.VARCHAR),
new SqlOutParameter("out_birth_date", Types.DATE)
);
}
// ... métodos adicionales
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_actor")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("in_id")
.declareParameters(
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
SqlOutParameter("out_last_name", Types.VARCHAR),
SqlOutParameter("out_birth_date", Types.DATE)
)
// ... métodos adicionales
}
La ejecución y los resultados finales son los mismos en estos dos ejemplos. En el segundo ejemplo, todos los detalles se especifican explícitamente, en lugar de utilizar metadatos.
Formas de definir
SqlParameters
Para defina un parámetro para las clases SimpleJdbc
, así como para
las clases de operación RDBMS (discutidas en "Modelado Operaciones JDBC
como
objetos Java ") puede utilizar SqlParameter
o una de sus subclases. Para hacer esto,
generalmente especifica el nombre del parámetro y el tipo SQL en el constructor. El tipo SQL se especifica
utilizando las constantes java.sql.Types
. Anteriormente en este capítulo, vimos declaraciones
similares
a las siguientes:
new SqlParameter("in_id", Types.NUMERIC),
new SqlOutParameter("out_first_name", Types.VARCHAR),
SqlParameter("in_id", Types.NUMERIC),
SqlOutParameter("out_first_name", Types.VARCHAR),
La primera línea con SqlParameter
declara el parámetro IN. Puede usar parámetros IN tanto para
llamadas a procedimientos almacenados como para consultas usando SqlQuery
y sus subclases (que se
analizan en la sección "Introducción
a
SqlQuery
").
La segunda línea (con SqlOutParameter
) declara out
parámetro a utilizar en la llamada
al procedimiento almacenado. También hay un SqlInOutParameter
para los parámetros InOut
(parámetros que proporcionan un procedimiento con un valor IN y que
también
devuelven un valor).
SqlParameter
y SqlInOutParameter
se utilizan para proporcionar valores de entrada. Esto contrasta con la clase StoredProcedure
, que
(por
razones de compatibilidad con versiones anteriores) permite proporcionar valores de entrada para los parámetros
declarados como SqlOutParameter
.
Para los parámetros IN, además del nombre y el tipo de SQL, puede especificar un conjunto para datos numéricos
o un nombre de 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 especificar un SqlReturnType
, que le permite definir
un
manejo personalizado de los valores de retorno.
Llamar a una función almacenada con
SimpleJdbcCall
Llamar Puede definir una función almacenada de la misma manera que puede definir
un procedimiento almacenado, excepto que especifica el nombre de la función en lugar del nombre del
procedimiento.
El método withFunctionName
se utiliza como parte de la configuración para indicar que es necesario
realizar una llamada a la función y luego se genera la cadena adecuada para llamar a la función. Para ejecutar
una
función, utiliza una llamada (executeFunction
) especializada que devuelve el valor de retorno de la
función como un objeto del tipo especificado, lo que significa que no necesita obtener el valor de retorno del
mapa
resultante. Un método auxiliar similar (llamado executeObject
) también está disponible para
procedimientos almacenados que tienen solo un parámetro out
. El siguiente ejemplo (para MySQL) se
basa
en la función almacenada get_actor_name
, que devuelve el nombre completo del actor:
CREATE FUNCTION get_actor_name (in_id INTEGER)
RETURNS VARCHAR(200) READS SQL DATA
BEGIN
DECLARE out_name VARCHAR(200);
SELECT concat(first_name, ' ', last_name)
INTO out_name
FROM t_actor where id = in_id;
RETURN out_name;
END;
Para llamar a esta función, volvemos a crear un SimpleJdbcCall
en el método de inicialización,
como se muestra en el siguiente ejemplo:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall funcGetActorName;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
.withFunctionName("get_actor_name");
}
public String getActorName(Long id) {
SqlParameterSource in = new MapSqlParameterSource()
.addValue("in_id", id);
String name = funcGetActorName.executeFunction(String.class, in);
return name;
}
// ... métodos adicionales
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val jdbcTemplate = JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}
private val funcGetActorName = SimpleJdbcCall(jdbcTemplate)
.withFunctionName("get_actor_name")
fun getActorName(id: Long): String {
val source = MapSqlParameterSource().addValue("in_id", id)
return funcGetActorName.executeFunction(String::class.java, source)
}
// ... métodos adicionales
}
Método utilizado executeFunction
devuelve una String
que contiene el valor de
retorno de la llamada a la función.
Devuelve un ResultSet
o un cursor REF desde un Objeto SimpleJdbcCall
Llamar a un procedimiento almacenado o una función que devuelve un
conjunto de resultados es una tarea un poco confusa. Algunas bases de datos devuelven conjuntos de resultados
durante el procesamiento de resultados JDBC, mientras que otras requieren un parámetro out
registrado
explícitamente de un tipo específico. Ambos enfoques requieren procesamiento adicional para iterar sobre el
conjunto
de resultados y procesar las filas devueltas. Con SimpleJdbcCall
puede utilizar el método returningResultSet
y declarar una implementación RowMapper
que se utilizará para un parámetro determinado. Si se
devuelve
un conjunto de resultados mientras se procesan los resultados, entonces los nombres no se definirán, por lo que
los
resultados devueltos deben coincidir con el orden en que se declaran las implementaciones de
RowMapper
.
El nombre especificado todavía se usa para almacenar la lista procesada de resultados en el mapa resultante que
se
devuelve desde la instrucción execute
.
El siguiente ejemplo (para MySQL) utiliza un
procedimiento almacenado que no acepta ningún parámetro IN y devuelve todas las filas de la tabla
t_actor
:
CREATE PROCEDURE read_all_actors()
BEGIN
SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
END;
Para llamar a este procedimiento, puede declarar un RowMapper
. Dado que la clase que desea
asignar sigue las reglas de JavaBean, puede usar BeanPropertyRowMapper
, que se crea pasando la
clase
requerida para asignar al método newInstance
. El siguiente ejemplo muestra cómo hacer esto:
public class JdbcActorDao implements ActorDao {
private SimpleJdbcCall procReadAllActors;
public void setDataSource(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
.withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor.class));
}
public List getActorsList() {
Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
return (List) m.get("actors");
}
// ... métodos adicionales
}
class JdbcActorDao(dataSource: DataSource) : ActorDao {
private val procReadAllActors = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
isResultsMapCaseInsensitive = true
}).withProcedureName("read_all_actors")
.returningResultSet("actors",
BeanPropertyRowMapper.newInstance(Actor::class.java))
fun getActorsList(): List<Actor> {
val m = procReadAllActors.execute(mapOf<String, Any>())
return m["actors"] as List<Actor>
}
// ... métodos adicionales
}
Llamar a execute
pasa un Map
vacío porque esto la llamada no toma ningún parámetro.
La lista de actores luego se recupera del mapa resultante y se devuelve al código de llamada.