Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Capitulo 8 - Soporte Spring JDBC - 0 PDF
Capitulo 8 - Soporte Spring JDBC - 0 PDF
En los captulos anteriores, hemos visto lo fcil que es construir una aplicacin totalmente
administrada por Spring. Ahora usted tiene una slida comprensin de la configuracin bean y de la
Programacin Orientada a Aspectos (AOP), en otras palabras, usted sabe cmo cablear la aplicacin
entera usando Spring. Sin embargo, falta una de las piezas del rompecabezas: cmo conseguir los
datos que maneja la aplicacin?
Adems de utilidades desechables simples de lnea de comandos, casi todas las aplicaciones
necesitan conservar los datos en algn tipo de almacn de datos. El almacn de datos ms usual y
conveniente es una base de datos relacional.
Las bases de datos relacionales de cdigo abierto ms destacadas son, quizs, MySQL
(www.mysql.com) y PostgreSQL (www.postgresql.org). En trminos de caractersticas RDBMS
proporcionadas, ambas bases de datos son aproximadamente los mismos. MySQL es por lo general
ms ampliamente utilizado para el desarrollo de aplicaciones web, especialmente en la plataforma
Linux. Por otro lado, PostgreSQL es ms amigable para los desarrolladores de Oracle, debido a que su
lenguaje procedural, PL/pgSQL, est muy cerca de lenguaje PL/SQL de Oracle.
Incluso si usted elige la base de datos ms rpida y confiable, no puede permitirse perder la
velocidad ofrecida y flexibilidad usando una capa de acceso a datos mal diseada e implementada.
Las aplicaciones tienden a usar la capa de acceso a datos con mucha frecuencia, por lo que los cuellos
de botella innecesarios en el cdigo de acceso a datos afecta a toda la aplicacin, no importa lo bien
diseada que este.
En este captulo, te mostramos cmo puedes usar Spring para simplificar la implementacin
del cdigo de acceso a datos usando JDBC. Empezamos viendo la horrible cantidad de cdigo que
normalmente se necesita para escribir sin Spring y luego compararla con una clase de acceso a datos
implementada usando clases de acceso a datos de Spring. El resultado es realmente sorprendenteSpring te permite utilizar todo el poder de consultas SQL concertadas por el humano, mientras reduce
al mnimo la cantidad de cdigo de apoyo que usted necesita para poner en prctica. En concreto,
hablaremos de lo siguiente:
Comparando cdigo JDBC tradicional y el soporte de Spring JDBC: Exploramos cmo Spring
Conectndose a la base de datos: Aunque no entremos en cada pequeo detalle del manejo
Recuperando y mapeando los datos a objetos Java: Te mostramos cmo recuperar los datos y
simplifica el antiguo estilo del cdigo JDBC manteniendo la misma funcionalidad. Usted
tambin ver cmo Spring accede a la API JDBC de bajo nivel y cmo esta API de bajo nivel es
mapeada a las clases prcticas, como JdbcTemplate.
Antes de proceder a la discusin, nos gustara introducir un modelo de datos muy simple que se
utilizar para los ejemplos de este captulo, as como para los prximos captulos cuando se hable de
otras tcnicas de acceso a datos (ampliaremos el modelo en consecuencias para satisfacer las
necesidades de cada tema a medida que avanzamos).
El modelo es una base de datos CONTACT muy sencilla. Hay dos tablas. La primera es la tabla
CONTACT, que almacena la informacin de contacto de una persona, y la otra tabla es
CONTACT_TEL_DETAIL, que almacena los detalles telefnicos de un contacto. Cada contacto puede
tener cero o ms nmeros de telfono, en otras palabras, es una relacin de uno-a-muchos entre
CONTACT y CONTACT_TEL_DETAIL. La informacin de un contacto incluye su nombre, apellido y fecha
de nacimiento, mientras que una parte de informacin telefnica detallada incluye el tipo de telfono
(Mvil, Casa, etc.) y el nmero de telfono correspondiente. La Figura 8-1 muestra el diagrama
entidad-relacin (ER) de la base de datos.
Como puede ver, ambas tablas tienen una columna ID que ser asignada automticamente por la
base de datos durante la insercin. Para la tabla CONTACT_TEL_DETAIL, hay una relacin de llave
fornea con la tabla CONTACT, que est vinculada por la columna CONTACT_ID con la llave primaria de
la tabla CONTACT (es decir, la columna ID).
Nota: El modelo de datos fue creado usando un plugin de Eclipse llamado Clay Mark II. La versin
sin licencia puede ser utilizada libremente para crear modelos de datos para bases de datos
gratuitas y de cdigo abierto como MySQL, PostgreSQL, HSQL, Derby, y etctera. Usted no
necesita el plug-in para ejecutar el cdigo de ejemplo, ya que los scripts para crear las tablas se
proporcionan con el cdigo de ejemplo. Sin embargo, el archivo del diagrama del modelo (situado
en ch8/data-model/prospring3-ch8-datamodel.clay) se incluy en el cdigo de ejemplo, y si
usted est interesado, puede instalar el plug-in y ver el diagrama (por favor consulte
www.azzurri.co.jp para ms detalles).
El Listado 8-1 muestra el script para la creacin de la base de datos (que es compatible con
MySQL).
Listado 8-2. Script Sencillo para Crear el Modelo de Datos (schema.sql)
CREATE TABLE CONTACT (
ID INT NOT NULL AUTO_INCREMENT
, FIRST_NAME VARCHAR(60) NOT NULL
, LAST_NAME VARCHAR(40) NOT NULL
, BIRTH_DATE DATE
, UNIQUE UQ_CONTACT_1 (FIRST_NAME, LAST_NAME)
, PRIMARY KEY (ID)
);
CREATE TABLE CONTACT_TEL_DETAIL (
ID INT NOT NULL AUTO_INCREMENT
, CONTACT_ID INT NOT NULL
, TEL_TYPE VARCHAR(20) NOT NULL
, TEL_NUMBER VARCHAR(20) NOT NULL
, UNIQUE UQ_CONTACT_TEL_DETAIL_1 (CONTACT_ID, TEL_TYPE)
, PRIMARY KEY (ID)
, CONSTRAINT FK_CONTACT_TEL_DETAIL_1 FOREIGN KEY (CONTACT_ID)
REFERENCES CONTACT (ID)
);
El Listado 8-2 muestra el script que carga algunos datos de ejemplo en las tablas CONTACT y
CONTACT_TEL_DETAIL.
Listado 8-3. Script Sencillo para la Cargar los Datos (test-data.sql)
insert into contact (first_name, last_name, birth_date) values ('Clarence', 'Ho', '198007-30');
insert into contact (first_name, last_name, birth_date) values ('Scott', 'Tiger', '199011-02');
insert into contact (first_name, last_name, birth_date) values ('John', 'Smith', '1964-0228');
insert into contact_tel_detail (contact_id, tel_type, tel_number) values (1, 'Mvil',
'1234567890');
insert into contact_tel_detail (contact_id, tel_type, tel_number) values (1, 'Casa',
'1234567890');
insert into contact_tel_detail (contact_id, tel_type, tel_number) values (2, 'Casa',
'1234567890');
En secciones posteriores de este captulo, usted ver ejemplos para recuperar los datos de la
base de datos a travs de JDBC y asignar directamente el resulSet en objetos Java (es decir, POJOs).
Los listado 8-3 y 8-4 muestran las clases de dominio Contact y ContactTelDetail,
respectivamente.
Listado 8-3. El Objeto de Dominio Contact
package com.apress.prospring3.ch8.domain;
import java.io.Serializable;
import java.sql.Date;
import java.util.List;
public class Contact implements Serializable {
private
private
private
private
private
Long id;
String firstName;
String lastName;
Date birthDate;
List<ContactTelDetail> contactTelDetails;
Long id;
Long contactId;
String telType;
String telNumber;
Vamos a empezar con una interfaz muy sencilla para ContactDao que encapsula todos los
servicios de acceso a datos para la informacin del contacto. El listado 8-5 muestra la interfaz
ContactDao.
Listado 8-5. La Interfaze ContactDao
package com.apress.prospring3.ch8.dao;
import java.util.List;
import com.apress.prospring3.ch8.domain.Contact;
public interface ContactDao {
public
public
public
public
public
List<Contact> findAll();
List<Contact> findByFirstName(String firstName);
void insert(Contact contact);
void update(Contact contact);
void delete(Long contactId);
En la interfaz anterior, se definen dos mtodos de bsqueda y los mtodos insert, update, y
delete, respectivamente. Que corresponden con los trminos CRUD (Create, Read, Update, Delete).
Por ltimo, para facilitar las pruebas, vamos a modificar las propiedades de log4j para activar
el nivel log a DEBUG para todas las clases. En el nivel DEBUG, el mdulo de Spring JDBC da salida a
todas las sentencias SQL subyacentes que se dispararon a la base de datos para que usted sepa
exactamente lo que est sucediendo, es especialmente til para solucionar errores de sintaxis SQL. El
listado 8-6 muestra el archivo log4j.properties (que reside dentro de /src/main/resources con
los archivos de cdigo fuente para el proyecto del Captulo 8) con el nivel DEBUG activado.
Listado 8-6. El Archivo log4j.properties
log4j.rootCategory=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %40.40c:%4L - %m%n
Nota: En STS, despus de que un proyecto de plantilla de Spring es creado, STS generar un
archivo log4j.properties en la carpeta src/test/resources. Usted puede simplemente
mover el archivo a la carpeta src/main/resources y modificarlo, o puede eliminar el que est en
src/test/resources
y
crear
el
archivo
log4j.properties
en
el
directorio
src/main/resources.
JDBC proporciona un medio estndar para que las aplicaciones Java puedan acceder a los
datos almacenados en una base de datos. El ncleo de la infraestructura JDBC es un controlador que
es especfico para cada base de datos, es este controlador el que permite que el cdigo Java tenga
acceso a la base de datos.
Una vez que un controlador es cargado, se registra as mismo con una clase
java.sql.DriverManager. Esta clase maneja una lista de controladores y proporciona mtodos
estticos para establecer conexiones con la base de datos. El mtodo getConnection() de
DriverManager devuelve una implementacin del controlador de la interfaz java.sql.Connection.
Esta interfaz le permite ejecutar sentencias SQL a la base de datos.
El framework JDBC es bastante complejo y bien probado, sin embargo, con esta complejidad
vienen dificultades en el desarrollo. El primer nivel de complejidad radica en hacer que su cdigo
administre las conexiones a la base de datos. Una conexin es un recurso escaso y es muy costoso de
establecer. Generalmente, la base de datos crea un thread (hilo) o genera un proceso hijo por cada
Este cdigo no es muy completo, pero te da una idea de los pasos que necesitas para manejar
una conexin JDBC. Este cdigo no considera incluso tratar con pool de conexiones, que es una
tcnica comn para gestionar conexiones a bases de datos con ms eficacia. No hablamos sobre pool
de conexiones en este punto (pool de conexiones se discute en la seccin "Conexiones a Base de
Datos y DataSources" ms adelante en este captulo), en cambio, en el Listado 8-8, mostramos una
implementacin de los mtodos findAll(), insert() y delete() de la interfaz ContactDao usando
JDBC puro.
java.sql.Connection;
java.sql.DriverManager;
java.sql.PreparedStatement;
java.sql.ResultSet;
java.sql.SQLException;
java.sql.Statement;
java.util.ArrayList;
java.util.List;
import com.apress.prospring3.ch8.dao.ContactDao;
import com.apress.prospring3.ch8.domain.Contact;
public class PlainContactDao implements ContactDao {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException ex) {
// noop
}
}
private Connection getConnection() throws SQLException {
return DriverManager.getConnection(
"jdbc:mysql://localhost:3306/prospring3_ch8", "prospring3",
"prospring3");
}
private void closeConnection(Connection connection) {
if (connection == null) return;
try {
connection.close();
} catch (SQLException ex) {
// noop
}
}
public List<Contact> findAll() {
List<Contact> result = new ArrayList<Contact>();
Connection connection = null;
try {
connection = getConnection();
PreparedStatement statement =
connection.prepareStatement("select * from contact");
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
Contact contact = new Contact();
contact.setId(resultSet.getLong("id"));
contact.setFirstName(resultSet.getString("first_name"));
contact.setLastName(resultSet.getString("last_name"));
contact.setBirthDate(resultSet.getDate("birth_date"));
result.add(contact);
}
} catch (SQLException ex) {
ex.printStackTrace();
} finally {
closeConnection(connection);
}
return result;
}
public List<Contact> findByFirstName(String firstName) {
return null;
}
public void insert(Contact contact) {
Connection connection = null;
try {
connection = getConnection();
PreparedStatement statement = connection.prepareStatement(
"insert into Contact (first_name, last_name, birth_date) values (?, ?,
?)",
Statement.RETURN_GENERATED_KEYS);
statement.setString(1, contact.getFirstName());
statement.setString(2, contact.getLastName());
statement.setDate(3, contact.getBirthDate());
statement.execute();
ResultSet generatedKeys = statement.getGeneratedKeys();
if (generatedKeys.next()) {
contact.setId(generatedKeys.getLong(1));
}
} catch (SQLException ex) {
ex.printStackTrace();
} finally {
closeConnection(connection);
}
}
public void update(Contact contact) {
}
public void delete(Long contactId) {
Connection connection = null;
try {
connection = getConnection();
PreparedStatement statement = connection.prepareStatement(
"delete from contact where id=?");
statement.setLong(1, contactId);
statement.execute();
} catch (SQLException ex) {
ex.printStackTrace();
} finally {
closeConnection(connection);
}
}
}
El Listado 8-9 muestra una prueba del programa principal con la anterior implementacin DAO
en accin.
Listado 8-9. Probando la Implementacin de JDBC Puro
package com.apress.prospring3.ch8;
import java.sql.Date;
import java.util.GregorianCalendar;
import java.util.List;
import com.apress.prospring3.ch8.dao.ContactDao;
import com.apress.prospring3.ch8.dao.plain.PlainContactDao;
import com.apress.prospring3.ch8.domain.Contact;
public class PlainJdbcSample {
private static ContactDao contactDao = new PlainContactDao();
public static void main(String[] args) {
// Listar todos los contactos
System.out.println("Listando los datos iniciales de contact:");
listAllContacts();
System.out.println();
// Insertar un nuevo contacto
System.out.println("Insertar un nuevo contacto");
Contact contact = new Contact();
contact.setFirstName("Jacky");
contact.setLastName("Chan");
contact.setBirthDate(new Date((new GregorianCalendar(2001, 10, 1))
.getTime().getTime()));
contactDao.insert(contact);
System.out.println(
"Listando los datos de contact despus de crear el contacto nuevo:");
listAllContacts();
System.out.println();
// Eliminar el contacto nuevo recin creado
System.out.println("Eliminando el contacto recin creado");
contactDao.delete(contact.getId());
System.out.println(
"Listando los datos de contact despus de eliminar el contacto nuevo:");
listAllContacts();
}
private static void listAllContacts() {
List<Contact> contacts = contactDao.findAll();
for (Contact contact : contacts) {
System.out.println(contact);
}
}
}
GroupID
mysql
Artifact ID
mysql-connector-java
Version
5.1.18
Description
Librera del Controlador MySQL Java
Al ejecutar el programa en el Listado 8-9 dar el siguiente resultado (suponiendo que usted tiene una
base de datos MySQL instalada localmente, con una base de datos llamada prospring3_ch8 con un
nombre de usuario y contrasea establecida a prospring3, debera ser capaz de acceder al esquema
de la base de datos, y usted debera ejecutar los scripts schema.sql y test-data.sql contra la base
de datos para crear las tablas y cargar los datos iniciales):
Listando
Contacto
Contacto
Contacto
Insertar
Listando
Contacto
Contacto
Contacto
Contacto
un nuevo contacto
los datos de contact despus de crear el contacto nuevo:
- Id: 1, Nombre: Clarence, Apellido: Ho, Fecha de Nacimiento: 1980-07-30
- Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02
- Id: 3, Nombre: John, Apellido: Smith, Fecha de Nacimiento: 1964-02-28
- Id: 4, Nombre: Jacky, Apellido: Chan, Fecha de Nacimiento: 2001-11-01
Como se muestra en la salida, el primer bloque de lneas muestra los datos iniciales. El
segundo bloque de lneas muestra que el nuevo registro fue aadido. El bloque final de las lneas
muestra que el contacto recin creado se ha eliminado.
Como usted puede ver en el Listado 8-8, una gran cantidad de cdigo debe ser trasladado a
una clase de ayuda o-peor an-duplicado en cada clase DAO. Esta es la principal desventaja de JDBC
desde el punto de vista del programador de la aplicacin-que, simplemente usted no tiene tiempo
para programar el cdigo repetitivo en todas las clases DAO. En su lugar, usted desea concentrarse
en escribir el cdigo que realmente hace lo que usted necesita, hacer la clase DAO: seleccionar,
actualizar y borrar los datos. Usted necesita escribir ms cdigo de ayuda, necesita verificar ms las
excepciones a manejar, y puede que usted presente ms errores en su cdigo.
Es aqu donde un framework DAO y Spring entran. Un framework elimina el cdigo que
realmente no realiza ninguna lgica personalizada y le permite olvidarse de todos los tareas que debe
realizar. Adems, el amplio soporte de Spring JDBC hace su vida mucho ms fcil.
El cdigo del cual hablamos en la primera parte del captulo no es muy complejo, pero es molesto
para escribir, y porque hay mucho de esto para escribir, la probabilidad de errores de codificacin es
10
bastante alta. Es tiempo de echar un vistazo de cmo Spring hace las cosas ms fciles y ms
elegantes.
El soporte de JDBC en Spring est dividido en los cinco paquetes detallados en la Tabla 8-2, cada uno
maneja diferentes aspectos de acceso JDBC.
Tabla 8-2. Paquetes de Spring JDBC
Paquete
org.springframework.jdbc.core
Descripcin
Contiene las bases de las clases JDBC en Spring. Este
incluye el ncleo de la clase JDBC, JdbcTemplate, que
simplifica las operaciones de programacin a la base
de datos con JDBC. Algunos sub paquetes
proporcionan soporte de acceso a datos JDBC con
propsitos ms especficos (por ejemplo, una clase
JdbcTemplate que soporta parmetros con nombre) y
soporte de clases relacionadas tambin.
org.springframework.jdbc.datasource
org.springframework.jdbc.object
org.springframework.jdbc.support
org.springframework.jdbc.config
Vamos a empezar la discusin del soporte de Spring JDBC mirando la funcionalidad del nivel
ms bajo. Lo primero que usted tiene que hacer antes de siquiera pensar cmo ejecutar las consultas
SQL, es establecer una conexin con la base de datos.
11
Usted puede usar Spring para manejar la conexin a la base de datos, proporcionando un bean que
implemente javax.sql.DataSource. La diferencia entre DataSource y Connection es que
DataSource proporciona y maneja Connections.
DriverManagerDataSource (dentro del paquete org.springframework.jdbc.datasource) es la
implementacin ms sencilla de un DataSource. Al observar el nombre de la clase, usted puede
adivinar que sencillamente llama a DriverManager para obtener una conexin. El hecho de que
DriverManagerDataSource no soporte el pool de conexiones a bases de datos hace que esta clase
sea inadecuada para algo ms que pruebas. La configuracin de DriverManagerDataSource es
bastante sencilla, como usted puede ver en el Listado 8-10, slo tiene que proporcionar el nombre de
la clase del controlador, una URL de conexin, un nombre de usuario y una contrasea (datasourcedrivermanager.xml).
Listado 8-10. Bean dataSource con DriverManagerDataSource Manejado por Spring
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value = ${jdbc.driverClassName} />
<property name="url" value = ${jdbc.url} />
<property name="username" value = ${jdbc.username} />
<property name="password" value = ${jdbc.password} />
</bean>
<context:property-placeholder location="jdbc.properties" />
</beans>
Es muy probable que reconozca las propiedades en negrita en el Listado. Ellos representan los
valores que normalmente se pasan a JDBC para obtener una interfaz Connection. La informacin de
la conexin a la base de datos normalmente se almacena en un archivo de propiedades para un fcil
mantenimiento y sustitucin en diferentes entornos de despliegue. El Listado 8-11 muestra un
jdbc.properties de ejemplo de la cual la propiedad placeholder de Spring cargar la informacin
de la conexin.
Listado 8-11. El Archivo jdbc.properties
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/prospring3_ch8
jdbc.username=prospring3
jdbc.password=prospring3
En aplicaciones del mundo real, usted puede usar Apache Commons BasicDataSource
(http://commons.apache.org/dbcp/) o un DataSource implementado por un servidor de aplicaciones
JEE (por ejemplo, JBoss, WebSphere, WebLogic, GlassFish, etc.), el cual puede aumentar an ms el
12
rendimiento de la aplicacin. Usted podra utilizar un DataSource en el cdigo JDBC puro y obtener
los mismos beneficios del pooling, sin embargo, en la mayora de los casos, usted todava perdera un
lugar central para configurar el datasource. Spring, por el contrario, le permite declarar un bean
dataSource y establecer las propiedades de conexin en los archivos de definicin de
ApplicationContext (vea el Listado 8-12, y el nombre del archivo es datasource-dbcp.xml).
Nota: Adems de Apache Commons BasicDataSource, otras libreras populares de pool de
conexiones
de
base
de
datos
de
cdigo
abierto
incluyen
el
C3P0
(www.mchange.com/projects/c3p0/index.html) y BoneCP (http://jolbox.com/).
Listado 8-12. Bean dataSource Manejado por Spring
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value = ${jdbc.driverClassName} />
<property name="url" value = ${jdbc.url} />
<property name="username" value = ${jdbc.username} />
<property name="password" value = ${jdbc.password} />
</bean>
<context:property-placeholder location="jdbc.properties" />
</beans>
Este
particular
DataSource
manejado
por
Spring
es
implementado
en
org.apache.commons.dbcp.BasicDataSource. La parte ms importante es que el bean DataSource
implementa a javax.sql.DataSource, y usted puede empezar inmediatamente a usarlo en sus
clases de acceso a datos.
Otra forma de configurar un bean dataSource es utilizar JNDI. Si la aplicacin que usted est
desarrollando se va a ejecutar en un contenedor JEE, usted puede tomar ventaja del pool de conexin
manejado por el contenedor. Para usar un dataSource basado en JNDI, usted necesita cambiar la
declaracin del bean dataSource, como se muestra en el Listado 8-13 (datasource-jndi.xml).
Listado 8-13. Bean dataSource JNDI Manejado por Spring
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value=" java:comp/env/jdbc/prospring3ch8" />
</bean>
</beans>
13
A partir de la versin 3.0, Spring tambin ofrece el soporte de base de datos embebidas, que inicia
automticamente una base de datos embebida y la expone como un DataSource para la aplicacin.
El Listado 8-16 muestra la configuracin de una base de datos embebida (app-context-xml.xml).
14
Vamos a empezar de nuevo con una interfaz ContactDao vaca y una implementacin sencilla de la
misma. Vamos a aadir ms caractersticas a medida que avanzamos y explicamos lo que lo hagamos
con las clases de Spring JDBC. El listado 8-17 muestra la interfaz ContactDao vaca.
Listado 8-17. Interfaz e Implementacin de ContactDao
public interface ContactDao {}
public class JdbcContactDao implements ContactDao {}
15
Para la implementacin sencilla, primero aadiremos una propiedad dataSource. La razn por
la que deseamos agregar la propiedad dataSource a la implementacin de la clase en lugar de la
interfaz debera ser bastante obvia: la interfaz no necesita saber cmo se van a recuperar y actualizar
los datos. Aadiendo los mtodos get/setDataSource a la interfaz, nosotros-en el mejor de los
escenarios-forzamos las implementaciones para declarar los fragmentos de getter y setter.
Claramente, esto no es una prctica muy buena de diseo. Echa un vistazo a la sencilla clase
JdbcContactDao en el Listado 8-18.
Listado 8-18. Propiedad dataSource con JdbcContactDao
package com.apress.prospring3.ch8.dao.jdbc.xml;
import javax.sql.DataSource;
import com.apress.prospring3.ch8.dao.ContactDao;
public class JdbcContactDao implements ContactDao {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
Ahora podemos instruir a Spring para configurar nuestro bean contactDao usando la
implementacin JdbcContactDao y establecer la propiedad dataSource (vea el Listado 8-19, el
nombre del archivo es app-context-xml.xml).
Listado 8-19. Archivo de Contexto de la Aplicacin de Spring con los Beans dataSource y contactDao
<!-- Declaracin de Nombres de Espacio omitidos -->
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>
<bean id="contactDao" class="com.apress.prospring3.ch8.dao.jdbc.xml.JdbcContactDao">
<property name="dataSource">
<ref local="dataSource" />
</property>
</bean>
Para soportar la base de datos H2, tenemos que aadir la dependencia para la base de datos H2 en el
proyecto, como se muestra en la Tabla 8-3.
Tabla 8-3. Dependencia para la Base de Datos H2
GroupID
Artifact ID
com.h2database h2
Version
1.3.160
Description
Librera Java para la base de datos H2
16
Spring ahora crea el bean contactDao para instanciar la clase JdbcContactDao con la
propiedad dataSource establecida en el bean dataSource.
Es una buena prctica asegurarse de que se han establecido todas las propiedades requeridas
de un bean. La forma ms sencilla de hacerlo es implementar la interfaz InitializingBean y
proporcionar una implementacin para el mtodo afterPropertiesSet() (vea el Listado 8-20). De
esta manera, usted se asegura de que se han establecido todas las propiedades requeridas en su
JdbcContactDao. Para mayor informacin sobre la inicializacin de bean, consulte el Captulo 5.
Listado 8-20. Implementacin de JdbcContactDao con InitializingBean
package com.apress.prospring3.ch8.dao.jdbc.xml;
import javax.sql.DataSource;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.InitializingBean;
import com.apress.prospring3.ch8.dao.ContactDao;
public class JdbcContactDao implements ContactDao, InitializingBean {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void afterPropertiesSet() throws Exception {
if (dataSource == null) {
throw new BeanCreationException(
"Debe establecer el dataSource ContactDao");
}
}
}
El cdigo que hemos visto hasta ahora utiliza Spring para manejar el datasource y presenta la
interfaz ContactDao y su implementacin JDBC. Tambin se establece la propiedad dataSource en la
clase JdbcContactDao en el archivo ApplicationContext de Spring. Ahora ampliamos el cdigo
aadiendo las operaciones DAO reales a la interfaz y a la implementacin.
Manejo de Excepciones
Debido a que los defensores de Spring usan excepciones en tiempo de ejecucin en lugar de las
excepciones comprobadas, usted necesita un mecanismo para traducir la SQLException comprobada
en una excepcin en tiempo de ejecucin de Spring JDBC. Dado que las excepciones de Spring SQL
son excepciones en tiempo de ejecucin, ellas pueden ser mucho ms detalladas que las excepciones
comprobadas. (Por definicin, esta no es una caracterstica de las excepciones en tiempo de
ejecucin, pero es muy incmodo tener que declarar una larga lista de excepciones comprobadas en
la clusula throws, por lo que las excepciones comprobadas tienden a ser mucho ms genricas que
sus equivalentes en tiempo de ejecucin.)
Spring
proporciona
una
implementacin
predeterminada
de
la
interfaz
SQLExceptionTranslator, que se encarga de traducir los cdigos SQL genricos de error en las
excepciones de Spring JDBC. En la mayor parte los casos, esta implementacin es ms que suficiente,
pero podemos extender la implementacin predeterminada de Spring y establecer nuestra nueva
17
GroupID
Artifact ID
org.springframework spring-jdbc
Version
Description
3.1.0.RELEASE Modulo Spring JDBC
Para usar el traductor personalizado tenemos que pasarlo al JdbcTemplate en las clases DAO.
El Listado 8-22 muestra un fragmento de cdigo de ejemplo para este propsito.
Listado 8-22. Usando un SQLExceptionTranslator Personalizado en Spring Jdbc
// Dentro de cualquier clase DAO
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
// crear un traductor personalizado y establecer el datasource para la
// bsqueda de la traduccin por defecto
MySQLErrorCodesTranslator errorTranslator = new MySQLErrorCodesTranslator();
errorTranslator.setDataSource(dataSource);
jdbcTemplate.setExceptionTranslator(errorTranslator);
// usar el JdbcTemplate para este SqlUpdate
SqlUpdate sqlUpdate = new SqlUpdate();
sqlUpdate.setJdbcTemplate(jdbcTemplate);
sqlUpdate.setSql("update contact set first_name = 'Clarence'");
sqlUpdate.compile();
sqlUpdate.update();
y la traduccin personalizada de excepciones ocurrir cuando el cdigo de error sea -12345. Para
otros errores, Spring retroceder a su mecanismo por defecto para la traduccin de excepciones.
Obviamente, nada puede impedirle crear el SQLExceptionTranslator como un bean
manejado por Spring y usar el bean JdbcTemplate en sus clases DAO. No se preocupe si usted no
recuerda haber ledo acerca de la clase JdbcTemplate, vamos a hablar de ello con ms detalle.
La Clase JdbcTemplate
Esta clase representa el ncleo del soporte de Spring JDBC. Esta puede ejecutar todo tipo de
sentencias SQL. Desde el punto de vista ms simplista, usted puede clasificar las sentencias de
definicin y manipulacin de datos. La cobertura de sentencias de definicin de datos crea varios
objetos de base de datos (tablas, vistas, procedimientos almacenados, etc.). Las sentencias de
manipulacin de datos manipulan los datos y pueden ser clasificados como sentencias select y
update. Una sentencia select generalmente devuelve un conjunto de filas, cada fila tiene el mismo
conjunto de columnas. Una sentencia update modifica los datos en la base de datos pero no devuelve
ningn resultado.
La clase JdbcTemplate le permite emitir cualquier tipo de sentencia SQL a la base de datos y
devolver cualquier tipo de resultado.
En esta seccin, iremos a travs de varios casos de uso comn para la programacin JDBC en
Spring con la clase JdbcTemplate.
Antes de discutir cmo usar JdbcTemplate, echemos un vistazo cmo preparar JdbcTemplate para
su uso en la clase DAO. Es muy sencillo, la mayor parte del tiempo usted slo necesita construir la
clase pasandole el objeto datasource (que debera ser inyectado por Spring en la clase DAO). El
Listado 8-23 muestra el fragmento de cdigo que inicializa el objeto JdbcTemplate.
Listado 8-23. Inicializar JdbcTemplate
private JdbcTemplate jdbcTemplate;
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
19
Empecemos con una consulta sencilla que devuelve un nico valor. Por ejemplo, queremos ser
capaces de recuperar el nombre de un contacto por su ID. Aadamos primero el mtodo en la interfaz
ContactDao:
public String findFirstNameById(Long id);
Usando JdbcTemplate, podemos recuperar el valor con facilidad. El Listado 8-24 muestra la
implementacin del mtodo findFirstNameById() en la clase JdbcContactDao. Para los otros
mtodos, se crearon implementaciones vacas.
Listado 8-24. Usando JdbcTemplate para Recuperar un nico Valor
package com.apress.prospring3.ch8.dao.jdbc.xml;
// Omitidas las declaraciones import
public class JdbcContactDao implements ContactDao, InitializingBean {
public String findFirstNameById(Long id) {
String firstName = jdbcTemplate.queryForObject(
"select first_name from contact where id = ?",
new Object[] { id }, String.class);
return firstName;
}
public List<Contact> findAll() {
return null;
}
public List<Contact> findByFirstName(String firstName) {
return null;
}
public void insert(Contact contact) { }
public void update(Contact contact) { }
public void delete(Long contactId) { }
}
20
En el ejemplo anterior, estamos usando el marcador de posicin normal (el carcter ?) como
parmetros de consulta. Como tambin usted puede ver, tenemos que pasar los parmetros como un
array Object. Cuando se utiliza un marcador de posicin normal, el orden es muy importante, y el
orden en que usted pone los parmetros en el array debera ser el mismo orden de los parmetros en
la consulta.
Algunos desarrolladores (como yo) prefieren utilizar parmetros con nombre para asegurar
que el parmetro est vinculado exactamente como quera. En Spring, una variante de
JdbcTemplate,
llamada
NamedParameterJdbcTemplate
(dentro
del
paquete
org.springframework.jdbc.core.namedparam), proporciona soporte para esto. Veamos cmo
funciona.
Por ejemplo, en esta ocasin queremos aadir otro mtodo para encontrar el apellido por ID,
as que vamos a agregar el mtodo a la interfaz ContactDao:
public String findLastNameById(Long id);
21
En primer lugar, usted ver que en lugar del marcador de posicin ?, fue usado el parmetro
con nombre (prefijado por dos puntos). En segundo lugar, fue inicializado un SqlParameterSource,
que es un Map-basado en la fuente de los parmetros SQL con la llave como el nombre del parmetro
llamado y el valor como el valor del parmetro. En lugar de SqlParameterSource, usted tambin
puede simplemente construir un map para el almacenamiento de parmetros con nombre. El Listado
8-27 es una variante del mtodo anterior.
Listado 8-27. Usando NamedParameterJdbcTemplate para Recuperar un nico Valor
package com.apress.prospring3.ch8.dao.jdbc.xml;
// Omitidas las declaraciones import
public class JdbcContactDao implements ContactDao, InitializingBean {
// Otros mtodos omitidos
public String findLastNameById(Long id) {
String sql = "select last_name from contact where id = :contactId";
Map<String, Object> namedParameters = new HashMap<String, Object>();
namedParameters.put("contactId", id);
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters,
String.class);
}
}
Para probar el cdigo, slo tiene que aadir el mtodo en la clase main de prueba en el
Listado 8-25 y ejecutarlo. Voy a omitir esto.
En lugar de recuperar un nico valor, la mayor parte de las veces usted desear consultar una o ms
filas y luego transformar cada lnea en el objeto de dominio correspondiente.
La interfaz RowMapper<T> de Spring (dentro del paquete org.springframework.jdbc.core)
proporciona una manera sencilla para que usted pueda realizar el mapeo desde un resultset JDBC a
POJOs. Veamos esto en accin implementando el mtodo findAll() de la interfaz ContactDao
utilizando la interfaz de RowMapper<T>. El Listado 8-28 muestra la implementacin del mtodo
findAll().
Listado 8-28. Usando RowMapper<T> en Consultas de Objetos de Dominio
package com.apress.prospring3.ch8.dao.jdbc.xml;
// Omitidas las declaraciones import
22
En el Listado anterior, definimos una clase esttica interna llamada ContactMapper que
implementa la interfaz RowMapper<T>. La clase debe proporcionar la implementacin mapRow(), que
transforma los valores en un registro especfico del conjunto de resultados en el objeto de dominio
que usted desee. Debido a que es una clase interna esttica le permite compartir el RowMapper<T>
entre los mltiples mtodos de bsqueda.
Despus, el mtodo findAll() slo tiene que invocar el mtodo query y pasarla en la cadena
de consulta y el row mapper. En el caso de que la consulta requiera parmetros, el mtodo query()
proporciona una sobrecarga que acepta los parmetros de la consulta.
Aadimos el siguiente fragmento de cdigo (Listado 8-29) en el programa de pruebas (la clase
JdbcContactDaoSample).
Listado 8-29. Fragmento de Cdigo para Listar los Contactos
// Encontrar y lista todos los contactos
List<Contact> contacts = contactDao.findAll();
for (Contact contact: contacts) {
System.out.println(contact);
if (contact.getContactTelDetails() != null) {
for (ContactTelDetail contactTelDetail: contact.getContactTelDetails()) {
System.out.println("---" + contactTelDetail);
}
}
System.out.println();
}
Al ejecutar el programa produce el siguiente resultado (se han omitido las otras salidas):
Contacto - Id: 1, Nombre: Clarence, Apellido: Ho, Fecha de Nacimiento: 1980-07-30
Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02
Contacto - Id: 3, Nombre: John, Apellido: Smith, Fecha de Nacimiento: 1964-02-28
23
Prosigamos con un ejemplo un poco ms complicado, en el que tenemos que recuperar los datos de
la tabla padres (CONTACT) e hija (CONTACT_TEL_DETAIL) con un join y en consecuencia transformar
los datos en el objeto anidado (ContactTelDetail a Contact).
El RowMapper<T> anteriormente mencionado es adecuado nicamente para el mapeo base de
la fila a un objeto de dominio nico. Para una estructura de objeto ms complicada, tenemos que usar
la interfaz ResultSetExtractor. Para demostrar su uso, aadiremos un mtodo ms,
findAllWithDetail(), en la interfaz ContactDao. El mtodo debera poblar la lista de contactos con
sus detalles telefnicos.
public List<Contact> findAllWithDetail();
24
contactTelDetail.setId(contactTelDetailId);
contactTelDetail.setContactId(id);
contactTelDetail.setTelType(rs.getString("tel_type"));
contactTelDetail.setTelNumber(rs.getString("tel_number"));
contact.getContactTelDetails().add(contactTelDetail);
}
}
return new ArrayList<Contact>(map.values());
}
}
}
El cdigo es muy parecido al ejemplo RowMapper, pero esta vez declaramos una clase interna
que implementa ResultSetExtractor. Luego implementamos el mtodo extractData() para en
consecuencia transformar el conjunto de resultados en una lista de objetos Contact. Para el mtodo
findAllWithDetail(), la consulta utiliza un left join para unir las dos tablas y que tambin sean
recuperados los contactos sin telfonos. El resultado es un producto Cartesiano de las dos tablas. Por
ltimo, usamos el mtodo JdbcTemplate.query(), pasndole la cadena de consulta y el
resultsetExtractor.
Agreguemos el siguiente fragmento de cdigo (Listado 8-31) en el programa de pruebas (la
clase JdbcContactDaoSample).
Listado 8-31. Fragmento de Cdigo para Listar los Contactos
// Encontrar y listar todos los contactos con detalles
List<Contact> contactsWithDetail = contactDao.findAllWithDetail();
for (Contact contact: contactsWithDetail) {
System.out.println(contact);
if (contact.getContactTelDetails() != null) {
for (ContactTelDetail contactTelDetail: contact.getContactTelDetails()) {
System.out.println("---" + contactTelDetail);
}
}
System.out.println();
}
Ejecute de nuevo el programa de pruebas, y ste producir la siguiente salida (las otras salidas
se han omitido):
Contacto - Id: 1, Nombre: Clarence, Apellido: Ho, Fecha de Nacimiento: 1980-07-30
---Contacto Detalles Tl - Id: 2, Contacto id: 1, Tipo: Casa, Nmero: 1234567890
---Contacto Detalles Tl - Id: 1, Contacto id: 1, Tipo: Mvil, Nmero: 1234567890
Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02
---Contacto Detalles Tl - Id: 3, Contacto id: 2, Tipo: Casa, Nmero: 1234567890
Contacto - Id: 3, Nombre: John, Apellido: Smith, Fecha de Nacimiento: 1964-02-2
Usted puede ver que los contactos y sus detalles telefnicos fueron listados como corresponde.
Los datos estn basados en los scripts que cargan los datos del Listado 8-2.
Hasta ahora, usted ha visto cmo usar JdbcTemplate para realizar algunas operaciones de
consulta comn. JdbcTemplate (y tambin la clase NamedParameterJdbcTemplate) tambin ofrece
una serie de sobrecarga de mtodos update() que soportan operaciones de actualizacin de datos,
25
incluyendo insert, update, delete, etctera. Sin embargo, el mtodo update() es bastante autoexplicativo, por lo que decidimos no cubrirlo en esta seccin.
Por otro lado, como se ver en secciones posteriores, usaremos la clase SqlUpdate
proporcionada por Spring para realizar operaciones de actualizacin de datos.
En la seccin anterior, vimos cmo JdbcTemplate y las clases de utilidad relacionadas con el mapeo
de datos han simplificado enormemente el modelo de programacin en el desarrollo de la lgica de
acceso a datos con JDBC. Construir sobre JdbcTemplate, Spring tambin proporciona un nmero de
clases tiles que modelan las operaciones JDBC de datos y permiten a los desarrolladores mantener la
consulta y transformar la lgica desde el conjunto de resultados a objetos de dominio de una manera
ms orientada a objetos. Como se ha mencionado, las clases se empaquetan dentro de
org.springframework.jdbc.object. En concreto, hablaremos de las siguientes clases:
MappingSqlQuery<T>: La clase MappingSqlQuery<T> le permite envolver la cadena de
consulta, junto con el mtodo mapRow() en una sola clase.
SqlUpdate: La clase SqlUpdate le permite ajustar cualquier sentencia de actualizacin SQL en
el mismo. Tambin proporciona una gran cantidad de funciones tiles para enlazar parmetros
SQL, recuperar la llave de un RDBMS-generada despus de que un nuevo registro es
insertado, etctera.
BatchSqlUpdate: Como su nombre lo indica, la clase le permite realizar operaciones de
actualizacin por lotes. Por ejemplo, usted puede recorrer a travs de un objeto List de Java
y hacer que BatchSqlUpdate encole los registros y enviar las sentencias de actualizacin para
usted en un lote. Usted puede configurar el tamao del lote y nivelar la operacin en el
momento que desee.
SqlFunction<T>: La clase SqlFunction<T> le permite llamar a funciones almacenadas en la
base de datos con el argumento y el tipo de retorno. Tambin existe otra clase,
StoredProcedure, que le ayuda a invocar los procedimientos almacenados.
Nota: En secciones anteriores, todo el cdigo de ejemplo usa la configuracin de tipo XML. Por lo
tanto, en las siguientes secciones, usaremos las anotaciones de Spring para la configuracin del
ApplicationContext. En caso de que decida adoptar la configuracin XML en la aplicacin,
creemos que usted tendr una buena idea de cmo hacerlo.
Primero vamos a mirar cmo configurar la implementacin de la clase DAO usando anotaciones en
primer lugar. El Listado 8-32 muestra la interfaz de la clase ContactDao con un listado ms completo
de los servicios de acceso a datos que este proporciona.
Listado 8-32. Interfaze ContactDao
package com.apress.prospring3.ch8.dao;
// Omitidas las declaraciones import
public interface ContactDao {
public List<Contact> findAll();
26
En el Listado anterior, usamos @Repository para declarar el bean de Spring con el nombre de
contactDao, y puesto que la clase contiene un cdigo de acceso a datos, @Repository tambin
instruye a Spring a realizar excepciones SQL especficas de la bases de datos a la jerarqua
DataAccessException ms amigables con la aplicacin en Spring.
27
Despus, usted ver que se han generado implementaciones vacas de los mtodos. A
continuacin podemos proceder a implementar los mtodos progresivamente.
El Listado 8-34 muestra la configuracin XML para Spring usando anotaciones (app-contextannotation.xml).
Listado 8-34. Configuracin Usando Anotaciones de Spring
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.1.xsd">
29
<context:component-scan base-package="com.apress.prospring3.ch8.dao.jdbc.annotation"
/>
<context:annotation-config />
<jdbc:embedded-database id="dataSource" type="H2">
<jdbc:script location="classpath:schema.sql" />
<jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>
</beans>
30
return contact;
}
}
31
En STS, ya que establecemos las propiedades logging al nivel DEBUG, desde la salida de la consola,
usted ver tambin la consulta que fue enviada por Spring (ver Figura 8-4).
32
33
}
public List<Contact> findByFirstName(String firstName) {
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("first_name", firstName);
return selectContactByFirstName.executeByNamedParam(paramMap);
}
// Omitido el otro cdigo
}
Un punto a destacar aqu es que MappingSqlQuery<T> slo es adecuado para mapear una
sola fila a un objeto de dominio. Para un objeto anidado, usted todava tiene que usar JdbcTemplate
con ResultSetExtractor como en el ejemplo del mtodo findAllWithDetail() presentado en la
seccin de la clase JdbcTemplate.
Para actualizar los datos, Spring proporciona la clase SqlUpdate. El Listado 8-41 muestra la clase
UpdateContact que extiende la clase SqlUpdate para operaciones de actualizacin.
Listado 8-41. La Clase UpdateContact
package com.apress.prospring3.ch8.dao.jdbc.annotation;
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlUpdate;
public class UpdateContact extends SqlUpdate {
private static final String SQL_UPDATE_CONTACT =
"update contact set first_name=:first_name, last_name=:last_name, "
+ "birth_date=:birth_date where id=:id";
34
El Listado 8-41 debera ser familiar para usted ahora. Se construye una instancia de la clase
SqlUpdate con la consulta, y se declaran tambin los parmetros con nombre.
El Listado 8-42 muestra la implementacin del mtodo update() en la clase JdbcContactDao.
Listado 8-42. Usando SqlUpdate
package com.apress.prospring3.ch8.dao.jdbc.annotation;
// Omitidos las declaraciones import
@Repository("contactDao")
public class JdbcContactDao implements ContactDao {
private UpdateContact updateContact;
@Resource(name="dataSource")
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
selectAllContacts = new SelectAllContacts(dataSource);
selectContactByFirstName = new SelectContactByFirstName(dataSource);
updateContact = new UpdateContact(dataSource);
}
public void update(Contact contact) {
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("first_name", contact.getFirstName());
paramMap.put("last_name", contact.getLastName());
paramMap.put("birth_date", contact.getBirthDate());
paramMap.put("id", contact.getId());
updateContact.updateByNamedParam(paramMap);
log.info("Contacto actual actualizado con id: " + contact.getId());
}
// Omitidos otros cdigos
}
35
En la salida, usted puede ver que el contacto con un ID de 1 fue actualizado en consecuencia.
Para insertar datos, tambin usamos la clase SqlUpdate. Sin embargo, un punto interesante aqu es
acerca de la llave primaria, la columna id, que slo estar disponible slo despus de que la
sentencia de insercin se haya completado, mientras que un RDBMS genera el valor de identidad para
el registro. La columna ID fue declarada con el atributo AUTO_INCREMENT y es la llave primaria, lo que
significa que el valor fue asignado por un RDBMS durante la operacin de insercin.
Si usted est utilizando Oracle, es probable que usted obtenga primero un ID nico a partir de
una secuencia de Oracle y luego disparar la sentencia de insercin con la consulta. Sin embargo, en
nuestro caso, cmo podemos recuperar la llave generada por un RDBMS despus de que el registro
es insertado?
En antiguas versiones de JDBC, el mtodo es un poco complicado. Por ejemplo, si estamos
usando MySQL, tenemos que disparar el SQL select last_insert_id() y select @@IDENTITY
para Microsoft SQL Server.
Afortunadamente, a partir de la versin 3.0 de JDBC, fue aadida una nueva caracterstica que
permite recuperar una llave generada por un RDBMS de una manera unificada. El Listado 8-37
muestra la implementacin del mtodo insert(), que tambin recupera la llave generada para el
registro del contacto insertado. Esto funcionar en la mayor parte de las bases de datos (si no todas),
slo asegrese de que usted est usando un driver JDBC que es compatible con JDBC 3.0 o posterior.
Comenzamos por crear la clase InsertContact para la operacin de insercin, que extiende a
la clase SqlUpdate. El Listado 8-44 muestra la clase.
Listado 8-44. La Clase InsertContact
package com.apress.prospring3.ch8.dao.jdbc.annotation;
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
36
import org.springframework.jdbc.object.SqlUpdate;
public class InsertContact extends SqlUpdate {
private static final String SQL_INSERT_CONTACT =
"insert into contact (first_name, last_name, birth_date) " +
"values (:first_name, :last_name, :birth_date)";
public InsertContact(DataSource dataSource) {
super(dataSource, SQL_INSERT_CONTACT);
super.declareParameter(new SqlParameter("first_name", Types.VARCHAR));
super.declareParameter(new SqlParameter("last_name", Types.VARCHAR));
super.declareParameter(new SqlParameter("birth_date", Types.DATE));
super.setGeneratedKeysColumnNames(new String[] {"id"});
super.setReturnGeneratedKeys(true);
}
}
La clase InsertContact es casi la misma que la clase UpdateContact. Slo tenemos que
hacer dos cosas ms. Al construir la clase InsertContact, llamamos al mtodo
SqlUpdate.setGeneratedKeysColumnNames() para declarar el nombre de la columna ID. El mtodo
SqlUpdate.setReturnGeneratedKeys() indica al controlador JDBC subyacente que recupere la
llave generada.
El Listado 8-45 muestra la implementacin del mtodo insert() en la clase JdbcContactDao.
Listado 8-45. Usando SqlUpdate para Operaciones de Insercin
package com.apress.prospring3.ch8.dao.jdbc.annotation;
//Omitidos las declaraciones import
@Repository("contactDao")
public class JdbcContactDao implements ContactDao {
private InsertContact insertContact;
@Resource(name = "dataSource")
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
selectAllContacts = new SelectAllContacts(dataSource);
selectContactByFirstName = new SelectContactByFirstName(dataSource);
updateContact = new UpdateContact(dataSource);
insertContact = new InsertContact(dataSource);
}
public void insert(Contact contact) {
Map<String, Object> paramMap = new HashMap<String, Object>();
paramMap.put("first_name", contact.getFirstName());
paramMap.put("last_name", contact.getLastName());
paramMap.put("birth_date", contact.getBirthDate());
KeyHolder keyHolder = new GeneratedKeyHolder();
insertContact.updateByNamedParam(paramMap, keyHolder);
contact.setId(keyHolder.getKey().longValue());
log.info("Contacto nuevo insertado con id: " + contact.getId());
}
37
Del Listado 8-45, despus de la inyeccin del datasource, fue construida una instancia de
InsertContact (tenga en cuenta las lneas en negrita). En el mtodo insert(), tambin utilizamos
el mtodo SqlUpdate.updateByNamedParam(). Sin embargo, tambin pasamos en una instancia de
KeyHolder al mtodo, el cual tendr almacenado el ID generado. Despus de que los datos son
insertados, podemos recuperar entonces la llave generada desde el KeyHolder.
Para comprobar el funcionamiento, agregue el siguiente fragmento de cdigo del Listado 8-46
en la clase AnnotationJdbcDaoSample.
Listado 8-46. Probando el Mtodo insert()
// Insertar un contacto
contact = new Contact();
contact.setFirstName("Rod");
contact.setLastName("Johnson");
contact.setBirthDate(new Date((new GregorianCalendar(2001, 10, 1)).getTime().getTime()));
contactDao.insert(contact);
contacts = contactDao.findAll();
listContacts(contacts);
Usted puede ver que el nuevo contacto fue insertado con un ID de 4 y recuperado
correctamente.
Para las operaciones por lotes, usamos la clase BatchSqlUpdate. El uso es bsicamente el mismo
que la clase SqlUpdate, slo tenemos que hacer unas cuantas cosas ms. Para demostrar su uso,
vamos a aadir un nuevo mtodo a la interfaz ContactDao:
public void insertWithDetail(Contact contact);
El nuevo mtodo insertWithDetail() insertar tanto en el contacto como en sus datos telefnicos
en la base de datos. Para poder insertar el registro de detalle telefnico, tenemos que crear la clase
InsertContactTelDetail, que se muestra en el Listado 8-47.: La clase InsertContactTelDetail.
Listado 8-47. La Clase InsertContactTelDetail
package com.apress.prospring3.ch8.dao.jdbc.annotation;
38
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.BatchSqlUpdate;
public class InsertContactTelDetail extends BatchSqlUpdate {
private static final String SQL_INSERT_CONTACT_TEL =
"insert into contact_tel_detail (contact_id, tel_type, tel_number) " +
"values (:contact_id, :tel_type, :tel_number)";
private static final int BATCH_SIZE = 10;
public InsertContactTelDetail(DataSource dataSource) {
super(dataSource, SQL_INSERT_CONTACT_TEL);
declareParameter(new SqlParameter("contact_id", Types.INTEGER));
declareParameter(new SqlParameter("tel_type", Types.VARCHAR));
declareParameter(new SqlParameter("tel_number", Types.VARCHAR));
setBatchSize(BATCH_SIZE);
}
}
39
40
Del Listado 8-48, cada vez que es llamado el mtodo insertWithDetail(), se construye una
nueva instancia de InsertContactTelDetail. La razn es que la clase BatchSqlUpdate no es
thread safe. Entonces la usamos al igual que SqlUpdate. Sin embargo, la clase BatchSqlUpdate
encolar las operaciones de insercin y las enva por lotes a la base de datos. Cada vez que el nmero
de registros es igual al tamao del lote, Spring disparar una operacin de insercin masiva a la base
de datos para los registros pendientes. Por otra parte, al finalizar, llamamos el mtodo
BatchSqlUpdate.flush() para instruir a Spring para que limpie todas las operaciones pendientes
(es decir, las operaciones de insercin encoladas que an no han alcanzado el tamao del lote
todava). Finalmente, recorremos la lista de objetos ContactTelDetail en el objeto Contact e
invocamos el mtodo BatchSqlUpdate.updateByNamedParam().
Para facilitar las pruebas, tambin fue implementado el mtodo findAllWithDetail(). El
Listado 8-49 muestra el fragmento de cdigo para agregar a la clase AnnotationJdbcDaoSample
para probar la operacin de insercin por lotes.
Listado 8-49. Probando el Mtodo InsertWithDetail()
// Insertar contacto con detalles
contact = new Contact();
contact.setFirstName("Michael");
contact.setLastName("Jackson");
contact.setBirthDate(new Date((new GregorianCalendar(1964, 10, 1)).getTime().getTime()));
List<ContactTelDetail> contactTelDetails = new ArrayList<ContactTelDetail>();
ContactTelDetail contactTelDetail = new ContactTelDetail();
contactTelDetail.setTelType("Casa");
contactTelDetail.setTelNumber("11111111");
contactTelDetails.add(contactTelDetail);
contactTelDetail = new ContactTelDetail();
contactTelDetail.setTelType("Mvil");
contactTelDetail.setTelNumber("22222222");
contactTelDetails.add(contactTelDetail);
contact.setContactTelDetails(contactTelDetails);
contactDao.insertWithDetail(contact);
contacts = contactDao.findAllWithDetail();
listContacts(contacts);
41
Usted puede ver que los nuevos contactos con los datos telefnicos fueron todos insertados en
la base de datos.
Spring tambin ofrece una serie de clases para simplificar la ejecucin de procedimientos
almacenados y funciones usando JDBC. En esta seccin, le mostraremos una funcin sencilla usando
la clase SqlFunction para llamar a una funcin SQL en la base de datos. Usaremos MySQL como un
ejemplo, crearemos una funcin almacenada, y la llamaremos usando la clase SqlFunction<T>.
Suponemos que usted tiene una base de datos MySQL con un esquema denominado
prospring3_ch8, con un nombre de usuario y contrasea, ambos iguales a prospring3 (igual que el
ejemplo de la seccin "Explorando la infraestructura JDBC"). Creamos una funcin almacenada
llamada getFirstNameById(), que acepta el ID del contacto y devuelve el nombre del contacto. El
Listado 8-50 muestra el script para crear la funcin almacenada en MySQL (store-function.sql).
Ejecute el script contra la base de datos MySQL.
Listado 8-50. Funcin Almacenada para Mysql
DELIMITER //
CREATE FUNCTION getFirstNameById(in_id INT)
RETURNS VARCHAR(60)
BEGIN
RETURN (SELECT first_name FROM contact WHERE id = in_id);
END //
DELIMITER ;
42
import java.sql.Types;
import javax.sql.DataSource;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.object.SqlFunction;
public class SfFirstNameById extends SqlFunction<String> {
private static final String SQL = "select getfirstnamebyid(?)";
public SfFirstNameById(DataSource dataSource) {
super(dataSource, SQL);
declareParameter(new SqlParameter(Types.INTEGER));
compile();
}
}
En el listado 8-52, se extiende la clase SqlFunction<T> y le pasa el tipo String, que indica el
tipo de retorno de la funcin. Luego declaramos el cdigo SQL para llamar a la funcin almacenada en
MySQL. Despus, en el constructor, se declara el parmetro, y luego, se compila la operacin. Ahora
la clase est lista para nuestro uso en la implementacin de la clase. El Listado 8-53 muestra la clase
JdbcContactSfDao, que implementa la interfaz ContactSfDao.
Listado 8-53. La Clase JdbcContactSfDao
package com.apress.prospring3.ch8.dao.jdbc.annotation;
import java.util.List;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.springframework.stereotype.Repository;
import com.apress.prospring3.ch8.dao.ContactSfDao;
@Repository("contactSfDao")
public class JdbcContactSfDao implements ContactSfDao {
private DataSource dataSource;
private SfFirstNameById sfFirstNameById;
@Resource(name = "dataSource")
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
sfFirstNameById = new SfFirstNameById(dataSource);
}
public DataSource getDataSource() {
return dataSource;
}
43
GroupID
Artifact ID
Version Description
commons-dbcp
commons-dbcp
1.4
44
En caso de que usted prefiera usar la clase de configuracin de Java en lugar de la configuracin XML,
el Listado 8-56 muestra la clase de configuracin de Spring.
Listado 8-56. Usando la Configuracin de Java
package com.apress.prospring3.ch8.javaconfig;
import javax.sql.DataSource;
import
import
import
import
import
import
org.springframework.context.annotation.Bean;
org.springframework.context.annotation.ComponentScan;
org.springframework.context.annotation.Configuration;
org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;
org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
@Configuration
@ComponentScan(basePackages = "com.apress.prospring3.ch8.dao.jdbc.annotation")
public class AppConfig {
45
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.H2)
.addScript("schema.sql").addScript("test-data.sql").build();
return db;
}
}
Como mencionamos al comienzo de este captulo, en los ltimos aos la tecnologa de base de datos
ha evolucionado tan rpidamente con la aparicin de tantas bases de datos de propsitos especficos,
hoy da un RDBMS no es la nica opcin como un sistemas de gestin de bases de datos back-end de
una aplicacin. Para responder a esta evolucin tecnolgica de las bases de datos y a la necesidad de
la comunidad de desarrolladores, Spring cre el proyecto Spring Data (www.springsource.org/springdata). El objetivo principal del proyecto es proporcionar extensiones tiles por encima de la
funcionalidad bsica de acceso de datos de Spring para atender las necesidades de los desarrolladores
de Spring que interactan con motores de bases de datos distintas a RDBMSs. Tambin estn
disponibles las caractersticas avanzadas para los estndares de acceso a datos (por ejemplo, JDBC,
JPA).
El proyecto Spring Data viene con un montn de extensiones. Una extensin que nos gustara
mencionar aqu es JDBC Extensions (www.springsource.org/spring-data/jdbc-extensions). Como su
nombre lo indica, la extensin proporciona algunas caractersticas avanzadas que facilitan el desarrollo
de aplicaciones JDBC usando Spring.
Al momento de escribir, la primera versin (versin 1.0.0) todava estaba en su etapa
milestone. Las caractersticas principales que proporciona la extensin son listadas aqu:
Soporte Avanzado para Bases de Datos de Oracle: La extensin proporciona una gran cantidad
de caractersticas avanzadas para los usuarios de bases de datos de Oracle. Por el lado de la
conexin a la base de datos, soporta configuraciones de sesin especficas de Oracle, as como
la tecnologa Fast Connection Failover cuando se trabaja con Oracle RAC. Adems, se
proporcionan las clases que se integran con Oracle Advanced Queuing. Del lado del tipo de
dato, se proporciona el soporte nativo para tipos XML de Oracle, STRUCT y ARRAY, etctera.
Si est desarrollando aplicaciones JDBC usando Spring con Base de Datos de Oracle, JDBC
Extensions merece realmente una oportunidad.
Desde los debates anteriores, usted puede ver cmo Spring puede hacer su vida mucho ms fcil
usando JDBC para interactuar con un RDBMS. Sin embargo, todava hay un montn de cdigo que
46
Resumen
En este captulo se demostr cmo usar Spring para simplificar la programacin JDBC. Usted aprendi
cmo conectarse a una base de datos y realizar operaciones de seleccin, actualizacin, eliminacin, e
insercin, y llamar funciones almacenadas. Cmo usar la clase ncleo de Spring JDBC, JdbcTemplate,
fue discutida en detalle. Adems, cubrimos otras clases de Spring que se construyen a partir de
JdbcTemplate y que le ayudar a modelar varias operaciones JDBC. En los prximos captulos, vamos
a discutir cmo utilizar tecnologas de Spring con ORM populares desarrollando la lgica de acceso a
datos.
47