SlideShare una empresa de Scribd logo
1 de 48
Descargar para leer sin conexión
Tabla de contenido
Capitulo 8 : Soporte Spring JDBC .................................................................................................. 1
Ejemplo de Modelo de Datos para Código de Ejemplo ..................................................................... 2
Explorando la Infraestructura de JDBC .......................................................................................... 5
Infraestructura de Spring JDBC ................................................................................................... 10
   Información 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 Parámetros 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 Configuración de Java ................................................................................................. 45
Proyecto Spring Data: JDBC Extensions ....................................................................................... 46
Consideraciones para Usar JDBC ................................................................................................. 46
Resumen .................................................................................................................................. 47
Capitulo 8: Soporte Spring JDBC
En los capítulos anteriores, hemos visto lo fácil que es construir una aplicación totalmente
administrada por Spring. Ahora usted tiene una sólida comprensión de la configuración bean y de la
Programación Orientada a Aspectos (AOP), en otras palabras, usted sabe cómo cablear la aplicación
entera usando Spring. Sin embargo, falta una de las piezas del rompecabezas: ¿cómo conseguir los
datos que maneja la aplicación?
        Además de utilidades desechables simples de línea de comandos, casi todas las aplicaciones
necesitan conservar los datos en algún tipo de almacén de datos. El almacén de datos más usual y
conveniente es una base de datos relacional.
        Las bases de datos relacionales de código abierto más destacadas son, quizás, MySQL
(www.mysql.com) y PostgreSQL (www.postgresql.org). En términos de características RDBMS
proporcionadas, ambas bases de datos son aproximadamente los mismos. MySQL es por lo general
más ampliamente utilizado para el desarrollo de aplicaciones web, especialmente en la plataforma
Linux. Por otro lado, PostgreSQL es más 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 más rápida y confiable, no puede permitirse perder la
velocidad ofrecida y flexibilidad usando una capa de acceso a datos mal diseñada 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 código de acceso a datos afecta a toda la aplicación, no importa lo bien
diseñada que este.
        En este capítulo, te mostramos cómo puedes usar Spring para simplificar la implementación
del código de acceso a datos usando JDBC. Empezamos viendo la horrible cantidad de código 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 sorprendente-
Spring te permite utilizar todo el poder de consultas SQL concertadas por el humano, mientras reduce
al mínimo la cantidad de código de apoyo que usted necesita para poner en práctica. En concreto,
hablaremos de lo siguiente:

      Comparando código JDBC tradicional y el soporte de Spring JDBC: Exploramos cómo Spring
       simplifica el antiguo estilo del código JDBC manteniendo la misma funcionalidad. Usted
       también verá cómo Spring accede a la API JDBC de bajo nivel y cómo esta API de bajo nivel es
       mapeada a las clases prácticas, como JdbcTemplate.

      Conectándose a la base de datos: Aunque no entremos en cada pequeño detalle del manejo
       de la conexión a la base de datos, le mostramos las diferencias fundamentales entre un
       sencillo Connection y un DataSource. Naturalmente, hablamos de cómo Spring maneja los
       DataSources y qué datasources usted puede utilizar en sus aplicaciones.

      Recuperando y mapeando los datos a objetos Java: Te mostramos cómo recuperar los datos y
       luego mapear con eficacia los datos seleccionados a objetos Java. También usted aprende que
       Spring JDBC es una alternativa viable para herramientas de mapeo de objeto-relacional (ORM).

      Insertando, actualizando y eliminando datos: Finalmente, discutimos cómo 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 código que
       usted ha escrito.




                                                                                   1
¿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 máquina
física que ejecuta el administrador de procesos. Formalmente, una base de datos es una colección de
datos, el software de base de datos (como Oracle, PostgreSQL, MySQL, etc.) es llamado el software
de gestión de bases de datos o, más específicamente, un sistema de gestión de bases de datos
relacionales (RDBMS), la instancia de un RDBMS es llamado motor de base de datos y, por último, la
máquina 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 término base de
datos desde el contexto en el que es usado. Es por eso que usamos este término para representar los
cuatro significados que acabamos de describir.

En los últimos años, debido al crecimiento explosivo de Internet y las tecnologías de computación en
la nube, una gran cantidad de aplicaciones web para fines-específicos, han surgido, tales como las
redes sociales, motores de búsqueda, mapas, videos, etc. Para atender los requerimientos específicos
de acceso a datos de aquellas aplicaciones, también se han desarrollado muchas categorías 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 gráficas, bases de datos centradas en
documentos, etcétera. Por lo tanto, la base de datos ahora es un término mucho más amplio. Sin
embargo, una discusión 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 Código de Ejemplo
Antes de proceder a la discusión, nos gustaría introducir un modelo de datos muy simple que se
utilizará para los ejemplos de este capítulo, así como para los próximos capítulos cuando se hable de
otras técnicas 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 información de contacto de una persona, y la otra tabla es
CONTACT_TEL_DETAIL, que almacena los detalles telefónicos de un contacto. Cada contacto puede
tener cero o más números de teléfono, en otras palabras, es una relación de uno-a-muchos entre
CONTACT y CONTACT_TEL_DETAIL. La información de un contacto incluye su nombre, apellido y fecha
de nacimiento, mientras que una parte de información telefónica detallada incluye el tipo de teléfono
(Móvil, Casa, etc.) y el número de teléfono correspondiente. La Figura 8-1 muestra el diagrama
entidad-relación (ER) de la base de datos.




Figura 8-1. Ejemplo del Modelo de Datos para el Código de Ejemplo
    Como puede ver, ambas tablas tienen una columna ID que será asignada automáticamente por la
base de datos durante la inserción. Para la tabla CONTACT_TEL_DETAIL, hay una relación de llave
foránea 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).



                                                                                   2
Nota: El modelo de datos fue creado usando un plugin de Eclipse llamado Clay Mark II. La versión
   sin licencia puede ser utilizada libremente para crear modelos de datos para bases de datos
   gratuitas y de código abierto como MySQL, PostgreSQL, HSQL, Derby, y etcétera. Usted no
   necesita el plug-in para ejecutar el código de ejemplo, ya que los scripts para crear las tablas se
   proporcionan con el código de ejemplo. Sin embargo, el archivo del diagrama del modelo (situado
   en ch8/data-model/prospring3-ch8-datamodel.clay) se incluyó en el código de ejemplo, y si
   usted está interesado, puede instalar el plug-in y ver el diagrama (por favor consulte
   www.azzurri.co.jp para más detalles).

   El Listado 8-1 muestra el script para la creación 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', '1980-
07-30');
insert into contact (first_name, last_name, birth_date) values ('Scott', 'Tiger', '1990-
11-02');
insert into contact (first_name, last_name, birth_date) values ('John', 'Smith', '1964-02-
28');

insert into contact_tel_detail (contact_id, tel_type, tel_number) values (1, 'Móvil',
'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');




                                                                                   3
En secciones posteriores de este capítulo, usted verá ejemplos para recuperar los datos de la
base de datos a través 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   Long id;
       private   String firstName;
       private   String lastName;
       private   Date birthDate;
       private   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   Long id;
       private   Long contactId;
       private   String telType;
       private   String telNumber;

       // métodos 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 información del contacto. El listado 8-5 muestra la interfaz
ContactDao.

Listado 8-5. La Interfaze ContactDao

package com.apress.prospring3.ch8.dao;



                                                                                 4
import java.util.List;

import com.apress.prospring3.ch8.domain.Contact;

public interface ContactDao {

       public   List<Contact> findAll();
       public   List<Contact> findByFirstName(String firstName);
       public   void insert(Contact contact);
       public   void update(Contact contact);
       public   void delete(Long contactId);

}

        En la interfaz anterior, se definen dos métodos de búsqueda y los métodos insert, update, y
delete, respectivamente. Que corresponden con los términos 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 módulo 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 código fuente para el proyecto del Capítulo 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, después 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 estándar para que las aplicaciones Java puedan acceder a los
datos almacenados en una base de datos. El núcleo de la infraestructura JDBC es un controlador que
es específico para cada base de datos, es este controlador el que permite que el código 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 métodos
estáticos para establecer conexiones con la base de datos. El método getConnection() de
DriverManager devuelve una implementación 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 código
administre las conexiones a la base de datos. Una conexión 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


                                                                                    5
conexión. Además, el número de conexiones simultáneas por lo general es limitada, y un número
excesivo de conexiones abiertas ralentiza la base de datos.
       Le mostraremos cómo 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 través 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 términos de rendimiento) de
crear una conexión para cada declaración. Esto en gran medida reduce el rendimiento de Java y
añade tensión adicional a la base de datos porque una conexión tiene que ser establecida por cada
consulta. Sin embargo, si mantenemos una conexión abierta, podríamos traer el servidor de base de
datos a una parada. El Listado 8-7 muestra el código necesario para manejar una conexión JDBC,
usando MySQL como un ejemplo.

Listado 8-7. Manejando una Conexión 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 código no es muy completo, pero te da una idea de los pasos que necesitas para manejar
una conexión JDBC. Este código no considera incluso tratar con pool de conexiones, que es una
técnica común para gestionar conexiones a bases de datos con más eficacia. No hablamos sobre pool
de conexiones en este punto (pool de conexiones se discute en la sección "Conexiones a Base de
Datos y DataSources" más adelante en este capítulo), en cambio, en el Listado 8-8, mostramos una
implementación de los métodos findAll(), insert() y delete() de la interfaz ContactDao usando
JDBC puro.




                                                                                6
Listado 8-8. Implementación de JDBC DAO Puro

package com.apress.prospring3.ch8.dao.plain;

import   java.sql.Connection;
import   java.sql.DriverManager;
import   java.sql.PreparedStatement;
import   java.sql.ResultSet;
import   java.sql.SQLException;
import   java.sql.Statement;
import   java.util.ArrayList;
import   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"));



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



                                                                          8
El Listado 8-9 muestra una prueba del programa principal con la anterior implementación DAO
en acción.

Listado 8-9. Probando la Implementación 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 después de crear el contacto nuevo:");
              listAllContacts();

              System.out.println();

              // Eliminar el contacto nuevo recién creado
              System.out.println("Eliminando el contacto recién creado");
              contactDao.delete(contact.getId());
              System.out.println(
              "Listando los datos de contact después de eliminar el contacto nuevo:");
              listAllContacts();

       }

       private static void listAllContacts() {

              List<Contact> contacts = contactDao.findAll();

              for (Contact contact : contacts) {
                    System.out.println(contact);
              }


                                                                                9
}
}

       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        Artifact ID               Version         Description
mysql          mysql-connector-java      5.1.18          Librería 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 contraseña establecida a prospring3, debería ser capaz de acceder al esquema
de la base de datos, y usted debería ejecutar los scripts schema.sql y test-data.sql contra la base
de datos para crear las tablas y cargar los datos iniciales):

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

Insertar   un nuevo contacto
Listando   los datos de contact después de crear 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
Contacto   - Id: 4, Nombre: Jacky, Apellido: Chan, Fecha de Nacimiento: 2001-11-01

Eliminando el contacto recién creado
Listando los datos de contact después 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 líneas muestra los datos iniciales. El
segundo bloque de líneas muestra que el nuevo registro fue añadido. El bloque final de las líneas
muestra que el contacto recién creado se ha eliminado.
        Como usted puede ver en el Listado 8-8, una gran cantidad de código debe ser trasladado a
una clase de ayuda o-peor aún-duplicado en cada clase DAO. Esta es la principal desventaja de JDBC
desde el punto de vista del programador de la aplicación-que, simplemente usted no tiene tiempo
para programar el código repetitivo en todas las clases DAO. En su lugar, usted desea concentrarse
en escribir el código que realmente hace lo que usted necesita, hacer la clase DAO: seleccionar,
actualizar y borrar los datos. Usted necesita escribir más código de ayuda, necesita verificar más las
excepciones a manejar, y puede que usted presente más errores en su código.
        Es aquí donde un framework DAO y Spring entran. Un framework elimina el código que
realmente no realiza ninguna lógica personalizada y le permite olvidarse de todos los tareas que debe
realizar. Además, el amplio soporte de Spring JDBC hace su vida mucho más fácil.

Infraestructura de Spring JDBC
El código del cual hablamos en la primera parte del capítulo no es muy complejo, pero es molesto
para escribir, y porque hay mucho de esto para escribir, la probabilidad de errores de codificación es



                                                                                   10
bastante alta. Es tiempo de echar un vistazo de cómo Spring hace las cosas más fáciles y más
elegantes.

Información 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                                       Descripción
org.springframework.jdbc.core                 Contiene las bases de las clases JDBC en Spring. Este
                                              incluye el núcleo de la clase JDBC, JdbcTemplate, que
                                              simplifica las operaciones de programación a la base
                                              de datos con JDBC. Algunos sub paquetes
                                              proporcionan soporte de acceso a datos JDBC con
                                              propósitos más específicos (por ejemplo, una clase
                                              JdbcTemplate que soporta parámetros con nombre) y
                                              soporte de clases relacionadas también.

org.springframework.jdbc.datasource           Contiene las clases de ayuda y de las
                                              implementaciones DataSource que usted puede utilizar
                                              para ejecutar código JDBC fuera de un contenedor
                                              JEE. Algunos sub paquetes proporcionan soporte a
                                              bases de datos embebidas, inicialización de base de
                                              datos, y varios mecanismos de búsqueda de
                                              datasource.

org.springframework.jdbc.object               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.

org.springframework.jdbc.support              La clase más importante de este paquete es el soporte
                                              de traducción de SQLException. Esto permite que
                                              Spring reconozca los códigos de error utilizados por la
                                              base de datos y mapearlos a las excepciones de más
                                              alto nivel.

org.springframework.jdbc.config               Contiene clases que soportan la configuración 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>).


       Vamos a empezar la discusión del soporte de Spring JDBC mirando la funcionalidad del nivel
más bajo. Lo primero que usted tiene que hacer antes de siquiera pensar cómo ejecutar las consultas
SQL, es establecer una conexión con la base de datos.




                                                                                  11
Conexiones a Base de Datos y DataSources
Usted puede usar Spring para manejar la conexión 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
implementación más sencilla de un DataSource. Al observar el nombre de la clase, usted puede
adivinar que sencillamente llama a DriverManager para obtener una conexión. El hecho de que
DriverManagerDataSource no soporte el pool de conexiones a bases de datos hace que esta clase
sea inadecuada para algo más que pruebas. La configuración de DriverManagerDataSource es
bastante sencilla, como usted puede ver en el Listado 8-10, sólo tiene que proporcionar el nombre de
la clase del controlador, una URL de conexión, un nombre de usuario y una contraseña (datasource-
drivermanager.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 información de
la conexión a la base de datos normalmente se almacena en un archivo de propiedades para un fácil
mantenimiento y sustitución 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 información
de la conexión.

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 aún más el


                                                                                  12
rendimiento de la aplicación. Usted podría utilizar un DataSource en el código JDBC puro y obtener
los mismos beneficios del pooling, sin embargo, en la mayoría de los casos, usted todavía perdería un
lugar central para configurar el datasource. Spring, por el contrario, le permite declarar un bean
dataSource y establecer las propiedades de conexión en los archivos de definición de
ApplicationContext (vea el Listado 8-12, y el nombre del archivo es datasource-dbcp.xml).

   Nota: Además de Apache Commons BasicDataSource, otras librerías populares de pool de
   conexiones  de   base     de     datos    de    código     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 más 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 aplicación que usted está
desarrollando se va a ejecutar en un contenedor JEE, usted puede tomar ventaja del pool de conexión
manejado por el contenedor. Para usar un dataSource basado en JNDI, usted necesita cambiar la
declaración 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 búsqueda del
datasource JNDI. A partir de la versión 2.5, Spring proporciona el espacio de nombres jee, lo que
simplifica aún más la configuración. El Listado 8-14 muestra la misma configuración 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 (resource-
ref) en el archivo descriptor de la aplicación (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 posición, que usted tiene que cambiar
dependiendo de cómo su módulo esté empaquetado. Por ejemplo, se convierte en <web-app> en el
descriptor de despliegue web (WEB-INF/web.xml) si la aplicación es un módulo web. Lo más probable
es que usted tendrá que configurar el resource-ref en un archivo descriptor de la aplicación del
servidor-específico también. 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 implementación real o la ubicación del datasource del resto del
código de la aplicación. En otras palabras, sus clases DAO no saben y no tienen que saber dónde
señala el DataSource.
       La administración de la conexión también 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 versión 3.0, Spring también ofrece el soporte de base de datos embebidas, que inicia
automáticamente una base de datos embebida y la expone como un DataSource para la aplicación.
El Listado 8-16 muestra la configuración 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>. Después, usamos <jdbc:embedded-database> para declarar la base de datos embebida y
asignarla con un ID de dataSource. Dentro de la etiqueta, también 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 Definición de Datos (DDL) siempre debería de aparecer en primer lugar,
seguido por el archivo con el Lenguaje de Manipulación de Datos (DML). Para el atributo type,
especificamos el tipo de base de datos embebida a usar. A partir de la versión 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 capítulo, usaremos la base de datos embebida para ejecutar el código de ejemplo,
por lo que su máquina 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 vacía y una implementación sencilla de la
misma. Vamos a añadir más características a medida que avanzamos y explicamos lo que lo hagamos
con las clases de Spring JDBC. El listado 8-17 muestra la interfaz ContactDao vacía.

Listado 8-17. Interfaz e Implementación de ContactDao

public interface ContactDao {}
public class JdbcContactDao implements ContactDao {}




                                                                                   15
Para la implementación sencilla, primero añadiremos una propiedad dataSource. La razón por
la que deseamos agregar la propiedad dataSource a la implementación de la clase en lugar de la
interfaz debería ser bastante obvia: la interfaz no necesita saber cómo se van a recuperar y actualizar
los datos. Añadiendo los métodos 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 práctica muy buena de diseño. 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
implementación 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 Aplicación de Spring con los Beans dataSource y contactDao

       <!-- Declaración 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 añadir 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                   Version          Description
com.h2database h2                            1.3.160          Librería 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 práctica asegurarse de que se han establecido todas las propiedades requeridas
de un bean. La forma más sencilla de hacerlo es implementar la interfaz InitializingBean y
proporcionar una implementación para el método 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 información sobre la inicialización de bean, consulte el Capítulo 5.

Listado 8-20. Implementación 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 código que hemos visto hasta ahora utiliza Spring para manejar el datasource y presenta la
interfaz ContactDao y su implementación JDBC. También se establece la propiedad dataSource en la
clase JdbcContactDao en el archivo ApplicationContext de Spring. Ahora ampliamos el código
añadiendo las operaciones DAO reales a la interfaz y a la implementación.

Manejo de Excepciones
Debido a que los defensores de Spring usan excepciones en tiempo de ejecución en lugar de las
excepciones comprobadas, usted necesita un mecanismo para traducir la SQLException comprobada
en una excepción en tiempo de ejecución de Spring JDBC. Dado que las excepciones de Spring SQL
son excepciones en tiempo de ejecución, ellas pueden ser mucho más detalladas que las excepciones
comprobadas. (Por definición, esta no es una característica de las excepciones en tiempo de
ejecución, pero es muy incómodo tener que declarar una larga lista de excepciones comprobadas en
la cláusula throws, por lo que las excepciones comprobadas tienden a ser mucho más genéricas que
sus equivalentes en tiempo de ejecución.)
        Spring    proporciona     una     implementación    predeterminada     de    la    interfaz
SQLExceptionTranslator, que se encarga de traducir los códigos SQL genéricos de error en las
excepciones de Spring JDBC. En la mayor parte los casos, esta implementación es más que suficiente,
pero podemos extender la implementación predeterminada de Spring y establecer nuestra nueva


                                                                                  17
implementación SQLExceptionTranslator para ser usada en JdbcTemplate, como muestra el
Listado 8-21.
       A la vez, tenemos que añadir 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                Version       Description
org.springframework spring-jdbc                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 código de ejemplo para este propósito.

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
       // búsqueda de la traducción 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 traducción personalizada de excepciones ocurrirá cuando el código de error sea -12345. Para
otros errores, Spring retrocederá a su mecanismo por defecto para la traducción 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 leído acerca de la clase JdbcTemplate, vamos a hablar de ello con más detalle.

La Clase JdbcTemplate
Esta clase representa el núcleo del soporte de Spring JDBC. Esta puede ejecutar todo tipo de
sentencias SQL. Desde el punto de vista más simplista, usted puede clasificar las sentencias de
definición y manipulación de datos. La cobertura de sentencias de definición de datos crea varios
objetos de base de datos (tablas, vistas, procedimientos almacenados, etc.). Las sentencias de
manipulación 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
ningún 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 sección, iremos a través de varios casos de uso común para la programación JDBC en
Spring con la clase JdbcTemplate.

Inicializando JdbcTemplate en una Clase DAO
Antes de discutir cómo usar JdbcTemplate, echemos un vistazo cómo preparar JdbcTemplate para
su uso en la clase DAO. Es muy sencillo, la mayor parte del tiempo usted sólo necesita construir la
clase pasandole el objeto datasource (que debería ser inyectado por Spring en la clase DAO). El
Listado 8-23 muestra el fragmento de código 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 práctica general consiste en inicializar el JdbcTemplate dentro del método setDatasource
para que una vez que el datasource sea inyectado por Spring, el JdbcTemplate también sea
inicializado y esté listo para su uso.
         Una vez configurado, el JdbcTemplate es thread safe. Eso significa que usted también
puede optar por iniciar una única instancia de JdbcTemplate en la configuración XML de Spring y
tener esta inyección en todos los beans DAO.

   Nota: En el módulo 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á
   automáticamente.




                                                                                 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. Añadamos primero el método en la interfaz
ContactDao:

       public String findFirstNameById(Long id);

      Usando JdbcTemplate, podemos recuperar el valor con facilidad. El Listado 8-24 muestra la
implementación del método findFirstNameById() en la clase JdbcContactDao. Para los otros
métodos, se crearon implementaciones vacías.

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
parámetros que se pasan al SQL para enlazar el parámetro en forma de un objeto array. El último
argumento es el tipo a ser devuelto, que en este caso es un String. Además de Object, usted
también 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 ejecución del programa produce el siguiente resultado:

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

Usando Parámetros con Nombres con NamedParameterJdbcTemplate
En el ejemplo anterior, estamos usando el marcador de posición normal (el carácter ?) como
parámetros de consulta. Como también usted puede ver, tenemos que pasar los parámetros como un
array Object. Cuando se utiliza un marcador de posición normal, el orden es muy importante, y el
orden en que usted pone los parámetros en el array debería ser el mismo orden de los parámetros en
la consulta.
       Algunos desarrolladores (como yo) prefieren utilizar parámetros con nombre para asegurar
que el parámetro está vinculado exactamente como quería. En Spring, una variante de
JdbcTemplate,       llamada      NamedParameterJdbcTemplate           (dentro      del    paquete
org.springframework.jdbc.core.namedparam), proporciona soporte para esto. Veamos cómo
funciona.
       Por ejemplo, en esta ocasión queremos añadir otro método para encontrar el apellido por ID,
así que vamos a agregar el método a la interfaz ContactDao:

       public String findLastNameById(Long id);

       La inicialización de NamedParameterJdbcTemplate es la misma que JdbcTemplate, por lo
que sólo tenemos que declarar una variable con el tipo NamedParameterJdbcTemplate y añadir la
siguiente línea en la clase DAO del método setDataSource():

       this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);

       Ahora veamos cómo implementar el método. El Listado 8-26 muestra la implementación.

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 métodos 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 posición ?, fue usado el parámetro
con nombre (prefijado por dos puntos). En segundo lugar, fue inicializado un SqlParameterSource,
que es un Map-basado en la fuente de los parámetros SQL con la llave como el nombre del parámetro
llamado y el valor como el valor del parámetro. En lugar de SqlParameterSource, usted también
puede simplemente construir un map para el almacenamiento de parámetros con nombre. El Listado
8-27 es una variante del método 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 métodos 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 código, sólo tiene que añadir el método 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 más
filas y luego transformar cada línea 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 acción implementando el método findAll() de la interfaz ContactDao
utilizando la interfaz de RowMapper<T>. El Listado 8-28 muestra la implementación del método
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 métodos 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 estática interna llamada ContactMapper que
implementa la interfaz RowMapper<T>. La clase debe proporcionar la implementación mapRow(), que
transforma los valores en un registro específico del conjunto de resultados en el objeto de dominio
que usted desee. Debido a que es una clase interna estática le permite compartir el RowMapper<T>
entre los múltiples métodos de búsqueda.
        Después, el método findAll() sólo tiene que invocar el método query y pasarla en la cadena
de consulta y el row mapper. En el caso de que la consulta requiera parámetros, el método query()
proporciona una sobrecarga que acepta los parámetros de la consulta.
        Añadimos el siguiente fragmento de código (Listado 8-29) en el programa de pruebas (la clase
JdbcContactDaoSample).

Listado 8-29. Fragmento de Código 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 más 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 más complicada, tenemos que usar
la interfaz ResultSetExtractor. Para demostrar su uso, añadiremos un método más,
findAllWithDetail(), en la interfaz ContactDao. El método debería poblar la lista de contactos con
sus detalles telefónicos.

       public List<Contact> findAllWithDetail();

      El Listado 8-30 muestra la implementación del método 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 código es muy parecido al ejemplo RowMapper, pero esta vez declaramos una clase interna
que implementa ResultSetExtractor. Luego implementamos el método extractData() para en
consecuencia transformar el conjunto de resultados en una lista de objetos Contact. Para el método
findAllWithDetail(), la consulta utiliza un left join para unir las dos tablas y que también sean
recuperados los contactos sin teléfonos. El resultado es un producto Cartesiano de las dos tablas. Por
último, usamos el método JdbcTemplate.query(), pasándole la cadena de consulta y el
resultsetExtractor.
       Agreguemos el siguiente fragmento de código (Listado 8-31) en el programa de pruebas (la
clase JdbcContactDaoSample).

Listado 8-31. Fragmento de Código 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 Tél - Id: 2, Contacto id: 1, Tipo: Casa, Número: 1234567890
---Contacto Detalles Tél - Id: 1, Contacto id: 1, Tipo: Móvil, Número: 1234567890

Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02
---Contacto Detalles Tél - Id: 3, Contacto id: 2, Tipo: Casa, Número: 1234567890

Contacto - Id: 3, Nombre: John, Apellido: Smith, Fecha de Nacimiento: 1964-02-2

       Usted puede ver que los contactos y sus detalles telefónicos fueron listados como corresponde.
Los datos están basados en los scripts que cargan los datos del Listado 8-2.
       Hasta ahora, usted ha visto cómo usar JdbcTemplate para realizar algunas operaciones de
consulta común. JdbcTemplate (y también la clase NamedParameterJdbcTemplate) también ofrece
una serie de sobrecarga de métodos update() que soportan operaciones de actualización de datos,


                                                                                    25
incluyendo insert, update, delete, etcétera. Sin embargo, el método update() es bastante auto-
explicativo, por lo que decidimos no cubrirlo en esta sección.
        Por otro lado, como se verá en secciones posteriores, usaremos la clase SqlUpdate
proporcionada por Spring para realizar operaciones de actualización de datos.

Clases Spring que Modelan Operaciones JDBC
En la sección anterior, vimos cómo JdbcTemplate y las clases de utilidad relacionadas con el mapeo
de datos han simplificado enormemente el modelo de programación en el desarrollo de la lógica de
acceso a datos con JDBC. Construir sobre JdbcTemplate, Spring también proporciona un número de
clases útiles que modelan las operaciones JDBC de datos y permiten a los desarrolladores mantener la
consulta y transformar la lógica desde el conjunto de resultados a objetos de dominio de una manera
más 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 método mapRow() en una sola clase.

       SqlUpdate: La clase SqlUpdate le permite ajustar cualquier sentencia de actualización SQL en
       el mismo. También proporciona una gran cantidad de funciones útiles para enlazar parámetros
       SQL, recuperar la llave de un RDBMS-generada después de que un nuevo registro es
       insertado, etcétera.

       BatchSqlUpdate: Como su nombre lo indica, la clase le permite realizar operaciones de
       actualización por lotes. Por ejemplo, usted puede recorrer a través de un objeto List de Java
       y hacer que BatchSqlUpdate encole los registros y enviar las sentencias de actualización para
       usted en un lote. Usted puede configurar el tamaño del lote y nivelar la operación 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. También existe otra clase,
       StoredProcedure, que le ayuda a invocar los procedimientos almacenados.

   Nota: En secciones anteriores, todo el código de ejemplo usa la configuración de tipo XML. Por lo
   tanto, en las siguientes secciones, usaremos las anotaciones de Spring para la configuración del
   ApplicationContext. En caso de que decida adoptar la configuración XML en la aplicación,
   creemos que usted tendrá una buena idea de cómo hacerlo.

Configurando JDBC DAO para Usar Anotaciones
Primero vamos a mirar cómo configurar la implementación de la clase DAO usando anotaciones en
primer lugar. El Listado 8-32 muestra la interfaz de la clase ContactDao con un listado más 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 declaración inicial y la inyección de la propiedad datasource
usando la anotación 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 código de acceso a datos, @Repository también
instruye a Spring a realizar excepciones SQL específicas de la bases de datos a la jerarquía
DataAccessException más amigables con la aplicación en Spring.



                                                                                     27
También 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 métodos de la interfaz ContactDao uno por uno. Mientras tanto,
primero vamos a crear una implementación vacía de todos los métodos de la clase JdbcContactDao.
Una manera sencilla de hacerlo es usando STS para generar implementaciones vacías por nosotros.
En STS, en la clase haga clic-derecho y seleccione Source → Override/Implement Methods (véase
la Figura 8-2).




Figura 8-2. Implementando métodos en STS

      En la siguiente pantalla, todos los métodos en la interfaz ContactDao ya deberían estar
marcados, como se muestra en la Figura 8.3. Simplemente haga clic en OK y, se creará
automáticamente una implementación vacía de todos los métodos seleccionados.


                                                                             28
Figura 8-3. Seleccionando los métodos a implementar en STS

        Después, usted verá que se han generado implementaciones vacías de los métodos. A
continuación podemos proceder a implementar los métodos progresivamente.
El Listado 8-34 muestra la configuración XML para Spring usando anotaciones (app-context-
annotation.xml).

Listado 8-34. Configuración 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 configuración, acabamos de declarar la base de datos
embebida usando H2 y usamos <context:component-scan> para descubrir automáticamente el
bean de Spring. Teniendo la infraestructura en su lugar, ahora podemos proceder a la ejecución de
las operaciones JDBC.

Consultando Datos Usando MappingSqlQuery<T>
Spring proporciona la clase MappingSqlQuery<T> para modelar operaciones de consulta.
Básicamente, construimos una clase MappingSqlQuery<T> usando el datasource y la cadena de
consulta. Por otro lado, implementamos el método mapRow() para mapear cada registro del conjunto
de resultados en el objeto de dominio correspondiente.
       Primero vamos a implementar el método findAll(). Empezamos creando la clase
SelectAllContacts (que representa la operación 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 método super() para construir la clase,
usando     tanto    el   DataSource       como      la    sentencia   SQL.   Además,    el     método
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 método
findAll() en la clase JdbcContactDao. El Listado 8-36 muestra la clase.

Listado 8-36. Implementando el Método 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 vacíos
}




                                                                                    31
En el Listado 8-36, en el método setDataSource(), después de la inyección del DataSource,
se construye una instancia de la clase SelectAllContacts. En el método findAll(), simplemente
invocamos el método 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 lógica.

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á también 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 método findByFirstName(), que tiene un parámetro con
nombre. Al igual que el ejemplo anterior, creamos la clase SelectContactByFirstName para la
operación, 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
parámetro con nombre llamado first_name. En el método constructor, se llama al método
declareParameter()       (que    indirectamente   es    heredado    de   la   clase   abstracta
org.springframework.jdbc.object.RdbmsOperation). Prosigamos a implementar el método
findByFirstName() en la clase JdbcContactDao. El Listado 8-39 muestra el fragmento de código.

Listado 8-39. Implementando el Método 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 código
}

        En el Listado 8-39, después de la inyección del datasource, se construye una instancia de
SelectContactByFirstName (tenga en cuenta las líneas en negrita). Después, en el método
findByFirstName(), se construye un HashMap con los parámetros con nombre y valores. Por último,
se llama al método executeByNamedParam() (heredado indirectamente de la clase abstracta
SqlQuery<T>). Para probar el método, agregue el siguiente fragmento de código del Listado 8-40 en
la clase AnnotationJdbcDaoSample.

Listado 8-40. Probando el Método findByFirstName()

       // Encontrar y listar todos los contactos
       contacts = contactDao.findAllWithDetail();
       listContacts(contacts);

       Al ejecutar el programa se producirá la siguiente salida desde el método findByFirstName():

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

         Un punto a destacar aquí es que MappingSqlQuery<T> sólo es adecuado para mapear una
sola fila a un objeto de dominio. Para un objeto anidado, usted todavía tiene que usar JdbcTemplate
con ResultSetExtractor como en el ejemplo del método findAllWithDetail() presentado en la
sección 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 actualización.

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 debería ser familiar para usted ahora. Se construye una instancia de la clase
SqlUpdate con la consulta, y se declaran también los parámetros con nombre.
      El Listado 8-42 muestra la implementación del método 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 códigos
}

      En el Listado 8-42, después de la inyección del datasource, se construye una instancia de
UpdateContact (tenga en cuenta las líneas en negrita). En el método update(), se construye un
HashMap de parámetros con nombre pasados por el objeto Contact, y luego es llamado el método
updateByNamedParam() para actualizar el registro del contacto. Para comprobar el funcionamiento,
agregue el siguiente fragmento de código del Listado 8-43 en la clase AnnotationJdbcDaoSample.

Listado 8-43. Probando el Método 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
método update(). Al ejecutar el programa se producirá la siguiente salida desde el último método
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, también usamos la clase SqlUpdate. Sin embargo, un punto interesante aquí es
acerca de la llave primaria, la columna id, que sólo estará disponible sólo después de que la
sentencia de inserción 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 operación de inserción.
        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 inserción con la consulta. Sin embargo, en
nuestro caso, ¿cómo podemos recuperar la llave generada por un RDBMS después de que el registro
es insertado?
        En antiguas versiones de JDBC, el método 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 versión 3.0 de JDBC, fue añadida una nueva característica que
permite recuperar una llave generada por un RDBMS de una manera unificada. El Listado 8-37
muestra la implementación del método insert(), que también recupera la llave generada para el
registro del contacto insertado. Esto funcionará en la mayor parte de las bases de datos (si no todas),
sólo asegúrese 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 operación de inserción, 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
Capitulo 8   soporte spring jdbc 0
Capitulo 8   soporte spring jdbc 0
Capitulo 8   soporte spring jdbc 0
Capitulo 8   soporte spring jdbc 0
Capitulo 8   soporte spring jdbc 0
Capitulo 8   soporte spring jdbc 0
Capitulo 8   soporte spring jdbc 0
Capitulo 8   soporte spring jdbc 0
Capitulo 8   soporte spring jdbc 0
Capitulo 8   soporte spring jdbc 0
Capitulo 8   soporte spring jdbc 0

Más contenido relacionado

La actualidad más candente

Modelo jerarquico y modelo de red de base de datos
Modelo jerarquico y modelo de red de base de datosModelo jerarquico y modelo de red de base de datos
Modelo jerarquico y modelo de red de base de datosFernando Baculima
 
Fundamentos de base de datos 1a. unidad
Fundamentos de base de datos 1a. unidadFundamentos de base de datos 1a. unidad
Fundamentos de base de datos 1a. unidademilio_ambrosio
 
Seguridad En Base De Datos
Seguridad En Base De DatosSeguridad En Base De Datos
Seguridad En Base De DatosWilliam Suárez
 
Introducción a las bases de datos
Introducción a las bases de datosIntroducción a las bases de datos
Introducción a las bases de datosMaria Garcia
 
Clase I Estructura de Datos
Clase I Estructura de Datos Clase I Estructura de Datos
Clase I Estructura de Datos guestc906c2
 
Redundancia de datos
Redundancia de datosRedundancia de datos
Redundancia de datosdiegorap
 
CLASE 3_ArquiteturaBD_UsuariosBD_IndependiciaLogFis_ModelosBD.pdf
CLASE 3_ArquiteturaBD_UsuariosBD_IndependiciaLogFis_ModelosBD.pdfCLASE 3_ArquiteturaBD_UsuariosBD_IndependiciaLogFis_ModelosBD.pdf
CLASE 3_ArquiteturaBD_UsuariosBD_IndependiciaLogFis_ModelosBD.pdfMaria Garcia
 
Exposicion base datos
Exposicion  base datosExposicion  base datos
Exposicion base datosUNEFA
 
Dbms interview questions
Dbms interview questionsDbms interview questions
Dbms interview questionsambika93
 
Historia de la tecnologia de base de datos
Historia de la tecnologia de base de datosHistoria de la tecnologia de base de datos
Historia de la tecnologia de base de datosralbarracin
 
Usuarios y administrador de bases de datos
Usuarios y administrador de bases de datosUsuarios y administrador de bases de datos
Usuarios y administrador de bases de datosMaria Garcia
 
Crear base de datos mysql command
Crear base de datos mysql commandCrear base de datos mysql command
Crear base de datos mysql commandLouis Jhosimar
 

La actualidad más candente (20)

Programacion visual
Programacion visualProgramacion visual
Programacion visual
 
MySQL JOINS
MySQL JOINSMySQL JOINS
MySQL JOINS
 
Modelo jerarquico y modelo de red de base de datos
Modelo jerarquico y modelo de red de base de datosModelo jerarquico y modelo de red de base de datos
Modelo jerarquico y modelo de red de base de datos
 
Fundamentos de base de datos 1a. unidad
Fundamentos de base de datos 1a. unidadFundamentos de base de datos 1a. unidad
Fundamentos de base de datos 1a. unidad
 
Seguridad En Base De Datos
Seguridad En Base De DatosSeguridad En Base De Datos
Seguridad En Base De Datos
 
Introducción a las bases de datos
Introducción a las bases de datosIntroducción a las bases de datos
Introducción a las bases de datos
 
Modelo E-R.pdf
Modelo E-R.pdfModelo E-R.pdf
Modelo E-R.pdf
 
NORMALIZACIÓN
NORMALIZACIÓN  NORMALIZACIÓN
NORMALIZACIÓN
 
Clase I Estructura de Datos
Clase I Estructura de Datos Clase I Estructura de Datos
Clase I Estructura de Datos
 
Redundancia de datos
Redundancia de datosRedundancia de datos
Redundancia de datos
 
Joins in SQL
Joins in SQLJoins in SQL
Joins in SQL
 
CLASE 3_ArquiteturaBD_UsuariosBD_IndependiciaLogFis_ModelosBD.pdf
CLASE 3_ArquiteturaBD_UsuariosBD_IndependiciaLogFis_ModelosBD.pdfCLASE 3_ArquiteturaBD_UsuariosBD_IndependiciaLogFis_ModelosBD.pdf
CLASE 3_ArquiteturaBD_UsuariosBD_IndependiciaLogFis_ModelosBD.pdf
 
Exposicion base datos
Exposicion  base datosExposicion  base datos
Exposicion base datos
 
Join
JoinJoin
Join
 
Dbms interview questions
Dbms interview questionsDbms interview questions
Dbms interview questions
 
Historia de la tecnologia de base de datos
Historia de la tecnologia de base de datosHistoria de la tecnologia de base de datos
Historia de la tecnologia de base de datos
 
Usuarios y administrador de bases de datos
Usuarios y administrador de bases de datosUsuarios y administrador de bases de datos
Usuarios y administrador de bases de datos
 
DBMS & RDBMS (PPT)
DBMS & RDBMS (PPT)DBMS & RDBMS (PPT)
DBMS & RDBMS (PPT)
 
Crear base de datos mysql command
Crear base de datos mysql commandCrear base de datos mysql command
Crear base de datos mysql command
 
Joins And Its Types
Joins And Its TypesJoins And Its Types
Joins And Its Types
 

Similar a Capitulo 8 soporte spring jdbc 0

Similar a Capitulo 8 soporte spring jdbc 0 (20)

Tema 5
Tema 5Tema 5
Tema 5
 
Curso mysql modificado
Curso mysql modificadoCurso mysql modificado
Curso mysql modificado
 
curso_mysql.pdf
curso_mysql.pdfcurso_mysql.pdf
curso_mysql.pdf
 
Manejo conexbd
Manejo conexbdManejo conexbd
Manejo conexbd
 
Manejo conexbd
Manejo conexbdManejo conexbd
Manejo conexbd
 
mini-taller de Base de Datos y PostgreSQL
mini-taller de Base de Datos y PostgreSQLmini-taller de Base de Datos y PostgreSQL
mini-taller de Base de Datos y PostgreSQL
 
Entrenamiento Power BI [GerdauCorsa-Sep2019]
Entrenamiento Power BI [GerdauCorsa-Sep2019]Entrenamiento Power BI [GerdauCorsa-Sep2019]
Entrenamiento Power BI [GerdauCorsa-Sep2019]
 
Java con base de datos
Java con base de datosJava con base de datos
Java con base de datos
 
FUNCIONES DEL DBA - TIPOS DE BASE DE DATOS
FUNCIONES DEL DBA - TIPOS DE BASE DE DATOSFUNCIONES DEL DBA - TIPOS DE BASE DE DATOS
FUNCIONES DEL DBA - TIPOS DE BASE DE DATOS
 
Java con Base de Datos
Java con Base de DatosJava con Base de Datos
Java con Base de Datos
 
Especialista Web J4
Especialista Web   J4Especialista Web   J4
Especialista Web J4
 
Modelado de datos
Modelado de datosModelado de datos
Modelado de datos
 
Base de Datos
Base de DatosBase de Datos
Base de Datos
 
Base de Datos - Daniela Monsalve
Base de Datos - Daniela MonsalveBase de Datos - Daniela Monsalve
Base de Datos - Daniela Monsalve
 
S M D B E Q U I P O11
S M D B  E Q U I P O11S M D B  E Q U I P O11
S M D B E Q U I P O11
 
BASE DE DATOS
BASE DE DATOSBASE DE DATOS
BASE DE DATOS
 
Manual jdbc
Manual jdbcManual jdbc
Manual jdbc
 
DiseñO De Base De Datos
DiseñO De Base De DatosDiseñO De Base De Datos
DiseñO De Base De Datos
 
Investigacion De Smdb
Investigacion De SmdbInvestigacion De Smdb
Investigacion De Smdb
 
JDBC
JDBCJDBC
JDBC
 

Capitulo 8 soporte spring jdbc 0

  • 1. Tabla de contenido Capitulo 8 : Soporte Spring JDBC .................................................................................................. 1 Ejemplo de Modelo de Datos para Código de Ejemplo ..................................................................... 2 Explorando la Infraestructura de JDBC .......................................................................................... 5 Infraestructura de Spring JDBC ................................................................................................... 10 Información 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 Parámetros 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 Configuración de Java ................................................................................................. 45 Proyecto Spring Data: JDBC Extensions ....................................................................................... 46 Consideraciones para Usar JDBC ................................................................................................. 46 Resumen .................................................................................................................................. 47
  • 2. Capitulo 8: Soporte Spring JDBC En los capítulos anteriores, hemos visto lo fácil que es construir una aplicación totalmente administrada por Spring. Ahora usted tiene una sólida comprensión de la configuración bean y de la Programación Orientada a Aspectos (AOP), en otras palabras, usted sabe cómo cablear la aplicación entera usando Spring. Sin embargo, falta una de las piezas del rompecabezas: ¿cómo conseguir los datos que maneja la aplicación? Además de utilidades desechables simples de línea de comandos, casi todas las aplicaciones necesitan conservar los datos en algún tipo de almacén de datos. El almacén de datos más usual y conveniente es una base de datos relacional. Las bases de datos relacionales de código abierto más destacadas son, quizás, MySQL (www.mysql.com) y PostgreSQL (www.postgresql.org). En términos de características RDBMS proporcionadas, ambas bases de datos son aproximadamente los mismos. MySQL es por lo general más ampliamente utilizado para el desarrollo de aplicaciones web, especialmente en la plataforma Linux. Por otro lado, PostgreSQL es más 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 más rápida y confiable, no puede permitirse perder la velocidad ofrecida y flexibilidad usando una capa de acceso a datos mal diseñada 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 código de acceso a datos afecta a toda la aplicación, no importa lo bien diseñada que este. En este capítulo, te mostramos cómo puedes usar Spring para simplificar la implementación del código de acceso a datos usando JDBC. Empezamos viendo la horrible cantidad de código 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 sorprendente- Spring te permite utilizar todo el poder de consultas SQL concertadas por el humano, mientras reduce al mínimo la cantidad de código de apoyo que usted necesita para poner en práctica. En concreto, hablaremos de lo siguiente:  Comparando código JDBC tradicional y el soporte de Spring JDBC: Exploramos cómo Spring simplifica el antiguo estilo del código JDBC manteniendo la misma funcionalidad. Usted también verá cómo Spring accede a la API JDBC de bajo nivel y cómo esta API de bajo nivel es mapeada a las clases prácticas, como JdbcTemplate.  Conectándose a la base de datos: Aunque no entremos en cada pequeño detalle del manejo de la conexión a la base de datos, le mostramos las diferencias fundamentales entre un sencillo Connection y un DataSource. Naturalmente, hablamos de cómo Spring maneja los DataSources y qué datasources usted puede utilizar en sus aplicaciones.  Recuperando y mapeando los datos a objetos Java: Te mostramos cómo recuperar los datos y luego mapear con eficacia los datos seleccionados a objetos Java. También usted aprende que Spring JDBC es una alternativa viable para herramientas de mapeo de objeto-relacional (ORM).  Insertando, actualizando y eliminando datos: Finalmente, discutimos cómo 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 código que usted ha escrito. 1
  • 3. ¿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 máquina física que ejecuta el administrador de procesos. Formalmente, una base de datos es una colección de datos, el software de base de datos (como Oracle, PostgreSQL, MySQL, etc.) es llamado el software de gestión de bases de datos o, más específicamente, un sistema de gestión de bases de datos relacionales (RDBMS), la instancia de un RDBMS es llamado motor de base de datos y, por último, la máquina 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 término base de datos desde el contexto en el que es usado. Es por eso que usamos este término para representar los cuatro significados que acabamos de describir. En los últimos años, debido al crecimiento explosivo de Internet y las tecnologías de computación en la nube, una gran cantidad de aplicaciones web para fines-específicos, han surgido, tales como las redes sociales, motores de búsqueda, mapas, videos, etc. Para atender los requerimientos específicos de acceso a datos de aquellas aplicaciones, también se han desarrollado muchas categorías 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 gráficas, bases de datos centradas en documentos, etcétera. Por lo tanto, la base de datos ahora es un término mucho más amplio. Sin embargo, una discusión 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 Código de Ejemplo Antes de proceder a la discusión, nos gustaría introducir un modelo de datos muy simple que se utilizará para los ejemplos de este capítulo, así como para los próximos capítulos cuando se hable de otras técnicas 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 información de contacto de una persona, y la otra tabla es CONTACT_TEL_DETAIL, que almacena los detalles telefónicos de un contacto. Cada contacto puede tener cero o más números de teléfono, en otras palabras, es una relación de uno-a-muchos entre CONTACT y CONTACT_TEL_DETAIL. La información de un contacto incluye su nombre, apellido y fecha de nacimiento, mientras que una parte de información telefónica detallada incluye el tipo de teléfono (Móvil, Casa, etc.) y el número de teléfono correspondiente. La Figura 8-1 muestra el diagrama entidad-relación (ER) de la base de datos. Figura 8-1. Ejemplo del Modelo de Datos para el Código de Ejemplo Como puede ver, ambas tablas tienen una columna ID que será asignada automáticamente por la base de datos durante la inserción. Para la tabla CONTACT_TEL_DETAIL, hay una relación de llave foránea 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). 2
  • 4. Nota: El modelo de datos fue creado usando un plugin de Eclipse llamado Clay Mark II. La versión sin licencia puede ser utilizada libremente para crear modelos de datos para bases de datos gratuitas y de código abierto como MySQL, PostgreSQL, HSQL, Derby, y etcétera. Usted no necesita el plug-in para ejecutar el código de ejemplo, ya que los scripts para crear las tablas se proporcionan con el código de ejemplo. Sin embargo, el archivo del diagrama del modelo (situado en ch8/data-model/prospring3-ch8-datamodel.clay) se incluyó en el código de ejemplo, y si usted está interesado, puede instalar el plug-in y ver el diagrama (por favor consulte www.azzurri.co.jp para más detalles). El Listado 8-1 muestra el script para la creación 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', '1980- 07-30'); insert into contact (first_name, last_name, birth_date) values ('Scott', 'Tiger', '1990- 11-02'); insert into contact (first_name, last_name, birth_date) values ('John', 'Smith', '1964-02- 28'); insert into contact_tel_detail (contact_id, tel_type, tel_number) values (1, 'Móvil', '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'); 3
  • 5. En secciones posteriores de este capítulo, usted verá ejemplos para recuperar los datos de la base de datos a través 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 Long id; private String firstName; private String lastName; private Date birthDate; private 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 Long id; private Long contactId; private String telType; private String telNumber; // métodos 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 información del contacto. El listado 8-5 muestra la interfaz ContactDao. Listado 8-5. La Interfaze ContactDao package com.apress.prospring3.ch8.dao; 4
  • 6. import java.util.List; import com.apress.prospring3.ch8.domain.Contact; public interface ContactDao { public List<Contact> findAll(); public List<Contact> findByFirstName(String firstName); public void insert(Contact contact); public void update(Contact contact); public void delete(Long contactId); } En la interfaz anterior, se definen dos métodos de búsqueda y los métodos insert, update, y delete, respectivamente. Que corresponden con los términos 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 módulo 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 código fuente para el proyecto del Capítulo 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, después 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 estándar para que las aplicaciones Java puedan acceder a los datos almacenados en una base de datos. El núcleo de la infraestructura JDBC es un controlador que es específico para cada base de datos, es este controlador el que permite que el código 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 métodos estáticos para establecer conexiones con la base de datos. El método getConnection() de DriverManager devuelve una implementación 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 código administre las conexiones a la base de datos. Una conexión 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 5
  • 7. conexión. Además, el número de conexiones simultáneas por lo general es limitada, y un número excesivo de conexiones abiertas ralentiza la base de datos. Le mostraremos cómo 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 través 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 términos de rendimiento) de crear una conexión para cada declaración. Esto en gran medida reduce el rendimiento de Java y añade tensión adicional a la base de datos porque una conexión tiene que ser establecida por cada consulta. Sin embargo, si mantenemos una conexión abierta, podríamos traer el servidor de base de datos a una parada. El Listado 8-7 muestra el código necesario para manejar una conexión JDBC, usando MySQL como un ejemplo. Listado 8-7. Manejando una Conexión 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 código no es muy completo, pero te da una idea de los pasos que necesitas para manejar una conexión JDBC. Este código no considera incluso tratar con pool de conexiones, que es una técnica común para gestionar conexiones a bases de datos con más eficacia. No hablamos sobre pool de conexiones en este punto (pool de conexiones se discute en la sección "Conexiones a Base de Datos y DataSources" más adelante en este capítulo), en cambio, en el Listado 8-8, mostramos una implementación de los métodos findAll(), insert() y delete() de la interfaz ContactDao usando JDBC puro. 6
  • 8. Listado 8-8. Implementación de JDBC DAO Puro package com.apress.prospring3.ch8.dao.plain; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import 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")); 7
  • 9. 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); } } } 8
  • 10. El Listado 8-9 muestra una prueba del programa principal con la anterior implementación DAO en acción. Listado 8-9. Probando la Implementación 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 después de crear el contacto nuevo:"); listAllContacts(); System.out.println(); // Eliminar el contacto nuevo recién creado System.out.println("Eliminando el contacto recién creado"); contactDao.delete(contact.getId()); System.out.println( "Listando los datos de contact después de eliminar el contacto nuevo:"); listAllContacts(); } private static void listAllContacts() { List<Contact> contacts = contactDao.findAll(); for (Contact contact : contacts) { System.out.println(contact); } 9
  • 11. } } 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 Artifact ID Version Description mysql mysql-connector-java 5.1.18 Librería 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 contraseña establecida a prospring3, debería ser capaz de acceder al esquema de la base de datos, y usted debería ejecutar los scripts schema.sql y test-data.sql contra la base de datos para crear las tablas y cargar los datos iniciales): Listando los datos iniciales de contact: 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 Insertar un nuevo contacto Listando los datos de contact después de crear 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 Contacto - Id: 4, Nombre: Jacky, Apellido: Chan, Fecha de Nacimiento: 2001-11-01 Eliminando el contacto recién creado Listando los datos de contact después 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 líneas muestra los datos iniciales. El segundo bloque de líneas muestra que el nuevo registro fue añadido. El bloque final de las líneas muestra que el contacto recién creado se ha eliminado. Como usted puede ver en el Listado 8-8, una gran cantidad de código debe ser trasladado a una clase de ayuda o-peor aún-duplicado en cada clase DAO. Esta es la principal desventaja de JDBC desde el punto de vista del programador de la aplicación-que, simplemente usted no tiene tiempo para programar el código repetitivo en todas las clases DAO. En su lugar, usted desea concentrarse en escribir el código que realmente hace lo que usted necesita, hacer la clase DAO: seleccionar, actualizar y borrar los datos. Usted necesita escribir más código de ayuda, necesita verificar más las excepciones a manejar, y puede que usted presente más errores en su código. Es aquí donde un framework DAO y Spring entran. Un framework elimina el código que realmente no realiza ninguna lógica personalizada y le permite olvidarse de todos los tareas que debe realizar. Además, el amplio soporte de Spring JDBC hace su vida mucho más fácil. Infraestructura de Spring JDBC El código del cual hablamos en la primera parte del capítulo no es muy complejo, pero es molesto para escribir, y porque hay mucho de esto para escribir, la probabilidad de errores de codificación es 10
  • 12. bastante alta. Es tiempo de echar un vistazo de cómo Spring hace las cosas más fáciles y más elegantes. Información 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 Descripción org.springframework.jdbc.core Contiene las bases de las clases JDBC en Spring. Este incluye el núcleo de la clase JDBC, JdbcTemplate, que simplifica las operaciones de programación a la base de datos con JDBC. Algunos sub paquetes proporcionan soporte de acceso a datos JDBC con propósitos más específicos (por ejemplo, una clase JdbcTemplate que soporta parámetros con nombre) y soporte de clases relacionadas también. org.springframework.jdbc.datasource Contiene las clases de ayuda y de las implementaciones DataSource que usted puede utilizar para ejecutar código JDBC fuera de un contenedor JEE. Algunos sub paquetes proporcionan soporte a bases de datos embebidas, inicialización de base de datos, y varios mecanismos de búsqueda de datasource. org.springframework.jdbc.object 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. org.springframework.jdbc.support La clase más importante de este paquete es el soporte de traducción de SQLException. Esto permite que Spring reconozca los códigos de error utilizados por la base de datos y mapearlos a las excepciones de más alto nivel. org.springframework.jdbc.config Contiene clases que soportan la configuración 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>). Vamos a empezar la discusión del soporte de Spring JDBC mirando la funcionalidad del nivel más bajo. Lo primero que usted tiene que hacer antes de siquiera pensar cómo ejecutar las consultas SQL, es establecer una conexión con la base de datos. 11
  • 13. Conexiones a Base de Datos y DataSources Usted puede usar Spring para manejar la conexión 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 implementación más sencilla de un DataSource. Al observar el nombre de la clase, usted puede adivinar que sencillamente llama a DriverManager para obtener una conexión. El hecho de que DriverManagerDataSource no soporte el pool de conexiones a bases de datos hace que esta clase sea inadecuada para algo más que pruebas. La configuración de DriverManagerDataSource es bastante sencilla, como usted puede ver en el Listado 8-10, sólo tiene que proporcionar el nombre de la clase del controlador, una URL de conexión, un nombre de usuario y una contraseña (datasource- drivermanager.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 información de la conexión a la base de datos normalmente se almacena en un archivo de propiedades para un fácil mantenimiento y sustitución 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 información de la conexión. 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 aún más el 12
  • 14. rendimiento de la aplicación. Usted podría utilizar un DataSource en el código JDBC puro y obtener los mismos beneficios del pooling, sin embargo, en la mayoría de los casos, usted todavía perdería un lugar central para configurar el datasource. Spring, por el contrario, le permite declarar un bean dataSource y establecer las propiedades de conexión en los archivos de definición de ApplicationContext (vea el Listado 8-12, y el nombre del archivo es datasource-dbcp.xml). Nota: Además de Apache Commons BasicDataSource, otras librerías populares de pool de conexiones de base de datos de código 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 más 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 aplicación que usted está desarrollando se va a ejecutar en un contenedor JEE, usted puede tomar ventaja del pool de conexión manejado por el contenedor. Para usar un dataSource basado en JNDI, usted necesita cambiar la declaración 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
  • 15. En el ejemplo anterior, usamos JndiObjectFactoryBean de Spring para obtener la búsqueda del datasource JNDI. A partir de la versión 2.5, Spring proporciona el espacio de nombres jee, lo que simplifica aún más la configuración. El Listado 8-14 muestra la misma configuración 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 (resource- ref) en el archivo descriptor de la aplicación (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 posición, que usted tiene que cambiar dependiendo de cómo su módulo esté empaquetado. Por ejemplo, se convierte en <web-app> en el descriptor de despliegue web (WEB-INF/web.xml) si la aplicación es un módulo web. Lo más probable es que usted tendrá que configurar el resource-ref en un archivo descriptor de la aplicación del servidor-específico también. 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 implementación real o la ubicación del datasource del resto del código de la aplicación. En otras palabras, sus clases DAO no saben y no tienen que saber dónde señala el DataSource. La administración de la conexión también 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 versión 3.0, Spring también ofrece el soporte de base de datos embebidas, que inicia automáticamente una base de datos embebida y la expone como un DataSource para la aplicación. El Listado 8-16 muestra la configuración de una base de datos embebida (app-context-xml.xml). 14
  • 16. 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>. Después, usamos <jdbc:embedded-database> para declarar la base de datos embebida y asignarla con un ID de dataSource. Dentro de la etiqueta, también 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 Definición de Datos (DDL) siempre debería de aparecer en primer lugar, seguido por el archivo con el Lenguaje de Manipulación de Datos (DML). Para el atributo type, especificamos el tipo de base de datos embebida a usar. A partir de la versión 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 capítulo, usaremos la base de datos embebida para ejecutar el código de ejemplo, por lo que su máquina 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 vacía y una implementación sencilla de la misma. Vamos a añadir más características a medida que avanzamos y explicamos lo que lo hagamos con las clases de Spring JDBC. El listado 8-17 muestra la interfaz ContactDao vacía. Listado 8-17. Interfaz e Implementación de ContactDao public interface ContactDao {} public class JdbcContactDao implements ContactDao {} 15
  • 17. Para la implementación sencilla, primero añadiremos una propiedad dataSource. La razón por la que deseamos agregar la propiedad dataSource a la implementación de la clase en lugar de la interfaz debería ser bastante obvia: la interfaz no necesita saber cómo se van a recuperar y actualizar los datos. Añadiendo los métodos 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 práctica muy buena de diseño. 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 implementación 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 Aplicación de Spring con los Beans dataSource y contactDao <!-- Declaración 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 añadir 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 Version Description com.h2database h2 1.3.160 Librería Java para la base de datos H2 16
  • 18. Spring ahora crea el bean contactDao para instanciar la clase JdbcContactDao con la propiedad dataSource establecida en el bean dataSource. Es una buena práctica asegurarse de que se han establecido todas las propiedades requeridas de un bean. La forma más sencilla de hacerlo es implementar la interfaz InitializingBean y proporcionar una implementación para el método 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 información sobre la inicialización de bean, consulte el Capítulo 5. Listado 8-20. Implementación 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 código que hemos visto hasta ahora utiliza Spring para manejar el datasource y presenta la interfaz ContactDao y su implementación JDBC. También se establece la propiedad dataSource en la clase JdbcContactDao en el archivo ApplicationContext de Spring. Ahora ampliamos el código añadiendo las operaciones DAO reales a la interfaz y a la implementación. Manejo de Excepciones Debido a que los defensores de Spring usan excepciones en tiempo de ejecución en lugar de las excepciones comprobadas, usted necesita un mecanismo para traducir la SQLException comprobada en una excepción en tiempo de ejecución de Spring JDBC. Dado que las excepciones de Spring SQL son excepciones en tiempo de ejecución, ellas pueden ser mucho más detalladas que las excepciones comprobadas. (Por definición, esta no es una característica de las excepciones en tiempo de ejecución, pero es muy incómodo tener que declarar una larga lista de excepciones comprobadas en la cláusula throws, por lo que las excepciones comprobadas tienden a ser mucho más genéricas que sus equivalentes en tiempo de ejecución.) Spring proporciona una implementación predeterminada de la interfaz SQLExceptionTranslator, que se encarga de traducir los códigos SQL genéricos de error en las excepciones de Spring JDBC. En la mayor parte los casos, esta implementación es más que suficiente, pero podemos extender la implementación predeterminada de Spring y establecer nuestra nueva 17
  • 19. implementación SQLExceptionTranslator para ser usada en JdbcTemplate, como muestra el Listado 8-21. A la vez, tenemos que añadir 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 Version Description org.springframework spring-jdbc 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 código de ejemplo para este propósito. 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 // búsqueda de la traducción 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
  • 20. y la traducción personalizada de excepciones ocurrirá cuando el código de error sea -12345. Para otros errores, Spring retrocederá a su mecanismo por defecto para la traducción 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 leído acerca de la clase JdbcTemplate, vamos a hablar de ello con más detalle. La Clase JdbcTemplate Esta clase representa el núcleo del soporte de Spring JDBC. Esta puede ejecutar todo tipo de sentencias SQL. Desde el punto de vista más simplista, usted puede clasificar las sentencias de definición y manipulación de datos. La cobertura de sentencias de definición de datos crea varios objetos de base de datos (tablas, vistas, procedimientos almacenados, etc.). Las sentencias de manipulación 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 ningún 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 sección, iremos a través de varios casos de uso común para la programación JDBC en Spring con la clase JdbcTemplate. Inicializando JdbcTemplate en una Clase DAO Antes de discutir cómo usar JdbcTemplate, echemos un vistazo cómo preparar JdbcTemplate para su uso en la clase DAO. Es muy sencillo, la mayor parte del tiempo usted sólo necesita construir la clase pasandole el objeto datasource (que debería ser inyectado por Spring en la clase DAO). El Listado 8-23 muestra el fragmento de código 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 práctica general consiste en inicializar el JdbcTemplate dentro del método setDatasource para que una vez que el datasource sea inyectado por Spring, el JdbcTemplate también sea inicializado y esté listo para su uso. Una vez configurado, el JdbcTemplate es thread safe. Eso significa que usted también puede optar por iniciar una única instancia de JdbcTemplate en la configuración XML de Spring y tener esta inyección en todos los beans DAO. Nota: En el módulo 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á automáticamente. 19
  • 21. 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. Añadamos primero el método en la interfaz ContactDao: public String findFirstNameById(Long id); Usando JdbcTemplate, podemos recuperar el valor con facilidad. El Listado 8-24 muestra la implementación del método findFirstNameById() en la clase JdbcContactDao. Para los otros métodos, se crearon implementaciones vacías. 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 parámetros que se pasan al SQL para enlazar el parámetro en forma de un objeto array. El último argumento es el tipo a ser devuelto, que en este caso es un String. Además de Object, usted también 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
  • 22. 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 ejecución del programa produce el siguiente resultado: El Primer nombre para el contacto de id 1 es: Clarence Usando Parámetros con Nombres con NamedParameterJdbcTemplate En el ejemplo anterior, estamos usando el marcador de posición normal (el carácter ?) como parámetros de consulta. Como también usted puede ver, tenemos que pasar los parámetros como un array Object. Cuando se utiliza un marcador de posición normal, el orden es muy importante, y el orden en que usted pone los parámetros en el array debería ser el mismo orden de los parámetros en la consulta. Algunos desarrolladores (como yo) prefieren utilizar parámetros con nombre para asegurar que el parámetro está vinculado exactamente como quería. En Spring, una variante de JdbcTemplate, llamada NamedParameterJdbcTemplate (dentro del paquete org.springframework.jdbc.core.namedparam), proporciona soporte para esto. Veamos cómo funciona. Por ejemplo, en esta ocasión queremos añadir otro método para encontrar el apellido por ID, así que vamos a agregar el método a la interfaz ContactDao: public String findLastNameById(Long id); La inicialización de NamedParameterJdbcTemplate es la misma que JdbcTemplate, por lo que sólo tenemos que declarar una variable con el tipo NamedParameterJdbcTemplate y añadir la siguiente línea en la clase DAO del método setDataSource(): this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); Ahora veamos cómo implementar el método. El Listado 8-26 muestra la implementación. 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 métodos omitidos 21
  • 23. 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 posición ?, fue usado el parámetro con nombre (prefijado por dos puntos). En segundo lugar, fue inicializado un SqlParameterSource, que es un Map-basado en la fuente de los parámetros SQL con la llave como el nombre del parámetro llamado y el valor como el valor del parámetro. En lugar de SqlParameterSource, usted también puede simplemente construir un map para el almacenamiento de parámetros con nombre. El Listado 8-27 es una variante del método 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 métodos 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 código, sólo tiene que añadir el método 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 más filas y luego transformar cada línea 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 acción implementando el método findAll() de la interfaz ContactDao utilizando la interfaz de RowMapper<T>. El Listado 8-28 muestra la implementación del método 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
  • 24. public class JdbcContactDao implements ContactDao, InitializingBean { // Otros métodos 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 estática interna llamada ContactMapper que implementa la interfaz RowMapper<T>. La clase debe proporcionar la implementación mapRow(), que transforma los valores en un registro específico del conjunto de resultados en el objeto de dominio que usted desee. Debido a que es una clase interna estática le permite compartir el RowMapper<T> entre los múltiples métodos de búsqueda. Después, el método findAll() sólo tiene que invocar el método query y pasarla en la cadena de consulta y el row mapper. En el caso de que la consulta requiera parámetros, el método query() proporciona una sobrecarga que acepta los parámetros de la consulta. Añadimos el siguiente fragmento de código (Listado 8-29) en el programa de pruebas (la clase JdbcContactDaoSample). Listado 8-29. Fragmento de Código 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
  • 25. Recuperando Objetos de Dominio Anidados con ResultSetExtractor Prosigamos con un ejemplo un poco más 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 más complicada, tenemos que usar la interfaz ResultSetExtractor. Para demostrar su uso, añadiremos un método más, findAllWithDetail(), en la interfaz ContactDao. El método debería poblar la lista de contactos con sus detalles telefónicos. public List<Contact> findAllWithDetail(); El Listado 8-30 muestra la implementación del método 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
  • 26. 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 código es muy parecido al ejemplo RowMapper, pero esta vez declaramos una clase interna que implementa ResultSetExtractor. Luego implementamos el método extractData() para en consecuencia transformar el conjunto de resultados en una lista de objetos Contact. Para el método findAllWithDetail(), la consulta utiliza un left join para unir las dos tablas y que también sean recuperados los contactos sin teléfonos. El resultado es un producto Cartesiano de las dos tablas. Por último, usamos el método JdbcTemplate.query(), pasándole la cadena de consulta y el resultsetExtractor. Agreguemos el siguiente fragmento de código (Listado 8-31) en el programa de pruebas (la clase JdbcContactDaoSample). Listado 8-31. Fragmento de Código 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 Tél - Id: 2, Contacto id: 1, Tipo: Casa, Número: 1234567890 ---Contacto Detalles Tél - Id: 1, Contacto id: 1, Tipo: Móvil, Número: 1234567890 Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02 ---Contacto Detalles Tél - Id: 3, Contacto id: 2, Tipo: Casa, Número: 1234567890 Contacto - Id: 3, Nombre: John, Apellido: Smith, Fecha de Nacimiento: 1964-02-2 Usted puede ver que los contactos y sus detalles telefónicos fueron listados como corresponde. Los datos están basados en los scripts que cargan los datos del Listado 8-2. Hasta ahora, usted ha visto cómo usar JdbcTemplate para realizar algunas operaciones de consulta común. JdbcTemplate (y también la clase NamedParameterJdbcTemplate) también ofrece una serie de sobrecarga de métodos update() que soportan operaciones de actualización de datos, 25
  • 27. incluyendo insert, update, delete, etcétera. Sin embargo, el método update() es bastante auto- explicativo, por lo que decidimos no cubrirlo en esta sección. Por otro lado, como se verá en secciones posteriores, usaremos la clase SqlUpdate proporcionada por Spring para realizar operaciones de actualización de datos. Clases Spring que Modelan Operaciones JDBC En la sección anterior, vimos cómo JdbcTemplate y las clases de utilidad relacionadas con el mapeo de datos han simplificado enormemente el modelo de programación en el desarrollo de la lógica de acceso a datos con JDBC. Construir sobre JdbcTemplate, Spring también proporciona un número de clases útiles que modelan las operaciones JDBC de datos y permiten a los desarrolladores mantener la consulta y transformar la lógica desde el conjunto de resultados a objetos de dominio de una manera más 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 método mapRow() en una sola clase. SqlUpdate: La clase SqlUpdate le permite ajustar cualquier sentencia de actualización SQL en el mismo. También proporciona una gran cantidad de funciones útiles para enlazar parámetros SQL, recuperar la llave de un RDBMS-generada después de que un nuevo registro es insertado, etcétera. BatchSqlUpdate: Como su nombre lo indica, la clase le permite realizar operaciones de actualización por lotes. Por ejemplo, usted puede recorrer a través de un objeto List de Java y hacer que BatchSqlUpdate encole los registros y enviar las sentencias de actualización para usted en un lote. Usted puede configurar el tamaño del lote y nivelar la operación 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. También existe otra clase, StoredProcedure, que le ayuda a invocar los procedimientos almacenados. Nota: En secciones anteriores, todo el código de ejemplo usa la configuración de tipo XML. Por lo tanto, en las siguientes secciones, usaremos las anotaciones de Spring para la configuración del ApplicationContext. En caso de que decida adoptar la configuración XML en la aplicación, creemos que usted tendrá una buena idea de cómo hacerlo. Configurando JDBC DAO para Usar Anotaciones Primero vamos a mirar cómo configurar la implementación de la clase DAO usando anotaciones en primer lugar. El Listado 8-32 muestra la interfaz de la clase ContactDao con un listado más 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
  • 28. 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 declaración inicial y la inyección de la propiedad datasource usando la anotación 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 código de acceso a datos, @Repository también instruye a Spring a realizar excepciones SQL específicas de la bases de datos a la jerarquía DataAccessException más amigables con la aplicación en Spring. 27
  • 29. También 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 métodos de la interfaz ContactDao uno por uno. Mientras tanto, primero vamos a crear una implementación vacía de todos los métodos de la clase JdbcContactDao. Una manera sencilla de hacerlo es usando STS para generar implementaciones vacías por nosotros. En STS, en la clase haga clic-derecho y seleccione Source → Override/Implement Methods (véase la Figura 8-2). Figura 8-2. Implementando métodos en STS En la siguiente pantalla, todos los métodos en la interfaz ContactDao ya deberían estar marcados, como se muestra en la Figura 8.3. Simplemente haga clic en OK y, se creará automáticamente una implementación vacía de todos los métodos seleccionados. 28
  • 30. Figura 8-3. Seleccionando los métodos a implementar en STS Después, usted verá que se han generado implementaciones vacías de los métodos. A continuación podemos proceder a implementar los métodos progresivamente. El Listado 8-34 muestra la configuración XML para Spring usando anotaciones (app-context- annotation.xml). Listado 8-34. Configuración 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
  • 31. <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 configuración, acabamos de declarar la base de datos embebida usando H2 y usamos <context:component-scan> para descubrir automáticamente el bean de Spring. Teniendo la infraestructura en su lugar, ahora podemos proceder a la ejecución de las operaciones JDBC. Consultando Datos Usando MappingSqlQuery<T> Spring proporciona la clase MappingSqlQuery<T> para modelar operaciones de consulta. Básicamente, construimos una clase MappingSqlQuery<T> usando el datasource y la cadena de consulta. Por otro lado, implementamos el método mapRow() para mapear cada registro del conjunto de resultados en el objeto de dominio correspondiente. Primero vamos a implementar el método findAll(). Empezamos creando la clase SelectAllContacts (que representa la operación 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
  • 32. 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 método super() para construir la clase, usando tanto el DataSource como la sentencia SQL. Además, el método 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 método findAll() en la clase JdbcContactDao. El Listado 8-36 muestra la clase. Listado 8-36. Implementando el Método 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 vacíos } 31
  • 33. En el Listado 8-36, en el método setDataSource(), después de la inyección del DataSource, se construye una instancia de la clase SelectAllContacts. En el método findAll(), simplemente invocamos el método 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 lógica. 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á también la consulta que fue enviada por Spring (ver Figura 8-4). Figura 8-4. Salida en STS con el nivel log DEBUG encendido 32
  • 34. Prosigamos a implementar el método findByFirstName(), que tiene un parámetro con nombre. Al igual que el ejemplo anterior, creamos la clase SelectContactByFirstName para la operación, 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 parámetro con nombre llamado first_name. En el método constructor, se llama al método declareParameter() (que indirectamente es heredado de la clase abstracta org.springframework.jdbc.object.RdbmsOperation). Prosigamos a implementar el método findByFirstName() en la clase JdbcContactDao. El Listado 8-39 muestra el fragmento de código. Listado 8-39. Implementando el Método 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
  • 35. } 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 código } En el Listado 8-39, después de la inyección del datasource, se construye una instancia de SelectContactByFirstName (tenga en cuenta las líneas en negrita). Después, en el método findByFirstName(), se construye un HashMap con los parámetros con nombre y valores. Por último, se llama al método executeByNamedParam() (heredado indirectamente de la clase abstracta SqlQuery<T>). Para probar el método, agregue el siguiente fragmento de código del Listado 8-40 en la clase AnnotationJdbcDaoSample. Listado 8-40. Probando el Método findByFirstName() // Encontrar y listar todos los contactos contacts = contactDao.findAllWithDetail(); listContacts(contacts); Al ejecutar el programa se producirá la siguiente salida desde el método findByFirstName(): Contacto - Id: 1, Nombre: Clarence, Apellido: Ho, Fecha de Nacimiento: 1980-07-30 Un punto a destacar aquí es que MappingSqlQuery<T> sólo es adecuado para mapear una sola fila a un objeto de dominio. Para un objeto anidado, usted todavía tiene que usar JdbcTemplate con ResultSetExtractor como en el ejemplo del método findAllWithDetail() presentado en la sección 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 actualización. 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
  • 36. 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 debería ser familiar para usted ahora. Se construye una instancia de la clase SqlUpdate con la consulta, y se declaran también los parámetros con nombre. El Listado 8-42 muestra la implementación del método 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 códigos } En el Listado 8-42, después de la inyección del datasource, se construye una instancia de UpdateContact (tenga en cuenta las líneas en negrita). En el método update(), se construye un HashMap de parámetros con nombre pasados por el objeto Contact, y luego es llamado el método updateByNamedParam() para actualizar el registro del contacto. Para comprobar el funcionamiento, agregue el siguiente fragmento de código del Listado 8-43 en la clase AnnotationJdbcDaoSample. Listado 8-43. Probando el Método update() // Actualizar el contacto 35
  • 37. 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 método update(). Al ejecutar el programa se producirá la siguiente salida desde el último método 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, también usamos la clase SqlUpdate. Sin embargo, un punto interesante aquí es acerca de la llave primaria, la columna id, que sólo estará disponible sólo después de que la sentencia de inserción 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 operación de inserción. 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 inserción con la consulta. Sin embargo, en nuestro caso, ¿cómo podemos recuperar la llave generada por un RDBMS después de que el registro es insertado? En antiguas versiones de JDBC, el método 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 versión 3.0 de JDBC, fue añadida una nueva característica que permite recuperar una llave generada por un RDBMS de una manera unificada. El Listado 8-37 muestra la implementación del método insert(), que también recupera la llave generada para el registro del contacto insertado. Esto funcionará en la mayor parte de las bases de datos (si no todas), sólo asegúrese 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 operación de inserción, 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