Sie sind auf Seite 1von 48

Tabla de contenido

Capitulo 8 : Soporte Spring JDBC .................................................................................................. 1 Ejemplo de Modelo de Datos para Cdigo de Ejemplo ..................................................................... 2 Explorando la Infraestructura de JDBC .......................................................................................... 5 Infraestructura de Spring JDBC ................................................................................................... 10 Informacin General y Paquetes Usados................................................................................... 11 Conexiones a Base de Datos y DataSources .............................................................................. 12 Soporte de Base de Datos Embebidas ...................................................................................... 14 Usando DataSources en Clases DAO ............................................................................................ 15 Manejo de Excepciones .............................................................................................................. 17 La Clase JdbcTemplate ............................................................................................................... 19 Inicializando JdbcTemplate en una Clase DAO .......................................................................... 19 Recuperando un nico-Valor-Usando la clase JdbcTemplate....................................................... 20 Usando Parmetros con Nombres con NamedParameterJdbcTemplate ........................................ 21 Recuperando Objetos de Dominio con RowMapper<T> ............................................................. 22 Recuperando Objetos de Dominio Anidados con ResultSetExtractor ............................................ 24 Clases Spring que Modelan Operaciones JDBC.............................................................................. 26 Configurando JDBC DAO para Usar Anotaciones........................................................................ 26 Consultando Datos Usando MappingSqlQuery<T> .................................................................... 30 Actualizando Datos Usando SqlUpdate ..................................................................................... 34 Insertando Datos y Recuperando la Llave Generada .................................................................. 36 Operaciones de Procesamiento por Lotes con BatchSqlUpdate ................................................... 38 Llamando Funciones Almacenadas Usando SqlFunction.............................................................. 42 Usando la Configuracin de Java ................................................................................................. 45 Proyecto Spring Data: JDBC Extensions ....................................................................................... 46 Consideraciones para Usar JDBC ................................................................................................. 46 Resumen .................................................................................................................................. 47

Capitulo 8: Soporte Spring JDBC

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
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.

Conectndose a la base de datos: Aunque no entremos en cada pequeo detalle del manejo

de la conexin a la base de datos, le mostramos las diferencias fundamentales entre un sencillo Connection y un DataSource. Naturalmente, hablamos de cmo Spring maneja los DataSources y qu datasources usted puede utilizar en sus aplicaciones.

Recuperando y mapeando los datos a objetos Java: Te mostramos cmo recuperar los datos y

luego mapear con eficacia los datos seleccionados a objetos Java. Tambin usted aprende que Spring JDBC es una alternativa viable para herramientas de mapeo de objeto-relacional (ORM).

Insertando, actualizando y eliminando datos: Finalmente, discutimos cmo usted puede

implementar las operaciones de insert, update, y delete de manera que cualquier cambio en la base de datos que usted est utilizando no tenga un impacto devastador en el cdigo que usted ha escrito.

QU ES UNA BASE DE DATOS? Los desarrolladores a veces tienen problemas para describir lo que es una base de datos. En un caso, una base de datos representa los datos reales, y en otros casos, puede representar una pieza de software que maneja los datos, una instancia de un proceso de este software, o incluso la mquina fsica que ejecuta el administrador de procesos. Formalmente, una base de datos es una coleccin de datos, el software de base de datos (como Oracle, PostgreSQL, MySQL, etc.) es llamado el software de gestin de bases de datos o, ms especficamente, un sistema de gestin de bases de datos relacionales (RDBMS), la instancia de un RDBMS es llamado motor de base de datos y, por ltimo, la mquina que ejecuta el motor de base de datos se llama servidor de base de datos. Sin embargo, la mayor parte de los desarrolladores comprenden inmediatamente el significado del trmino base de datos desde el contexto en el que es usado. Es por eso que usamos este trmino para representar los cuatro significados que acabamos de describir. En los ltimos aos, debido al crecimiento explosivo de Internet y las tecnologas de computacin en la nube, una gran cantidad de aplicaciones web para fines-especficos, han surgido, tales como las redes sociales, motores de bsqueda, mapas, videos, etc. Para atender los requerimientos especficos de acceso a datos de aquellas aplicaciones, tambin se han desarrollado muchas categoras diferentes de "bases de datos". Algunos ejemplos incluyen bases de datos de par clave-valor (generalmente se conoce como bases de datos NoSQL), bases de datos grficas, bases de datos centradas en documentos, etctera. Por lo tanto, la base de datos ahora es un trmino mucho ms amplio. Sin embargo, una discusin de las bases de datos no relacionales no est dentro del alcance de este libro, y nos referimos a un RDBMS cuando mencionamos las bases de datos a lo largo de este libro.

Ejemplo de Modelo de Datos para Cdigo de Ejemplo

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.

Figura 8-1. Ejemplo del Modelo de Datos para el Cdigo de Ejemplo

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;

// metodos Getter y Setter omitidos public String toString() { return "Contacto - Id: " + id + ", Nombre: " + firstName + ", Apellido: " + lastName + ", Fecha de Nacimiento: " + birthDate; } } Listado 8-4. El Objeto de Dominio ContactTelDetail package com.apress.prospring3.ch8.domain; import java.io.Serializable; public class ContactTelDetail implements Serializable { private private private private Long id; Long contactId; String telType; String telNumber;

// mtodos Getter y Setter omitidos public String toString() { return "Contact Tel Detail - Id: " + id + ", Contact id: " + contactId + ", Type: " + telType + ", Number: " + 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.

Explorando la Infraestructura JDBC

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

conexin. Adems, el nmero de conexiones simultneas por lo general es limitada, y un nmero excesivo de conexiones abiertas ralentiza la base de datos. Le mostraremos cmo Spring ayuda a gestionar esta complejidad, pero antes de que podamos seguir adelante, tenemos que mostrarle como seleccionar, eliminar y actualizar los datos con JDBC puro. Vamos a crear una forma sencilla de implementar la interfaz ContactDao para interactuar con la base de datos a travs de JDBC puro. Teniendo en cuenta lo que ya sabemos acerca de las conexiones a base de datos, tomamos el enfoque prudente y costoso (en trminos de rendimiento) de crear una conexin para cada declaracin. Esto en gran medida reduce el rendimiento de Java y aade tensin adicional a la base de datos porque una conexin tiene que ser establecida por cada consulta. Sin embargo, si mantenemos una conexin abierta, podramos traer el servidor de base de datos a una parada. El Listado 8-7 muestra el cdigo necesario para manejar una conexin JDBC, usando MySQL como un ejemplo.
Listado 8-7. Manejando una Conexin JDBC 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 } } ...

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.

Listado 8-8. Implementacin de JDBC DAO Puro package com.apress.prospring3.ch8.dao.plain; import import import import import import import import 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); }

} }

Para ejecutar el programa, es necesario agregar la dependencia de MySQL para Java en su proyecto, como se muestra en la Tabla 8-1.
Tabla 8-1. Dependencia para Mysql

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 los datos iniciales de contact: - 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 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

Eliminando el contacto recin creado Listando los datos de contact despus de eliminar el contacto nuevo: 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

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.

Infraestructura de Spring JDBC

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.

Informacin General y Paquetes Usados

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. Contiene las clases de ayuda y de las implementaciones DataSource que usted puede utilizar para ejecutar cdigo JDBC fuera de un contenedor JEE. Algunos sub paquetes proporcionan soporte a bases de datos embebidas, inicializacin de base de datos, y varios mecanismos de bsqueda de datasource. Contiene clases que ayudan ha convertir los datos devueltos por la base de datos en objetos o listas de objetos. Estos objetos y listas son simples objetos de Java y por lo tanto son desconectados de la base de datos. La clase ms importante de este paquete es el soporte de traduccin de SQLException. Esto permite que Spring reconozca los cdigos de error utilizados por la base de datos y mapearlos a las excepciones de ms alto nivel. Contiene clases que soportan la configuracin JDBC dentro del ApplicationContext de Spring. Por ejemplo, este contiene la clase manejadora para el espacio de nombres jdbc (por ejemplo, etiquetas <jdbc:embedded-database>).

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

Conexiones a Base de Datos y DataSources

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

En el ejemplo anterior, usamos JndiObjectFactoryBean de Spring para obtener la bsqueda del datasource JNDI. A partir de la versin 2.5, Spring proporciona el espacio de nombres jee, lo que simplifica an ms la configuracin. El Listado 8-14 muestra la misma configuracin del dataSource JNDI utilizando el espacio de nombres jee (datasource-jee.xml).
Listado 8-14. Bean dataSource JNDI Manejado por Spring (Usando el Espacio de Nombre jee) <?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: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/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd"> <jee:jndi-lookup jndi-name="java:comp/env/jdbc/prospring3ch8" /> </beans>

En el Listado anterior, declaramos el espacio de nombres jee en la etiqueta <beans> y luego la etiqueta <jee:jndi-lookup> para declarar el datasource. Si se toma el enfoque JNDI, usted no debe olvidar agregar una referencia de recursos (resourceref) en el archivo descriptor de la aplicacin (vea el Listado 8-15).
Listado 8-15. Una Referencia de Recursos en el Archivo Descriptor <root-node> <resource-ref> <res-ref-name>jdbc/prospring3ch8</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> </root-node>

<root-node> es el valor de un marcador de posicin, que usted tiene que cambiar dependiendo de cmo su mdulo est empaquetado. Por ejemplo, se convierte en <web-app> en el descriptor de despliegue web (WEB-INF/web.xml) si la aplicacin es un mdulo web. Lo ms probable es que usted tendr que configurar el resource-ref en un archivo descriptor de la aplicacin del servidor-especfico tambin. Sin embargo, observe que el elemento resource-ref configura el nombre de la referencia a jdbc/prospring3ch8 y que el bean dataSource jndiName se establece a java:comp/env/jdbc/prospring3ch8. Como usted puede ver, Spring le permite configurar el DataSource en casi cualquier forma que usted desee, y este oculta la implementacin real o la ubicacin del datasource del resto del cdigo de la aplicacin. En otras palabras, sus clases DAO no saben y no tienen que saber dnde seala el DataSource. La administracin de la conexin tambin es delegada al bean dataSource, que por su parte se administra as mismo o utiliza el contenedor JEE que hacer todo el trabajo.

Soporte de Base de Datos Embebidas

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

Listado 8-16. Spring Soporte de Base de Datos Embebidas <?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"> <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="dataSource" /> </bean> </beans>

En el Listado anterior, primero declaramos el espacio de nombres jdbc en la etiqueta <beans>. Despus, usamos <jdbc:embedded-database> para declarar la base de datos embebida y asignarla con un ID de dataSource. Dentro de la etiqueta, tambin instruimos a Spring para que ejecute los scripts especificados para crear el esquema de la base de datos y en consecuencia poblarla con datos de prueba. Tenga en cuenta que es importante el orden de los scripts, y el archivo que contiene el Lenguaje de Definicin de Datos (DDL) siempre debera de aparecer en primer lugar, seguido por el archivo con el Lenguaje de Manipulacin de Datos (DML). Para el atributo type, especificamos el tipo de base de datos embebida a usar. A partir de la versin 3.1, Spring soporta HSQL (por defecto), H2, y Derby. El soporte de base de datos embebida es muy til para el desarrollo local o pruebas unitarias. En el resto de este captulo, usaremos la base de datos embebida para ejecutar el cdigo de ejemplo, por lo que su mquina no requiere que se instale una base de datos con el fin de ejecutar los ejemplos.

Usando DataSources en Clases DAO

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

implementacin SQLExceptionTranslator para ser usada en JdbcTemplate, como muestra el Listado 8-21. A la vez, tenemos que aadir la dependencia de spring-jdbc en el proyecto, como se muestra en la Tabla 8-4.
Tabla 8-4. Dependencia para spring-jdbc

GroupID Artifact ID org.springframework spring-jdbc

Version Description 3.1.0.RELEASE Modulo Spring JDBC

Listado 8-21. SQLExceptionTranslator personalizado package com.apress.prospring3.ch8.exception.translator; import java.sql.SQLException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DeadlockLoserDataAccessException; import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; public class MySQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator { protected DataAccessException customTranslate(String task, String sql, SQLException sqlex) { if (sqlex.getErrorCode() == -12345) return new DeadlockLoserDataAccessException(task, sqlex); return null; } }

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

Teniendo en su lugar el traductor personalizado de excepciones SQL, Spring invocar ste sobre las excepciones SQL detectadas cuando se ejecuten las sentencias SQL contra la base de datos 18

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.

Inicializando JdbcTemplate en una Clase DAO

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

La prctica general consiste en inicializar el JdbcTemplate dentro del mtodo setDatasource para que una vez que el datasource sea inyectado por Spring, el JdbcTemplate tambin sea inicializado y est listo para su uso. Una vez configurado, el JdbcTemplate es thread safe. Eso significa que usted tambin puede optar por iniciar una nica instancia de JdbcTemplate en la configuracin XML de Spring y tener esta inyeccin en todos los beans DAO. Nota: En el mdulo de Spring Jdbc, hay una clase llamada JdbcDaoSupport. Que envuelve la clase JdbcTemplate, y usted puede tener sus clases DAO extendiendo la clase JdbcDaoSupport. En este caso, cuando la clase DAO es inyectada con el datasource, el JdbcTemplate se inicializar automticamente.

19

Recuperando un nico-Valor-Usando la clase JdbcTemplate

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

En el Listado anterior, usamos queryForObject() de JdbcTemplate para recuperar el valor del first_name. El primer argumento es la cadena SQL, y el segundo argumento consiste en los parmetros que se pasan al SQL para enlazar el parmetro en forma de un objeto array. El ltimo argumento es el tipo a ser devuelto, que en este caso es un String. Adems de Object, usted tambin puede consultar por otros tipos como Long e Integer. Echemos un vistazo a los resultados. El listado 8-25 muestra las pruebas del programa.
Listado 8-25. Usando JdbcTemplate package com.apress.prospring3.ch8; import org.springframework.context.support.GenericXmlApplicationContext; import com.apress.prospring3.ch8.dao.ContactDao;

20

public class JdbcContactDaoSample { public static void main(String[] args) { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("classpath:app-context-xml.xml"); ctx.refresh(); ContactDao contactDao = ctx.getBean("contactDao", ContactDao.class); // Encontrar first_name por id System.out.println("El Primer nombre para el contacto de id 1 es: " + contactDao.findFirstNameById(1l)); } }

Como era de esperar, la ejecucin del programa produce el siguiente resultado:


El Primer nombre para el contacto de id 1 es: Clarence

Usando Parmetros con Nombres con NamedParameterJdbcTemplate

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

La inicializacin de NamedParameterJdbcTemplate es la misma que JdbcTemplate, por lo que slo tenemos que declarar una variable con el tipo NamedParameterJdbcTemplate y aadir la siguiente lnea en la clase DAO del mtodo setDataSource():
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);

Ahora veamos cmo implementar el mtodo. El Listado 8-26 muestra la implementacin.


Listado 8-26. 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

21

public String findLastNameById(Long id) { String sql = "select last_name from contact where id = :contactId"; SqlParameterSource namedParameters = new MapSqlParameterSource( "contactId", id); return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, String.class); } }

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.

Recuperando Objetos de Dominio con RowMapper<T>

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

public class JdbcContactDao implements ContactDao, InitializingBean { // Otros mtodos omitidos public List<Contact> findAll() { String sql = "select id, first_name, last_name, birth_date from contact"; return jdbcTemplate.query(sql, new ContactMapper()); } private static final class ContactMapper implements RowMapper<Contact> { public Contact mapRow(ResultSet rs, int rowNum) throws SQLException { Contact contact = new Contact(); contact.setId(rs.getLong("id")); contact.setFirstName(rs.getString("first_name")); contact.setLastName(rs.getString("last_name")); contact.setBirthDate(rs.getDate("birth_date")); return contact; } } }

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

Recuperando Objetos de Dominio Anidados con ResultSetExtractor

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

El Listado 8-30 muestra la implementacin del mtodo findAllWithDetail() usando ResultSetExtractor.


Listado 8-30. Usando ResultSetExtractor en Consultas de Objetos de Dominio package com.apress.prospring3.ch8.dao.jdbc.xml; // Omitidas las declaraciones import public class JdbcContactDao implements ContactDao, InitializingBean { public List<Contact> findAllWithDetail() { String sql = "select c.id, c.first_name, c.last_name, c.birth_date" + ", t.id as contact_tel_id, t.tel_type, t.tel_number from contact c " + "left join contact_tel_detail t on c.id = t.contact_id"; return jdbcTemplate.query(sql, new ContactWithDetailExtractor()); } private static final class ContactWithDetailExtractor implements ResultSetExtractor<List<Contact>> { public List<Contact> extractData(ResultSet rs) throws SQLException, DataAccessException { Map<Long, Contact> map = new HashMap<Long, Contact>(); Contact contact = null; while (rs.next()) { Long id = rs.getLong("id"); contact = map.get(id); if (contact == null) { // Nuevo registro contact contact = new Contact(); contact.setId(id); contact.setFirstName(rs.getString("first_name")); contact.setLastName(rs.getString("last_name")); contact.setBirthDate(rs.getDate("birth_date")); contact.setContactTelDetails( new ArrayList<ContactTelDetail>()); map.put(id, contact); } // Procesar los detalles de tel del contacto (si existe) Long contactTelDetailId = rs.getLong("contact_tel_id"); if (contactTelDetailId > 0) { ContactTelDetail contactTelDetail = new ContactTelDetail();

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.

Clases Spring que Modelan Operaciones JDBC

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.

Configurando JDBC DAO para Usar Anotaciones

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

public List<Contact> findAllWithDetail(); public List<Contact> findByFirstName(String firstName); public String findFirstNameById(Long id); public String findLastNameById(Long id); public void insert(Contact contact); public void update(Contact contact); public void delete(Long contactId); public void insertWithDetail(Contact contact); }

En el Listado 8-33, fue mostrada la declaracin inicial y la inyeccin de la propiedad datasource usando la anotacin JSR-250. El nombre de la clase es JdbcContactDao, pero esta vez la ponemos dentro del paquete com.apress.prospring3.ch8.dao.jdbc.annotation.
Listado 8-33. Declarando JdbcContactDao Usando Anotaciones package com.apress.prospring3.ch8.dao.jdbc.annotation; import javax.annotation.Resource; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.stereotype.Repository; import com.apress.prospring3.ch8.dao.ContactDao; @Repository("contactDao") public class JdbcContactDao implements ContactDao { private Log log = LogFactory.getLog(JdbcContactDao.class); private DataSource dataSource; @Resource(name = "dataSource") public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public DataSource getDataSource() { return dataSource; } }

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

Tambin declaramos la variable log usando Apache commons-logging para registrar el mensaje dentro del programa. Y para la propiedad datasource, usamos @Resource de JSR-250 para que Spring inyecte el datasource con el nombre de dataSource. Vamos a implementar los mtodos de la interfaz ContactDao uno por uno. Mientras tanto, primero vamos a crear una implementacin vaca de todos los mtodos de la clase JdbcContactDao. Una manera sencilla de hacerlo es usando STS para generar implementaciones vacas por nosotros. En STS, en la clase haga clic-derecho y seleccione Source Override/Implement Methods (vase la Figura 8-2).

Figura 8-2. Implementando mtodos en STS

En la siguiente pantalla, todos los mtodos en la interfaz ContactDao ya deberan estar marcados, como se muestra en la Figura 8.3. Simplemente haga clic en OK y, se crear automticamente una implementacin vaca de todos los mtodos seleccionados. 28

Figura 8-3. Seleccionando los mtodos a implementar en STS

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>

No hay nada especial acerca de la configuracin, acabamos de declarar la base de datos embebida usando H2 y usamos <context:component-scan> para descubrir automticamente el bean de Spring. Teniendo la infraestructura en su lugar, ahora podemos proceder a la ejecucin de las operaciones JDBC.

Consultando Datos Usando MappingSqlQuery<T>

Spring proporciona la clase MappingSqlQuery<T> para modelar operaciones de consulta. Bsicamente, construimos una clase MappingSqlQuery<T> usando el datasource y la cadena de consulta. Por otro lado, implementamos el mtodo mapRow() para mapear cada registro del conjunto de resultados en el objeto de dominio correspondiente. Primero vamos a implementar el mtodo findAll(). Empezamos creando la clase SelectAllContacts (que representa la operacin de consulta para seleccionar todos los contactos) que extiende la clase abstracta MappingSqlQuery<T>. El Listado 8-35 muestra la clase SelectAllContacts.
Listado 8-35. La Clase SelectAllContacts package com.apress.prospring3.ch8.dao.jdbc.annotation; import java.sql.ResultSet; import java.sql.SQLException; import javax.sql.DataSource; import org.springframework.jdbc.object.MappingSqlQuery; import com.apress.prospring3.ch8.domain.Contact; public class SelectAllContacts extends MappingSqlQuery<Contact> { private static final String SQL_SELECT_ALL_CONTACT = "select id, first_name, last_name, birth_date from contact"; public SelectAllContacts(DataSource dataSource) { super(dataSource, SQL_SELECT_ALL_CONTACT); } protected Contact mapRow(ResultSet rs, int rowNum) throws SQLException { Contact contact = new Contact(); contact.setId(rs.getLong("id")); contact.setFirstName(rs.getString("first_name")); contact.setLastName(rs.getString("last_name")); contact.setBirthDate(rs.getDate("birth_date"));

30

return contact; } }

En el Listado 8-35, dentro de la clase SelectAllContacts, se declara el SQL para seleccionar todos los contactos. En el constructor de la clase, se llama al mtodo super() para construir la clase, usando tanto el DataSource como la sentencia SQL. Adems, el mtodo MappingSqlQuery<T>.mapRow() es implementado para proporcionar el mapeo del conjunto de resultados al objeto de dominio Contact. Teniendo la clase SelectAllContacts en su lugar, podemos implementar el mtodo findAll() en la clase JdbcContactDao. El Listado 8-36 muestra la clase.
Listado 8-36. Implementando el Mtodo findAll() package com.apress.prospring3.ch8.dao.jdbc.annotation; import java.util.List; import javax.annotation.Resource; import javax.sql.DataSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.stereotype.Repository; import com.apress.prospring3.ch8.dao.ContactDao; import com.apress.prospring3.ch8.domain.Contact; @Repository("contactDao") public class JdbcContactDao implements ContactDao { private Log log = LogFactory.getLog(JdbcContactDao.class); private DataSource dataSource; private SelectAllContacts selectAllContacts; @Resource(name = "dataSource") public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; selectAllContacts = new SelectAllContacts(dataSource); } public DataSource getDataSource() { return dataSource; } public List<Contact> findAll() { return selectAllContacts.execute(); } // Omitidos otras implementaciones de metodos vacos }

31

En el Listado 8-36, en el mtodo setDataSource(), despus de la inyeccin del DataSource, se construye una instancia de la clase SelectAllContacts. En el mtodo findAll(), simplemente invocamos el mtodo SelectAllContacts.execute(), que se hereda indirectamente de la clase abstracta SqlQuery<T>. Eso es todo lo que tenemos que hacer. El Listado 8-37 muestra el programa de ejemplo para probar la lgica.
Listado 8-37. Probando MappingSqlQuery package com.apress.prospring3.ch8; // Omitidas las declaraciones import public class AnnotationJdbcDaoSample { public static void main(String[] args) { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("classpath:app-context-annotation.xml"); ctx.refresh(); ContactDao contactDao = ctx.getBean("contactDao", ContactDao.class); // Encontrar y listar todos los contactos List<Contact> contacts = contactDao.findAll(); listContacts(contacts); } private static void listContacts(List<Contact> contacts) { 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 de pruebas produce el siguiente resultado:


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

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

Figura 8-4. Salida en STS con el nivel log DEBUG encendido

32

Prosigamos a implementar el mtodo findByFirstName(), que tiene un parmetro con nombre. Al igual que el ejemplo anterior, creamos la clase SelectContactByFirstName para la operacin, que se muestra en el Listado 8-38.
Listado 8-38. La Clase SelectContactByFirstName package com.apress.prospring3.ch8.dao.jdbc.annotation; // Omitidas las declaraciones import public class SelectContactByFirstName extends MappingSqlQuery<Contact> { private static final String SQL_FIND_BY_FIRST_NAME = "select id, first_name, last_name, birth_date from contact " + "where first_name = :first_name"; public SelectContactByFirstName(DataSource dataSource) { super(dataSource, SQL_FIND_BY_FIRST_NAME); super.declareParameter(new SqlParameter("first_name", Types.VARCHAR)); } protected Contact mapRow(ResultSet rs, int rowNum) throws SQLException { Contact contact = new Contact(); contact.setId(rs.getLong("id")); contact.setFirstName(rs.getString("first_name")); contact.setLastName(rs.getString("last_name")); contact.setBirthDate(rs.getDate("birth_date")); return contact; } }

La clase SelectContactByFirstName es similar a la clase SelectAllContacts (las diferencias se resaltan en negrita). En primer lugar, la sentencia SQL es diferente y lleva un parmetro con nombre llamado first_name. En el mtodo constructor, se llama al mtodo declareParameter() (que indirectamente es heredado de la clase abstracta org.springframework.jdbc.object.RdbmsOperation). Prosigamos a implementar el mtodo findByFirstName() en la clase JdbcContactDao. El Listado 8-39 muestra el fragmento de cdigo.
Listado 8-39. Implementando el Mtodo findByFirstName() package com.apress.prospring3.ch8.dao.jdbc.annotation; // Omitidas las declaraciones import @Repository("contactDao") public class JdbcContactDao implements ContactDao { private SelectContactByFirstName selectContactByFirstName; @Resource(name = "dataSource") public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; selectAllContacts = new SelectAllContacts(dataSource); selectContactByFirstName = new SelectContactByFirstName(dataSource);

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 }

En el Listado 8-39, despus de la inyeccin del datasource, se construye una instancia de SelectContactByFirstName (tenga en cuenta las lneas en negrita). Despus, en el mtodo findByFirstName(), se construye un HashMap con los parmetros con nombre y valores. Por ltimo, se llama al mtodo executeByNamedParam() (heredado indirectamente de la clase abstracta SqlQuery<T>). Para probar el mtodo, agregue el siguiente fragmento de cdigo del Listado 8-40 en la clase AnnotationJdbcDaoSample.
Listado 8-40. Probando el Mtodo findByFirstName() // Encontrar y listar todos los contactos contacts = contactDao.findAllWithDetail(); listContacts(contacts);

Al ejecutar el programa se producir la siguiente salida desde el mtodo findByFirstName():


Contacto - Id: 1, Nombre: Clarence, Apellido: Ho, Fecha de Nacimiento: 1980-07-30

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.

Actualizando Datos Usando SqlUpdate

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

public UpdateContact(DataSource dataSource) { super(dataSource, SQL_UPDATE_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.declareParameter(new SqlParameter("id", Types.INTEGER)); } }

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 }

En el Listado 8-42, despus de la inyeccin del datasource, se construye una instancia de UpdateContact (tenga en cuenta las lneas en negrita). En el mtodo update(), se construye un HashMap de parmetros con nombre pasados por el objeto Contact, y luego es llamado el mtodo updateByNamedParam() para actualizar el registro del contacto. Para comprobar el funcionamiento, agregue el siguiente fragmento de cdigo del Listado 8-43 en la clase AnnotationJdbcDaoSample.
Listado 8-43. Probando el Mtodo update() // Actualizar el contacto

35

contact = new Contact(); contact.setId(1l); contact.setFirstName("Clarence"); contact.setLastName("Peter"); contact.setBirthDate(new Date((new GregorianCalendar(1977, 10, 1)).getTime().getTime())); contactDao.update(contact); contacts = contactDao.findAll(); listContacts(contacts);

En el listado 8-43, simplemente construimos un objeto de Contact y luego invocamos el mtodo update(). Al ejecutar el programa se producir la siguiente salida desde el ltimo mtodo listContacts():
11:12:27,020 INFO 3.ch8.dao.jdbc.annotation.JdbcContactDao: 87 - Contacto actual actualizado con id: 1 Contacto - Id: 1, Nombre: Clarence, Apellido: Peter, Fecha de Nacimiento: 1977-11-01 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

En la salida, usted puede ver que el contacto con un ID de 1 fue actualizado en consecuencia.

Insertando Datos y Recuperando la Llave Generada

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

// Omitidos otros Cdigos }

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

Al ejecutar el programa se producir la siguiente salida desde el ltimo mtodo listContacts():


11:36:08,871 INFO 3.ch8.dao.jdbc.annotation.JdbcContactDao: 88 - Contacto nuevo insertado con id: 4 Contacto - Id: 1, Nombre: Clarence, Apellido: Peter, Fecha de Nacimiento: 1977-11-01 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 Contacto - Id: 4, Nombre: Rod, Apellido: Johnson, Fecha de Nacimiento: 2001-11-01

Usted puede ver que el nuevo contacto fue insertado con un ID de 4 y recuperado correctamente.

Operaciones de Procesamiento por Lotes con BatchSqlUpdate

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

Observe que en el constructor llamamos al mtodo BatchSqlUpdate.setBatchSize() para establecer el tamao del lote para la operacin JDBC de insercin. El Listado 8-48 muestra la implementacin del mtodo insertWithDetail() en la clase JdbcContactDao.
Listado 8-48. Operacin SQL de Actualizacin por Lote package com.apress.prospring3.ch8.dao.jdbc.annotation; // Omitidas las declaraciones import @Repository("contactDao") public class JdbcContactDao implements ContactDao { private private private private Log log = LogFactory.getLog(JdbcContactDao.class); DataSource dataSource; InsertContact insertContact; InsertContactTelDetail insertContactTelDetail;

public void insertWithDetail(Contact contact) { insertContactTelDetail = new InsertContactTelDetail(dataSource); // Insertar el contacto 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());

39

// Insercion por lote de los detalles telefnicos del contacto List<ContactTelDetail> contactTelDetails = contact.getContactTelDetails(); if (contactTelDetails != null) { for (ContactTelDetail contactTelDetail : contactTelDetails) { paramMap = new HashMap<String, Object>(); paramMap.put("contact_id", contact.getId()); paramMap.put("tel_type", contactTelDetail.getTelType()); paramMap.put("tel_number", contactTelDetail.getTelNumber()); insertContactTelDetail.updateByNamedParam(paramMap); } } insertContactTelDetail.flush(); } public List<Contact> findAllWithDetail() { JdbcTemplate jdbcTemplate = new JdbcTemplate(getDataSource()); String sql = "select c.id, c.first_name, c.last_name, c.birth_date" + ", t.id as contact_tel_id, t.tel_type, t.tel_number from contact c " + "left join contact_tel_detail t on c.id = t.contact_id"; return jdbcTemplate.query(sql, new ContactWithDetailExtractor()); } private static final class ContactWithDetailExtractor implements ResultSetExtractor<List<Contact>> { public List<Contact> extractData(ResultSet rs) throws SQLException, DataAccessException { Map<Long, Contact> map = new HashMap<Long, Contact>(); Contact contact = null; while (rs.next()) { Long id = rs.getLong("id"); contact = map.get(id); if (contact == null) { // nuevo registro del contacto contact = new Contact(); contact.setId(id); contact.setFirstName(rs.getString("first_name")); contact.setLastName(rs.getString("last_name")); contact.setBirthDate(rs.getDate("birth_date")); contact.setContactTelDetails( new ArrayList<ContactTelDetail>()); map.put(id, contact); } // Procesar los detalles telefnicos del contacto (si existe) Long contactTelDetailId = rs.getLong("contact_tel_id"); if (contactTelDetailId > 0) { ContactTelDetail contactTelDetail = new ContactTelDetail(); contactTelDetail.setId(contactTelDetailId); contactTelDetail.setContactId(id); contactTelDetail.setTelType(rs.getString("tel_type")); contactTelDetail.setTelNumber(rs.getString("tel_number")); contact.getContactTelDetails().add(contactTelDetail); } }

40

return new ArrayList<Contact>(map.values()); } } // Omitidos otros mtodos }

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

Al ejecutar el programa se producir la siguiente salida desde el ltimo mtodo listContacts():


Contacto - Id: 1, Nombre: Clarence, Apellido: Peter, Fecha de Nacimiento: 1977-11-01 ---Detalles Tl del Contacto - Id: 2, Contacto id: 1, Tipo: Casa, Nmero: 1234567890 ---Detalles Tl del Contacto - Id: 1, Contacto id: 1, Tipo: Mvil, Nmero: 1234567890 Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02 ---Detalles Tl del Contacto - Id: 3, Contacto id: 2, Tipo: Casa, Nmero: 1234567890 Contacto - Id: 3, Nombre: John, Apellido: Smith, Fecha de Nacimiento: 1964-02-28

41

Contacto - Id: 4, Nombre: Rod, Apellido: Johnson, Fecha de Nacimiento: 2001-11-01 Contacto - Id: 5, Nombre: Michael, Apellido: Jackson, Fecha de Nacimiento: 1964-11-01 ---Detalles Tl del Contacto - Id: 4, Contacto id: 5, Tipo: Casa, Nmero: 11111111 ---Detalles Tl del Contacto - Id: 5, Contacto id: 5, Tipo: Mvil, Nmero: 22222222

Usted puede ver que los nuevos contactos con los datos telefnicos fueron todos insertados en la base de datos.

Llamando Funciones Almacenadas Usando SqlFunction

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 ;

La funcin almacenada debera ser auto-explicativa. Simplemente acepta el ID y devuelve el nombre del contacto con el ID del registro. Creamos una nueva interfaz llamada ContactSfDao para este ejemplo. El Listado 8-51 muestra la interfaz.
Listado 8-51. La Interfaz ContactSfDao package com.apress.prospring3.ch8.dao; public interface ContactSfDao { public String getFirstNameById(Long id); }

El segundo paso es crear la clase SfFirstNameById para representar a la operacin de la funcin almacenada, que extiende a la clase SqlFunction<T>. El Listado 8-52 muestra la clase.
Listado 8-52. La Clase SfFirstNameById package com.apress.prospring3.ch8.dao.jdbc.annotation;

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

public String getFirstNameById(Long id) { List<String> result = sfFirstNameById.execute(id); return result.get(0); } }

En el Listado 8-53, despus de la inyeccin del datasource, se construye una instancia de SfFirstNameById. Luego en el mtodo getFirstNameById(), se llama al mtodo execute(), pasando el ID del contacto. El mtodo devolver una lista de Strings, y slo necesitamos el primero, porque debera devolver un slo registro en el conjunto de resultados. El Listado 8-54 muestra el archivo de configuracin de Spring para conectarse a MySQL (appcontext-sf.xml).
Listado 8-54. Configuracin Spring para Mysql <?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"> <import resource="datasource-dbcp.xml" /> <context:component-scan base-package="com.apress.prospring3.ch8.dao.jdbc.annotation" /> <context:annotation-config /> </beans>

En el Listado 8-54, es importado el archivo datasource-dbcp.xml, que tiene la configuracin del datasource a la base de datos MySQL. Para ejecutar el programa, debera agregarse la dependencia commons-dbcp al proyecto, como se muestra en la Tabla 8-5.
Tabla 8-5. Dependencia para commons-dbcp

GroupID
commons-dbcp

Artifact ID
commons-dbcp

Version Description
1.4 Librera Apache commons-dbcp para el pool de conexin a la base de datos

El Listado 8-55 muestra las pruebas del programa.


Listado 8-55. Probando la Funcin Almacenada en Mysql package com.apress.prospring3.ch8; import org.springframework.context.support.GenericXmlApplicationContext; import com.apress.prospring3.ch8.dao.ContactSfDao; public class JdbcContactSfDaoSample {

44

public static void main(String[] args) { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("classpath:app-context-sf.xml"); ctx.refresh(); ContactSfDao contactSfDao = ctx.getBean("contactSfDao", ContactSfDao.class); System.out.println(contactSfDao.getFirstNameById(1l)); } }

En el programa, pasamos un ID de 1 en la funcin almacenada. Esto devolver a Clarence como el nombre si se ejecut test-data.sql contra la base de datos MySQL. La ejecucin del programa produce el siguiente resultado:
15:16:11,990 DEBUG g.springframework.jdbc.core.JdbcTemplate: query 15:16:11,991 DEBUG g.springframework.jdbc.core.JdbcTemplate: statement [select firstnamebyid(?)] 15:16:11,998 DEBUG ramework.jdbc.datasource.DataSourceUtils: Connection from DataSource 15:16:12,289 DEBUG ramework.jdbc.datasource.DataSourceUtils: Connection to DataSource Clarence 635 - Executing prepared SQL 570 - Executing prepared SQL 110 - Fetching JDBC 332 - Returning JDBC

Usted puede ver que se recuper el nombre correctamente. Lo que es presentado aqu es slo una muestra sencilla para demostrar el mdulo de las funciones de Spring JDBC. Spring tambin ofrece otras clases (por ejemplo, StoredProcedure) para que usted pueda invocar la complejidad de los procedimientos almacenados que devuelven tipos de datos complejos. Le recomendamos que consulte el manual de referencia de Spring en caso de que necesite acceder a los procedimientos almacenados usando JDBC.

Usando la Configuracin de Java

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

En el listado anterior, usamos EmbeddedDatabaseBuilder para construir la base de datos H2 embebida, el efecto es el mismo como cuando se utiliza la etiqueta <jdbc:embedded-database> en la configuracin XML. Usted tambin puede usar la caracterstica @Profile para especificar que la configuracin es el nico blanco para un entorno especfico (por ejemplo, dev).

Proyecto Spring Data: JDBC Extensions

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 de QueryDSL: QueryDSL (www.querydsl.com) es un lenguaje especfico de dominio


que establece el marco para el desarrollo de algunas consultas de tipo seguro. Spring Data JDBC Extensions Proporcionan extensiones QueryDslJdbcTemplate para facilitar el desarrollo de aplicaciones JDBC que utilizan QueryDSL en lugar de sentencias SQL.

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.

Consideraciones para Usar JDBC

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

usted necesita desarrollar, especialmente cuando se transforma el conjunto de resultados en los correspondientes objetos de dominio. Adems de JDBC, se han desarrollado una gran cantidad de libreras de cdigo abierto para ayudar a cerrar la brecha entre la estructura de datos relacional y el modelo orientado a objetos de Java. Por ejemplo, MyBatis (anteriormente conocido como iBATIS) es un popular framework DataMapper que tambin se basa en el mapeo SQL. MyBatis le permite mapear objetos con procedimientos almacenados o consultas a un archivo descriptor XML (tambin es soportado anotaciones de Java). Al igual que Spring, MyBatis proporciona una forma declarativa para consultar el mapeo de objeto, le ahorra enormemente el tiempo que este toma para mantener consultas SQL que pueden ser dispersadas por diferentes clases DAO. Tambin hay muchos otros frameworks ORM que se centran en el modelo de objetos, en lugar de la consulta. Los ms populares son Hibernate, EclipseLink (tambin conocido como TopLink), y OpenJPA. Todos ellos cumplen con la especificacin JPA del JCP. En los ltimos aos, las herramientas ORM y los frameworks de mapeo se han vuelto mucho ms maduros por lo que la mayora de los desarrolladores se decidirn por uno de ellos, en lugar de usar JDBC directamente. Sin embargo, por motivos de rendimiento, en los casos donde usted necesite tener un control absoluto sobre la consulta que se presentar a la base de datos (por ejemplo, usando una consulta jerrquica en Oracle), Spring JDBC es realmente una opcin viable. Y usando Spring, una gran ventaja es que puedes mezclar y combinar diferentes tecnologas de acceso de datos. Por ejemplo, usted puede usar Hibernate como el ORM principal y luego JDBC como un suplemento como parte de la lgica de consultas complejas u operaciones por lotes, usted puede mezclar y combinar en una sola operacin de negocio y luego envolverlas en la misma transaccin de la base de datos. Spring le ayudar a manejar estas situaciones con facilidad.

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

Das könnte Ihnen auch gefallen