Your SlideShare is downloading. ×
Capitulo 8   soporte spring jdbc 0
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Capitulo 8 soporte spring jdbc 0

878
views

Published on


0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
878
On Slideshare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
35
Comments
0
Likes
0
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Tabla de contenidoCapitulo 8 : Soporte Spring JDBC .................................................................................................. 1Ejemplo de Modelo de Datos para Código de Ejemplo ..................................................................... 2Explorando la Infraestructura de JDBC .......................................................................................... 5Infraestructura 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 ...................................................................................... 14Usando DataSources en Clases DAO ............................................................................................ 15Manejo de Excepciones .............................................................................................................. 17La 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 ............................................ 24Clases 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.............................................................. 42Usando la Configuración de Java ................................................................................................. 45Proyecto Spring Data: JDBC Extensions ....................................................................................... 46Consideraciones para Usar JDBC ................................................................................................. 46Resumen .................................................................................................................................. 47
  • 2. Capitulo 8: Soporte Spring JDBCEn los capítulos anteriores, hemos visto lo fácil que es construir una aplicación totalmenteadministrada por Spring. Ahora usted tiene una sólida comprensión de la configuración bean y de laProgramación Orientada a Aspectos (AOP), en otras palabras, usted sabe cómo cablear la aplicaciónentera usando Spring. Sin embargo, falta una de las piezas del rompecabezas: ¿cómo conseguir losdatos que maneja la aplicación? Además de utilidades desechables simples de línea de comandos, casi todas las aplicacionesnecesitan conservar los datos en algún tipo de almacén de datos. El almacén de datos más usual yconveniente 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 RDBMSproporcionadas, ambas bases de datos son aproximadamente los mismos. MySQL es por lo generalmás ampliamente utilizado para el desarrollo de aplicaciones web, especialmente en la plataformaLinux. Por otro lado, PostgreSQL es más amigable para los desarrolladores de Oracle, debido a que sulenguaje 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 lavelocidad 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 cuellosde botella innecesarios en el código de acceso a datos afecta a toda la aplicación, no importa lo biendiseñada que este. En este capítulo, te mostramos cómo puedes usar Spring para simplificar la implementacióndel código de acceso a datos usando JDBC. Empezamos viendo la horrible cantidad de código quenormalmente se necesita para escribir sin Spring y luego compararla con una clase de acceso a datosimplementada 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 reduceal 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 desoftware que maneja los datos, una instancia de un proceso de este software, o incluso la máquinafísica que ejecuta el administrador de procesos. Formalmente, una base de datos es una colección dedatos, el software de base de datos (como Oracle, PostgreSQL, MySQL, etc.) es llamado el softwarede gestión de bases de datos o, más específicamente, un sistema de gestión de bases de datosrelacionales (RDBMS), la instancia de un RDBMS es llamado motor de base de datos y, por último, lamáquina que ejecuta el motor de base de datos se llama servidor de base de datos. Sin embargo, lamayor parte de los desarrolladores comprenden inmediatamente el significado del término base dedatos desde el contexto en el que es usado. Es por eso que usamos este término para representar loscuatro significados que acabamos de describir.En los últimos años, debido al crecimiento explosivo de Internet y las tecnologías de computación enla nube, una gran cantidad de aplicaciones web para fines-específicos, han surgido, tales como lasredes sociales, motores de búsqueda, mapas, videos, etc. Para atender los requerimientos específicosde acceso a datos de aquellas aplicaciones, también se han desarrollado muchas categorías diferentesde "bases de datos". Algunos ejemplos incluyen bases de datos de par clave-valor (generalmente seconoce como bases de datos NoSQL), bases de datos gráficas, bases de datos centradas endocumentos, etcétera. Por lo tanto, la base de datos ahora es un término mucho más amplio. Sinembargo, 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 EjemploAntes de proceder a la discusión, nos gustaría introducir un modelo de datos muy simple que seutilizará para los ejemplos de este capítulo, así como para los próximos capítulos cuando se hable deotras técnicas de acceso a datos (ampliaremos el modelo en consecuencias para satisfacer lasnecesidades de cada tema a medida que avanzamos). El modelo es una base de datos CONTACT muy sencilla. Hay dos tablas. La primera es la tablaCONTACT, que almacena la información de contacto de una persona, y la otra tabla esCONTACT_TEL_DETAIL, que almacena los detalles telefónicos de un contacto. Cada contacto puedetener cero o más números de teléfono, en otras palabras, es una relación de uno-a-muchos entreCONTACT y CONTACT_TEL_DETAIL. La información de un contacto incluye su nombre, apellido y fechade 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 diagramaentidad-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 labase de datos durante la inserción. Para la tabla CONTACT_TEL_DETAIL, hay una relación de llaveforánea con la tabla CONTACT, que está vinculada por la columna CONTACT_ID con la llave primaria dela 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 conMySQL).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 yCONTACT_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 labase 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 Contactpackage 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 ContactTelDetailpackage 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 losservicios de acceso a datos para la información del contacto. El listado 8-5 muestra la interfazContactDao.Listado 8-5. La Interfaze ContactDaopackage 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, ydelete, 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 activarel nivel log a DEBUG para todas las clases. En el nivel DEBUG, el módulo de Spring JDBC da salida atodas las sentencias SQL subyacentes que se dispararon a la base de datos para que usted sepaexactamente lo que está sucediendo, es especialmente útil para solucionar errores de sintaxis SQL. Ellistado 8-6 muestra el archivo log4j.properties (que reside dentro de /src/main/resources conlos archivos de código fuente para el proyecto del Capítulo 8) con el nivel DEBUG activado.Listado 8-6. El Archivo log4j.propertieslog4j.rootCategory=DEBUG, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.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 losdatos almacenados en una base de datos. El núcleo de la infraestructura JDBC es un controlador quees específico para cada base de datos, es este controlador el que permite que el código Java tengaacceso a la base de datos. Una vez que un controlador es cargado, se registra así mismo con una clasejava.sql.DriverManager. Esta clase maneja una lista de controladores y proporciona métodosestáticos para establecer conexiones con la base de datos. El método getConnection() deDriverManager 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 complejidadvienen dificultades en el desarrollo. El primer nivel de complejidad radica en hacer que su códigoadministre las conexiones a la base de datos. Una conexión es un recurso escaso y es muy costoso deestablecer. 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úmeroexcesivo de conexiones abiertas ralentiza la base de datos. Le mostraremos cómo Spring ayuda a gestionar esta complejidad, pero antes de que podamosseguir adelante, tenemos que mostrarle como seleccionar, eliminar y actualizar los datos con JDBCpuro. Vamos a crear una forma sencilla de implementar la interfaz ContactDao para interactuar conla base de datos a través de JDBC puro. Teniendo en cuenta lo que ya sabemos acerca de lasconexiones a base de datos, tomamos el enfoque prudente y costoso (en términos de rendimiento) decrear una conexión para cada declaración. Esto en gran medida reduce el rendimiento de Java yañade tensión adicional a la base de datos porque una conexión tiene que ser establecida por cadaconsulta. Sin embargo, si mantenemos una conexión abierta, podríamos traer el servidor de base dedatos 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 JDBCpublic 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 manejaruna conexión JDBC. Este código no considera incluso tratar con pool de conexiones, que es unatécnica común para gestionar conexiones a bases de datos con más eficacia. No hablamos sobre poolde conexiones en este punto (pool de conexiones se discute en la sección "Conexiones a Base deDatos y DataSources" más adelante en este capítulo), en cambio, en el Listado 8-8, mostramos unaimplementación de los métodos findAll(), insert() y delete() de la interfaz ContactDao usandoJDBC puro. 6
  • 8. Listado 8-8. Implementación de JDBC DAO Puropackage 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 DAOen acción.Listado 8-9. Probando la Implementación de JDBC Puropackage 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 suproyecto, como se muestra en la Tabla 8-1.Tabla 8-1. Dependencia para MysqlGroupID Artifact ID Version Descriptionmysql mysql-connector-java 5.1.18 Librería del Controlador MySQL JavaAl ejecutar el programa en el Listado 8-9 dará el siguiente resultado (suponiendo que usted tiene unabase de datos MySQL instalada localmente, con una base de datos llamada prospring3_ch8 con unnombre de usuario y contraseña establecida a prospring3, debería ser capaz de acceder al esquemade la base de datos, y usted debería ejecutar los scripts schema.sql y test-data.sql contra la basede 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-30Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02Contacto - Id: 3, Nombre: John, Apellido: Smith, Fecha de Nacimiento: 1964-02-28Insertar un nuevo contactoListando los datos de contact después de crear el contacto nuevo:Contacto - Id: 1, Nombre: Clarence, Apellido: Ho, Fecha de Nacimiento: 1980-07-30Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02Contacto - Id: 3, Nombre: John, Apellido: Smith, Fecha de Nacimiento: 1964-02-28Contacto - Id: 4, Nombre: Jacky, Apellido: Chan, Fecha de Nacimiento: 2001-11-01Eliminando el contacto recién creadoListando los datos de contact después de eliminar el contacto nuevo:Contacto - Id: 1, Nombre: Clarence, Apellido: Ho, Fecha de Nacimiento: 1980-07-30Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02Contacto - 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. Elsegundo bloque de líneas muestra que el nuevo registro fue añadido. El bloque final de las líneasmuestra 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 auna clase de ayuda o-peor aún-duplicado en cada clase DAO. Esta es la principal desventaja de JDBCdesde el punto de vista del programador de la aplicación-que, simplemente usted no tiene tiempopara programar el código repetitivo en todas las clases DAO. En su lugar, usted desea concentrarseen 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 lasexcepciones 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 querealmente no realiza ninguna lógica personalizada y le permite olvidarse de todos los tareas que deberealizar. Además, el amplio soporte de Spring JDBC hace su vida mucho más fácil.Infraestructura de Spring JDBCEl código del cual hablamos en la primera parte del capítulo no es muy complejo, pero es molestopara 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áselegantes.Información General y Paquetes UsadosEl soporte de JDBC en Spring está dividido en los cinco paquetes detallados en la Tabla 8-2, cada unomaneja diferentes aspectos de acceso JDBC.Tabla 8-2. Paquetes de Spring JDBCPaquete Descripciónorg.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 nivelmás bajo. Lo primero que usted tiene que hacer antes de siquiera pensar cómo ejecutar las consultasSQL, es establecer una conexión con la base de datos. 11
  • 13. Conexiones a Base de Datos y DataSourcesUsted puede usar Spring para manejar la conexión a la base de datos, proporcionando un bean queimplemente javax.sql.DataSource. La diferencia entre DataSource y Connection es queDataSource proporciona y maneja Connections.DriverManagerDataSource (dentro del paquete org.springframework.jdbc.datasource) es laimplementación más sencilla de un DataSource. Al observar el nombre de la clase, usted puedeadivinar que sencillamente llama a DriverManager para obtener una conexión. El hecho de queDriverManagerDataSource no soporte el pool de conexiones a bases de datos hace que esta clasesea inadecuada para algo más que pruebas. La configuración de DriverManagerDataSource esbastante sencilla, como usted puede ver en el Listado 8-10, sólo tiene que proporcionar el nombre dela 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 losvalores que normalmente se pasan a JDBC para obtener una interfaz Connection. La información dela conexión a la base de datos normalmente se almacena en un archivo de propiedades para un fácilmantenimiento y sustitución en diferentes entornos de despliegue. El Listado 8-11 muestra unjdbc.properties de ejemplo de la cual la propiedad placeholder de Spring cargará la informaciónde la conexión.Listado 8-11. El Archivo jdbc.propertiesjdbc.driverClassName=com.mysql.jdbc.Driverjdbc.url=jdbc:mysql://localhost:3306/prospring3_ch8jdbc.username=prospring3jdbc.password=prospring3En aplicaciones del mundo real, usted puede usar Apache Commons BasicDataSource(http://commons.apache.org/dbcp/) o un DataSource implementado por un servidor de aplicacionesJEE (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 obtenerlos mismos beneficios del pooling, sin embargo, en la mayoría de los casos, usted todavía perdería unlugar central para configurar el datasource. Spring, por el contrario, le permite declarar un beandataSource y establecer las propiedades de conexión en los archivos de definición deApplicationContext (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 enorg.apache.commons.dbcp.BasicDataSource. La parte más importante es que el bean DataSourceimplementa a javax.sql.DataSource, y usted puede empezar inmediatamente a usarlo en susclases 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ónmanejado por el contenedor. Para usar un dataSource basado en JNDI, usted necesita cambiar ladeclaració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 deldatasource JNDI. A partir de la versión 2.5, Spring proporciona el espacio de nombres jee, lo quesimplifica aún más la configuración. El Listado 8-14 muestra la misma configuración del dataSourceJNDI 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 laetiqueta <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 cambiardependiendo de cómo su módulo esté empaquetado. Por ejemplo, se convierte en <web-app> en eldescriptor de despliegue web (WEB-INF/web.xml) si la aplicación es un módulo web. Lo más probablees que usted tendrá que configurar el resource-ref en un archivo descriptor de la aplicación delservidor-específico también. Sin embargo, observe que el elemento resource-ref configura elnombre de la referencia a jdbc/prospring3ch8 y que el bean dataSource jndiName se establece ajava:comp/env/jdbc/prospring3ch8. Como usted puede ver, Spring le permite configurar el DataSource en casi cualquier formaque usted desee, y este oculta la implementación real o la ubicación del datasource del resto delcódigo de la aplicación. En otras palabras, sus clases DAO no saben y no tienen que saber dóndeseñala el DataSource. La administración de la conexión también es delegada al bean dataSource, que por su parte seadministra así mismo o utiliza el contenedor JEE que hacer todo el trabajo.Soporte de Base de Datos EmbebidasA partir de la versión 3.0, Spring también ofrece el soporte de base de datos embebidas, que iniciaautomá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 yasignarla con un ID de dataSource. Dentro de la etiqueta, también instruimos a Spring para queejecute los scripts especificados para crear el esquema de la base de datos y en consecuencia poblarlacon datos de prueba. Tenga en cuenta que es importante el orden de los scripts, y el archivo quecontiene 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 soportaHSQL (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 losejemplos.Usando DataSources en Clases DAOVamos a empezar de nuevo con una interfaz ContactDao vacía y una implementación sencilla de lamisma. Vamos a añadir más características a medida que avanzamos y explicamos lo que lo hagamoscon las clases de Spring JDBC. El listado 8-17 muestra la interfaz ContactDao vacía.Listado 8-17. Interfaz e Implementación de ContactDaopublic interface ContactDao {}public class JdbcContactDao implements ContactDao {} 15
  • 17. Para la implementación sencilla, primero añadiremos una propiedad dataSource. La razón porla que deseamos agregar la propiedad dataSource a la implementación de la clase en lugar de lainterfaz debería ser bastante obvia: la interfaz no necesita saber cómo se van a recuperar y actualizarlos datos. Añadiendo los métodos get/setDataSource a la interfaz, nosotros-en el mejor de losescenarios-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 claseJdbcContactDao en el Listado 8-18.Listado 8-18. Propiedad dataSource con JdbcContactDaopackage 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 laimplementación JdbcContactDao y establecer la propiedad dataSource (vea el Listado 8-19, elnombre 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 elproyecto, como se muestra en la Tabla 8-3.Tabla 8-3. Dependencia para la Base de Datos H2GroupID Artifact ID Version Descriptioncom.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 lapropiedad dataSource establecida en el bean dataSource. Es una buena práctica asegurarse de que se han establecido todas las propiedades requeridasde un bean. La forma más sencilla de hacerlo es implementar la interfaz InitializingBean yproporcionar una implementación para el método afterPropertiesSet() (vea el Listado 8-20). Deesta manera, usted se asegura de que se han establecido todas las propiedades requeridas en suJdbcContactDao. Para mayor información sobre la inicialización de bean, consulte el Capítulo 5.Listado 8-20. Implementación de JdbcContactDao con InitializingBeanpackage 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 lainterfaz ContactDao y su implementación JDBC. También se establece la propiedad dataSource en laclase JdbcContactDao en el archivo ApplicationContext de Spring. Ahora ampliamos el códigoañadiendo las operaciones DAO reales a la interfaz y a la implementación.Manejo de ExcepcionesDebido a que los defensores de Spring usan excepciones en tiempo de ejecución en lugar de lasexcepciones comprobadas, usted necesita un mecanismo para traducir la SQLException comprobadaen una excepción en tiempo de ejecución de Spring JDBC. Dado que las excepciones de Spring SQLson excepciones en tiempo de ejecución, ellas pueden ser mucho más detalladas que las excepcionescomprobadas. (Por definición, esta no es una característica de las excepciones en tiempo deejecución, pero es muy incómodo tener que declarar una larga lista de excepciones comprobadas enla cláusula throws, por lo que las excepciones comprobadas tienden a ser mucho más genéricas quesus equivalentes en tiempo de ejecución.) Spring proporciona una implementación predeterminada de la interfazSQLExceptionTranslator, que se encarga de traducir los códigos SQL genéricos de error en lasexcepciones 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 elListado 8-21. A la vez, tenemos que añadir la dependencia de spring-jdbc en el proyecto, como semuestra en la Tabla 8-4.Tabla 8-4. Dependencia para spring-jdbcGroupID Artifact ID Version Descriptionorg.springframework spring-jdbc 3.1.0.RELEASE Modulo Spring JDBCListado 8-21. SQLExceptionTranslator personalizadopackage 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á éstesobre 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. Paraotros errores, Spring retrocederá a su mecanismo por defecto para la traducción de excepciones. Obviamente, nada puede impedirle crear el SQLExceptionTranslator como un beanmanejado por Spring y usar el bean JdbcTemplate en sus clases DAO. No se preocupe si usted norecuerda haber leído acerca de la clase JdbcTemplate, vamos a hablar de ello con más detalle.La Clase JdbcTemplateEsta clase representa el núcleo del soporte de Spring JDBC. Esta puede ejecutar todo tipo desentencias SQL. Desde el punto de vista más simplista, usted puede clasificar las sentencias dedefinición y manipulación de datos. La cobertura de sentencias de definición de datos crea variosobjetos de base de datos (tablas, vistas, procedimientos almacenados, etc.). Las sentencias demanipulación de datos manipulan los datos y pueden ser clasificados como sentencias select yupdate. Una sentencia select generalmente devuelve un conjunto de filas, cada fila tiene el mismoconjunto de columnas. Una sentencia update modifica los datos en la base de datos pero no devuelveningún resultado. La clase JdbcTemplate le permite emitir cualquier tipo de sentencia SQL a la base de datos ydevolver cualquier tipo de resultado. En esta sección, iremos a través de varios casos de uso común para la programación JDBC enSpring con la clase JdbcTemplate.Inicializando JdbcTemplate en una Clase DAOAntes de discutir cómo usar JdbcTemplate, echemos un vistazo cómo preparar JdbcTemplate parasu uso en la clase DAO. Es muy sencillo, la mayor parte del tiempo usted sólo necesita construir laclase pasandole el objeto datasource (que debería ser inyectado por Spring en la clase DAO). ElListado 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 setDatasourcepara que una vez que el datasource sea inyectado por Spring, el JdbcTemplate también seainicializado y esté listo para su uso. Una vez configurado, el JdbcTemplate es thread safe. Eso significa que usted tambiénpuede optar por iniciar una única instancia de JdbcTemplate en la configuración XML de Spring ytener 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 JdbcTemplateEmpecemos con una consulta sencilla que devuelve un único valor. Por ejemplo, queremos sercapaces de recuperar el nombre de un contacto por su ID. Añadamos primero el método en la interfazContactDao: public String findFirstNameById(Long id); Usando JdbcTemplate, podemos recuperar el valor con facilidad. El Listado 8-24 muestra laimplementación del método findFirstNameById() en la clase JdbcContactDao. Para los otrosmétodos, se crearon implementaciones vacías.Listado 8-24. Usando JdbcTemplate para Recuperar un Único Valorpackage com.apress.prospring3.ch8.dao.jdbc.xml;// Omitidas las declaraciones importpublic 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 valordel first_name. El primer argumento es la cadena SQL, y el segundo argumento consiste en losparámetros que se pasan al SQL para enlazar el parámetro en forma de un objeto array. El últimoargumento es el tipo a ser devuelto, que en este caso es un String. Además de Object, ustedtambié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 JdbcTemplatepackage 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: ClarenceUsando Parámetros con Nombres con NamedParameterJdbcTemplateEn el ejemplo anterior, estamos usando el marcador de posición normal (el carácter ?) comoparámetros de consulta. Como también usted puede ver, tenemos que pasar los parámetros como unarray Object. Cuando se utiliza un marcador de posición normal, el orden es muy importante, y elorden en que usted pone los parámetros en el array debería ser el mismo orden de los parámetros enla consulta. Algunos desarrolladores (como yo) prefieren utilizar parámetros con nombre para asegurarque el parámetro está vinculado exactamente como quería. En Spring, una variante deJdbcTemplate, llamada NamedParameterJdbcTemplate (dentro del paqueteorg.springframework.jdbc.core.namedparam), proporciona soporte para esto. Veamos cómofunciona. 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 loque sólo tenemos que declarar una variable con el tipo NamedParameterJdbcTemplate y añadir lasiguiente 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 Valorpackage com.apress.prospring3.ch8.dao.jdbc.xml;// Omitidas las declaraciones importpublic 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ámetrocon 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ámetrollamado y el valor como el valor del parámetro. En lugar de SqlParameterSource, usted tambiénpuede simplemente construir un map para el almacenamiento de parámetros con nombre. El Listado8-27 es una variante del método anterior.Listado 8-27. Usando NamedParameterJdbcTemplate para Recuperar un Único Valorpackage com.apress.prospring3.ch8.dao.jdbc.xml;// Omitidas las declaraciones importpublic 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 elListado 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ásfilas 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 aPOJOs. Veamos esto en acción implementando el método findAll() de la interfaz ContactDaoutilizando la interfaz de RowMapper<T>. El Listado 8-28 muestra la implementación del métodofindAll().Listado 8-28. Usando RowMapper<T> en Consultas de Objetos de Dominiopackage 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 queimplementa la interfaz RowMapper<T>. La clase debe proporcionar la implementación mapRow(), quetransforma los valores en un registro específico del conjunto de resultados en el objeto de dominioque 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 cadenade 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 claseJdbcContactDaoSample).Listado 8-29. Fragmento de Código para Listar los Contactos// Encontrar y lista todos los contactosList<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-30Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02Contacto - Id: 3, Nombre: John, Apellido: Smith, Fecha de Nacimiento: 1964-02-28 23
  • 25. Recuperando Objetos de Dominio Anidados con ResultSetExtractorProsigamos con un ejemplo un poco más complicado, en el que tenemos que recuperar los datos dela tabla padres (CONTACT) e hija (CONTACT_TEL_DETAIL) con un join y en consecuencia transformarlos datos en el objeto anidado (ContactTelDetail a Contact). El RowMapper<T> anteriormente mencionado es adecuado únicamente para el mapeo base dela fila a un objeto de dominio único. Para una estructura de objeto más complicada, tenemos que usarla 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 consus detalles telefónicos. public List<Contact> findAllWithDetail(); El Listado 8-30 muestra la implementación del método findAllWithDetail() usandoResultSetExtractor.Listado 8-30. Usando ResultSetExtractor en Consultas de Objetos de Dominiopackage com.apress.prospring3.ch8.dao.jdbc.xml;// Omitidas las declaraciones importpublic 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 internaque implementa ResultSetExtractor. Luego implementamos el método extractData() para enconsecuencia transformar el conjunto de resultados en una lista de objetos Contact. Para el métodofindAllWithDetail(), la consulta utiliza un left join para unir las dos tablas y que también seanrecuperados 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 elresultsetExtractor. Agreguemos el siguiente fragmento de código (Listado 8-31) en el programa de pruebas (laclase JdbcContactDaoSample).Listado 8-31. Fragmento de Código para Listar los Contactos// Encontrar y listar todos los contactos con detallesList<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 salidasse 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: 1234567890Contacto - 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: 1234567890Contacto - 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 deconsulta común. JdbcTemplate (y también la clase NamedParameterJdbcTemplate) también ofreceuna 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 SqlUpdateproporcionada por Spring para realizar operaciones de actualización de datos.Clases Spring que Modelan Operaciones JDBCEn la sección anterior, vimos cómo JdbcTemplate y las clases de utilidad relacionadas con el mapeode datos han simplificado enormemente el modelo de programación en el desarrollo de la lógica deacceso a datos con JDBC. Construir sobre JdbcTemplate, Spring también proporciona un número declases útiles que modelan las operaciones JDBC de datos y permiten a los desarrolladores mantener laconsulta y transformar la lógica desde el conjunto de resultados a objetos de dominio de una maneramás orientada a objetos. Como se ha mencionado, las clases se empaquetan dentro deorg.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 AnotacionesPrimero vamos a mirar cómo configurar la implementación de la clase DAO usando anotaciones enprimer lugar. El Listado 8-32 muestra la interfaz de la clase ContactDao con un listado más completode los servicios de acceso a datos que este proporciona.Listado 8-32. Interfaze ContactDaopackage com.apress.prospring3.ch8.dao;// Omitidas las declaraciones importpublic 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 datasourceusando la anotación JSR-250. El nombre de la clase es JdbcContactDao, pero esta vez la ponemosdentro del paquete com.apress.prospring3.ch8.dao.jdbc.annotation.Listado 8-33. Declarando JdbcContactDao Usando Anotacionespackage 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 decontactDao, y puesto que la clase contiene un código de acceso a datos, @Repository tambiéninstruye a Spring a realizar excepciones SQL específicas de la bases de datos a la jerarquíaDataAccessException más amigables con la aplicación en Spring. 27
  • 29. También declaramos la variable log usando Apache commons-logging para registrar elmensaje dentro del programa. Y para la propiedad datasource, usamos @Resource de JSR-250 paraque 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éasela 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 estarmarcados, 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. Acontinuació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 datosembebida usando H2 y usamos <context:component-scan> para descubrir automáticamente elbean de Spring. Teniendo la infraestructura en su lugar, ahora podemos proceder a la ejecución delas 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 deconsulta. Por otro lado, implementamos el método mapRow() para mapear cada registro del conjuntode resultados en el objeto de dominio correspondiente. Primero vamos a implementar el método findAll(). Empezamos creando la claseSelectAllContacts (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 claseSelectAllContacts.Listado 8-35. La Clase SelectAllContactspackage 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 seleccionartodos 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étodoMappingSqlQuery<T>.mapRow() es implementado para proporcionar el mapeo del conjunto deresultados al objeto de dominio Contact. Teniendo la clase SelectAllContacts en su lugar, podemos implementar el métodofindAll() 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(), simplementeinvocamos el método SelectAllContacts.execute(), que se hereda indirectamente de la claseabstracta SqlQuery<T>. Eso es todo lo que tenemos que hacer. El Listado 8-37 muestra el programade ejemplo para probar la lógica.Listado 8-37. Probando MappingSqlQuerypackage com.apress.prospring3.ch8;// Omitidas las declaraciones importpublic 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-30Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02Contacto - Id: 3, Nombre: John, Apellido: Smith, Fecha de Nacimiento: 1964-02-28En 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 connombre. Al igual que el ejemplo anterior, creamos la clase SelectContactByFirstName para laoperación, que se muestra en el Listado 8-38.Listado 8-38. La Clase SelectContactByFirstNamepackage com.apress.prospring3.ch8.dao.jdbc.annotation;// Omitidas las declaraciones importpublic 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 (lasdiferencias se resaltan en negrita). En primer lugar, la sentencia SQL es diferente y lleva unparámetro con nombre llamado first_name. En el método constructor, se llama al métododeclareParameter() (que indirectamente es heredado de la clase abstractaorg.springframework.jdbc.object.RdbmsOperation). Prosigamos a implementar el métodofindByFirstName() 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 deSelectContactByFirstName (tenga en cuenta las líneas en negrita). Después, en el métodofindByFirstName(), 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 abstractaSqlQuery<T>). Para probar el método, agregue el siguiente fragmento de código del Listado 8-40 enla 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 unasola fila a un objeto de dominio. Para un objeto anidado, usted todavía tiene que usar JdbcTemplatecon ResultSetExtractor como en el ejemplo del método findAllWithDetail() presentado en lasección de la clase JdbcTemplate.Actualizando Datos Usando SqlUpdatePara actualizar los datos, Spring proporciona la clase SqlUpdate. El Listado 8-41 muestra la claseUpdateContact que extiende la clase SqlUpdate para operaciones de actualización.Listado 8-41. La Clase UpdateContactpackage 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 claseSqlUpdate 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 SqlUpdatepackage 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 deUpdateContact (tenga en cuenta las líneas en negrita). En el método update(), se construye unHashMap de parámetros con nombre pasados por el objeto Contact, y luego es llamado el métodoupdateByNamedParam() 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 elmétodo update(). Al ejecutar el programa se producirá la siguiente salida desde el último métodolistContacts():11:12:27,020 INFO 3.ch8.dao.jdbc.annotation.JdbcContactDao: 87 - Contacto actualactualizado con id: 1Contacto - Id: 1, Nombre: Clarence, Apellido: Peter, Fecha de Nacimiento: 1977-11-01Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02Contacto - 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 GeneradaPara insertar datos, también usamos la clase SqlUpdate. Sin embargo, un punto interesante aquí esacerca de la llave primaria, la columna id, que sólo estará disponible sólo después de que lasentencia de inserción se haya completado, mientras que un RDBMS genera el valor de identidad parael registro. La columna ID fue declarada con el atributo AUTO_INCREMENT y es la llave primaria, lo quesignifica 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 deuna secuencia de Oracle y luego disparar la sentencia de inserción con la consulta. Sin embargo, ennuestro caso, ¿cómo podemos recuperar la llave generada por un RDBMS después de que el registroes insertado? En antiguas versiones de JDBC, el método es un poco complicado. Por ejemplo, si estamosusando MySQL, tenemos que disparar el SQL select last_insert_id() y select @@IDENTITYpara Microsoft SQL Server. Afortunadamente, a partir de la versión 3.0 de JDBC, fue añadida una nueva característica quepermite recuperar una llave generada por un RDBMS de una manera unificada. El Listado 8-37muestra la implementación del método insert(), que también recupera la llave generada para elregistro 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 ala clase SqlUpdate. El Listado 8-44 muestra la clase.Listado 8-44. La Clase InsertContactpackage com.apress.prospring3.ch8.dao.jdbc.annotation;import java.sql.Types;import javax.sql.DataSource;import org.springframework.jdbc.core.SqlParameter; 36
  • 38. import org.springframework.jdbc.object.SqlUpdate;public class InsertContact extends SqlUpdate { private static final String SQL_INSERT_CONTACT = "insert into contact (first_name, last_name, birth_date) " + "values (:first_name, :last_name, :birth_date)"; public InsertContact(DataSource dataSource) { super(dataSource, SQL_INSERT_CONTACT); super.declareParameter(new SqlParameter("first_name", Types.VARCHAR)); super.declareParameter(new SqlParameter("last_name", Types.VARCHAR)); super.declareParameter(new SqlParameter("birth_date", Types.DATE)); super.setGeneratedKeysColumnNames(new String[] {"id"}); super.setReturnGeneratedKeys(true); }} La clase InsertContact es casi la misma que la clase UpdateContact. Sólo tenemos quehacer dos cosas más. Al construir la clase InsertContact, llamamos al métodoSqlUpdate.setGeneratedKeysColumnNames() para declarar el nombre de la columna ID. El métodoSqlUpdate.setReturnGeneratedKeys() indica al controlador JDBC subyacente que recupere lallave generada. El Listado 8-45 muestra la implementación del método insert() en la clase JdbcContactDao.Listado 8-45. Usando SqlUpdate para Operaciones de Inserciónpackage com.apress.prospring3.ch8.dao.jdbc.annotation;//Omitidos las declaraciones import@Repository("contactDao")public class JdbcContactDao implements ContactDao { private InsertContact insertContact; @Resource(name = "dataSource") public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; selectAllContacts = new SelectAllContacts(dataSource); selectContactByFirstName = new SelectContactByFirstName(dataSource); updateContact = new UpdateContact(dataSource); insertContact = new InsertContact(dataSource); } public void insert(Contact contact) { Map<String, Object> paramMap = new HashMap<String, Object>(); paramMap.put("first_name", contact.getFirstName()); paramMap.put("last_name", contact.getLastName()); paramMap.put("birth_date", contact.getBirthDate()); KeyHolder keyHolder = new GeneratedKeyHolder(); insertContact.updateByNamedParam(paramMap, keyHolder); contact.setId(keyHolder.getKey().longValue()); log.info("Contacto nuevo insertado con id: " + contact.getId()); } 37
  • 39. // Omitidos otros Códigos} Del Listado 8-45, después de la inyección del datasource, fue construida una instancia deInsertContact (tenga en cuenta las líneas en negrita). En el método insert(), también utilizamosel método SqlUpdate.updateByNamedParam(). Sin embargo, también pasamos en una instancia deKeyHolder al método, el cual tendrá almacenado el ID generado. Después de que los datos soninsertados, podemos recuperar entonces la llave generada desde el KeyHolder. Para comprobar el funcionamiento, agregue el siguiente fragmento de código del Listado 8-46en la clase AnnotationJdbcDaoSample.Listado 8-46. Probando el Método insert()// Insertar un contactocontact = new Contact();contact.setFirstName("Rod");contact.setLastName("Johnson");contact.setBirthDate(new Date((new GregorianCalendar(2001, 10, 1)).getTime().getTime()));contactDao.insert(contact);contacts = contactDao.findAll();listContacts(contacts); Al ejecutar el programa se producirá la siguiente salida desde el último métodolistContacts():11:36:08,871 INFO 3.ch8.dao.jdbc.annotation.JdbcContactDao: 88 - Contacto nuevo insertadocon id: 4Contacto - Id: 1, Nombre: Clarence, Apellido: Peter, Fecha de Nacimiento: 1977-11-01Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02Contacto - Id: 3, Nombre: John, Apellido: Smith, Fecha de Nacimiento: 1964-02-28Contacto - Id: 4, Nombre: Rod, Apellido: Johnson, Fecha de Nacimiento: 2001-11-01 Usted puede ver que el nuevo contacto fue insertado con un ID de 4 y recuperadocorrectamente.Operaciones de Procesamiento por Lotes con BatchSqlUpdatePara las operaciones por lotes, usamos la clase BatchSqlUpdate. El uso es básicamente el mismoque la clase SqlUpdate, sólo tenemos que hacer unas cuantas cosas más. Para demostrar su uso,vamos a añadir un nuevo método a la interfaz ContactDao:public void insertWithDetail(Contact contact);El nuevo método insertWithDetail() insertará tanto en el contacto como en sus datos telefónicosen la base de datos. Para poder insertar el registro de detalle telefónico, tenemos que crear la claseInsertContactTelDetail, que se muestra en el Listado 8-47.: La clase InsertContactTelDetail.Listado 8-47. La Clase InsertContactTelDetailpackage com.apress.prospring3.ch8.dao.jdbc.annotation; 38
  • 40. import java.sql.Types;import javax.sql.DataSource;import org.springframework.jdbc.core.SqlParameter;import org.springframework.jdbc.object.BatchSqlUpdate;public class InsertContactTelDetail extends BatchSqlUpdate { private static final String SQL_INSERT_CONTACT_TEL = "insert into contact_tel_detail (contact_id, tel_type, tel_number) " + "values (:contact_id, :tel_type, :tel_number)"; private static final int BATCH_SIZE = 10; public InsertContactTelDetail(DataSource dataSource) { super(dataSource, SQL_INSERT_CONTACT_TEL); declareParameter(new SqlParameter("contact_id", Types.INTEGER)); declareParameter(new SqlParameter("tel_type", Types.VARCHAR)); declareParameter(new SqlParameter("tel_number", Types.VARCHAR)); setBatchSize(BATCH_SIZE); }} Observe que en el constructor llamamos al método BatchSqlUpdate.setBatchSize() paraestablecer el tamaño del lote para la operación JDBC de inserción. El Listado 8-48 muestra la implementación del método insertWithDetail() en la claseJdbcContactDao.Listado 8-48. Operación SQL de Actualización por Lotepackage com.apress.prospring3.ch8.dao.jdbc.annotation;// Omitidas las declaraciones import@Repository("contactDao")public class JdbcContactDao implements ContactDao { private Log log = LogFactory.getLog(JdbcContactDao.class); private DataSource dataSource; private InsertContact insertContact; private InsertContactTelDetail insertContactTelDetail; public void insertWithDetail(Contact contact) { insertContactTelDetail = new InsertContactTelDetail(dataSource); // Insertar el contacto Map<String, Object> paramMap = new HashMap<String, Object>(); paramMap.put("first_name", contact.getFirstName()); paramMap.put("last_name", contact.getLastName()); paramMap.put("birth_date", contact.getBirthDate()); KeyHolder keyHolder = new GeneratedKeyHolder(); insertContact.updateByNamedParam(paramMap, keyHolder); contact.setId(keyHolder.getKey().longValue()); log.info("Contacto nuevo insertado con id: " + contact.getId()); 39
  • 41. // Insercion por lote de los detalles telefónicos del contacto List<ContactTelDetail> contactTelDetails = contact.getContactTelDetails(); if (contactTelDetails != null) { for (ContactTelDetail contactTelDetail : contactTelDetails) { paramMap = new HashMap<String, Object>(); paramMap.put("contact_id", contact.getId()); paramMap.put("tel_type", contactTelDetail.getTelType()); paramMap.put("tel_number", contactTelDetail.getTelNumber()); insertContactTelDetail.updateByNamedParam(paramMap); } } insertContactTelDetail.flush();}public List<Contact> findAllWithDetail() { JdbcTemplate jdbcTemplate = new JdbcTemplate(getDataSource()); String sql = "select c.id, c.first_name, c.last_name, c.birth_date" + ", t.id as contact_tel_id, t.tel_type, t.tel_number from contact c " + "left join contact_tel_detail t on c.id = t.contact_id"; return jdbcTemplate.query(sql, new ContactWithDetailExtractor());}private static final class ContactWithDetailExtractor implements ResultSetExtractor<List<Contact>> { public List<Contact> extractData(ResultSet rs) throws SQLException, DataAccessException { Map<Long, Contact> map = new HashMap<Long, Contact>(); Contact contact = null; while (rs.next()) { Long id = rs.getLong("id"); contact = map.get(id); if (contact == null) { // nuevo registro del contacto contact = new Contact(); contact.setId(id); contact.setFirstName(rs.getString("first_name")); contact.setLastName(rs.getString("last_name")); contact.setBirthDate(rs.getDate("birth_date")); contact.setContactTelDetails( new ArrayList<ContactTelDetail>()); map.put(id, contact); } // Procesar los detalles telefónicos del contacto (si existe) Long contactTelDetailId = rs.getLong("contact_tel_id"); if (contactTelDetailId > 0) { ContactTelDetail contactTelDetail = new ContactTelDetail(); contactTelDetail.setId(contactTelDetailId); contactTelDetail.setContactId(id); contactTelDetail.setTelType(rs.getString("tel_type")); contactTelDetail.setTelNumber(rs.getString("tel_number")); contact.getContactTelDetails().add(contactTelDetail); } } 40
  • 42. return new ArrayList<Contact>(map.values()); } } // Omitidos otros métodos} Del Listado 8-48, cada vez que es llamado el método insertWithDetail(), se construye unanueva instancia de InsertContactTelDetail. La razón es que la clase BatchSqlUpdate no esthread safe. Entonces la usamos al igual que SqlUpdate. Sin embargo, la clase BatchSqlUpdateencolará las operaciones de inserción y las envía por lotes a la base de datos. Cada vez que el númerode registros es igual al tamaño del lote, Spring disparará una operación de inserción masiva a la basede datos para los registros pendientes. Por otra parte, al finalizar, llamamos el métodoBatchSqlUpdate.flush() para instruir a Spring para que limpie todas las operaciones pendientes(es decir, las operaciones de inserción encoladas que aún no han alcanzado el tamaño del lotetodavía). Finalmente, recorremos la lista de objetos ContactTelDetail en el objeto Contact einvocamos el método BatchSqlUpdate.updateByNamedParam(). Para facilitar las pruebas, también fue implementado el método findAllWithDetail(). ElListado 8-49 muestra el fragmento de código para agregar a la clase AnnotationJdbcDaoSamplepara probar la operación de inserción por lotes.Listado 8-49. Probando el Método InsertWithDetail()// Insertar contacto con detallescontact = new Contact();contact.setFirstName("Michael");contact.setLastName("Jackson");contact.setBirthDate(new Date((new GregorianCalendar(1964, 10, 1)).getTime().getTime()));List<ContactTelDetail> contactTelDetails = new ArrayList<ContactTelDetail>();ContactTelDetail contactTelDetail = new ContactTelDetail();contactTelDetail.setTelType("Casa");contactTelDetail.setTelNumber("11111111");contactTelDetails.add(contactTelDetail);contactTelDetail = new ContactTelDetail();contactTelDetail.setTelType("Móvil");contactTelDetail.setTelNumber("22222222");contactTelDetails.add(contactTelDetail);contact.setContactTelDetails(contactTelDetails);contactDao.insertWithDetail(contact);contacts = contactDao.findAllWithDetail();listContacts(contacts); Al ejecutar el programa se producirá la siguiente salida desde el último métodolistContacts():Contacto - Id: 1, Nombre: Clarence, Apellido: Peter, Fecha de Nacimiento: 1977-11-01---Detalles Tél del Contacto - Id: 2, Contacto id: 1, Tipo: Casa, Número: 1234567890---Detalles Tél del Contacto - Id: 1, Contacto id: 1, Tipo: Móvil, Número: 1234567890Contacto - Id: 2, Nombre: Scott, Apellido: Tiger, Fecha de Nacimiento: 1990-11-02---Detalles Tél del Contacto - Id: 3, Contacto id: 2, Tipo: Casa, Número: 1234567890Contacto - Id: 3, Nombre: John, Apellido: Smith, Fecha de Nacimiento: 1964-02-28 41
  • 43. Contacto - Id: 4, Nombre: Rod, Apellido: Johnson, Fecha de Nacimiento: 2001-11-01Contacto - Id: 5, Nombre: Michael, Apellido: Jackson, Fecha de Nacimiento: 1964-11-01---Detalles Tél del Contacto - Id: 4, Contacto id: 5, Tipo: Casa, Número: 11111111---Detalles Tél del Contacto - Id: 5, Contacto id: 5, Tipo: Móvil, Número: 22222222 Usted puede ver que los nuevos contactos con los datos telefónicos fueron todos insertados enla base de datos.Llamando Funciones Almacenadas Usando SqlFunctionSpring también ofrece una serie de clases para simplificar la ejecución de procedimientosalmacenados y funciones usando JDBC. En esta sección, le mostraremos una función sencilla usandola clase SqlFunction para llamar a una función SQL en la base de datos. Usaremos MySQL como unejemplo, crearemos una función almacenada, y la llamaremos usando la clase SqlFunction<T>. Suponemos que usted tiene una base de datos MySQL con un esquema denominadoprospring3_ch8, con un nombre de usuario y contraseña, ambos iguales a prospring3 (igual que elejemplo de la sección "Explorando la infraestructura JDBC"). Creamos una función almacenadallamada getFirstNameById(), que acepta el ID del contacto y devuelve el nombre del contacto. ElListado 8-50 muestra el script para crear la función almacenada en MySQL (store-function.sql).Ejecute el script contra la base de datos MySQL.Listado 8-50. Función Almacenada para MysqlDELIMITER //CREATE FUNCTION getFirstNameById(in_id INT) RETURNS VARCHAR(60)BEGIN RETURN (SELECT first_name FROM contact WHERE id = in_id);END //DELIMITER ; La función almacenada debería ser auto-explicativa. Simplemente acepta el ID y devuelve elnombre del contacto con el ID del registro. Creamos una nueva interfaz llamada ContactSfDao para este ejemplo. El Listado 8-51muestra la interfaz.Listado 8-51. La Interfaz ContactSfDaopackage com.apress.prospring3.ch8.dao;public interface ContactSfDao { public String getFirstNameById(Long id);} El segundo paso es crear la clase SfFirstNameById para representar a la operación de lafunción almacenada, que extiende a la clase SqlFunction<T>. El Listado 8-52 muestra la clase.Listado 8-52. La Clase SfFirstNameByIdpackage com.apress.prospring3.ch8.dao.jdbc.annotation; 42
  • 44. import java.sql.Types;import javax.sql.DataSource;import org.springframework.jdbc.core.SqlParameter;import org.springframework.jdbc.object.SqlFunction;public class SfFirstNameById extends SqlFunction<String> { private static final String SQL = "select getfirstnamebyid(?)"; public SfFirstNameById(DataSource dataSource) { super(dataSource, SQL); declareParameter(new SqlParameter(Types.INTEGER)); compile(); }} En el listado 8-52, se extiende la clase SqlFunction<T> y le pasa el tipo String, que indica eltipo de retorno de la función. Luego declaramos el código SQL para llamar a la función almacenada enMySQL. Después, en el constructor, se declara el parámetro, y luego, se compila la operación. Ahorala clase está lista para nuestro uso en la implementación de la clase. El Listado 8-53 muestra la claseJdbcContactSfDao, que implementa la interfaz ContactSfDao.Listado 8-53. La Clase JdbcContactSfDaopackage com.apress.prospring3.ch8.dao.jdbc.annotation;import java.util.List;import javax.annotation.Resource;import javax.sql.DataSource;import org.springframework.stereotype.Repository;import com.apress.prospring3.ch8.dao.ContactSfDao;@Repository("contactSfDao")public class JdbcContactSfDao implements ContactSfDao { private DataSource dataSource; private SfFirstNameById sfFirstNameById; @Resource(name = "dataSource") public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; sfFirstNameById = new SfFirstNameById(dataSource); } public DataSource getDataSource() { return dataSource; } 43
  • 45. public String getFirstNameById(Long id) { List<String> result = sfFirstNameById.execute(id); return result.get(0); }} En el Listado 8-53, después de la inyección del datasource, se construye una instancia deSfFirstNameById. Luego en el método getFirstNameById(), se llama al método execute(),pasando el ID del contacto. El método devolverá una lista de Strings, y sólo necesitamos el primero,porque debería devolver un sólo registro en el conjunto de resultados. El Listado 8-54 muestra el archivo de configuración de Spring para conectarse a MySQL (app-context-sf.xml).Listado 8-54. Configuración Spring para Mysql<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd"> <import resource="datasource-dbcp.xml" /> <context:component-scan base-package="com.apress.prospring3.ch8.dao.jdbc.annotation"/> <context:annotation-config /></beans> En el Listado 8-54, es importado el archivo datasource-dbcp.xml, que tiene la configuración deldatasource a la base de datos MySQL. Para ejecutar el programa, debería agregarse la dependenciacommons-dbcp al proyecto, como se muestra en la Tabla 8-5.Tabla 8-5. Dependencia para commons-dbcpGroupID Artifact ID Version Description Librería Apache commons-dbcp para el pool de conexión a lacommons-dbcp commons-dbcp 1.4 base de datos El Listado 8-55 muestra las pruebas del programa.Listado 8-55. Probando la Función Almacenada en Mysqlpackage com.apress.prospring3.ch8;import org.springframework.context.support.GenericXmlApplicationContext;import com.apress.prospring3.ch8.dao.ContactSfDao;public class JdbcContactSfDaoSample { 44
  • 46. public static void main(String[] args) { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load("classpath:app-context-sf.xml"); ctx.refresh(); ContactSfDao contactSfDao = ctx.getBean("contactSfDao", ContactSfDao.class); System.out.println(contactSfDao.getFirstNameById(1l)); }} En el programa, pasamos un ID de 1 en la función almacenada. Esto devolverá a Clarencecomo el nombre si se ejecutó test-data.sql contra la base de datos MySQL. La ejecución delprograma produce el siguiente resultado:15:16:11,990 DEBUG g.springframework.jdbc.core.JdbcTemplate: 635 - Executing prepared SQLquery15:16:11,991 DEBUG g.springframework.jdbc.core.JdbcTemplate: 570 - Executing prepared SQLstatement [select firstnamebyid(?)]15:16:11,998 DEBUG ramework.jdbc.datasource.DataSourceUtils: 110 - Fetching JDBCConnection from DataSource15:16:12,289 DEBUG ramework.jdbc.datasource.DataSourceUtils: 332 - Returning JDBCConnectionto DataSourceClarence Usted puede ver que se recuperó el nombre correctamente. Lo que es presentado aquí es sólo una muestra sencilla para demostrar el módulo de lasfunciones de Spring JDBC. Spring también ofrece otras clases (por ejemplo, StoredProcedure) paraque usted pueda invocar la complejidad de los procedimientos almacenados que devuelven tipos dedatos complejos. Le recomendamos que consulte el manual de referencia de Spring en caso de quenecesite acceder a los procedimientos almacenados usando JDBC.Usando la Configuración de JavaEn caso de que usted prefiera usar la clase de configuración de Java en lugar de la configuración XML,el Listado 8-56 muestra la clase de configuración de Spring.Listado 8-56. Usando la Configuración de Javapackage com.apress.prospring3.ch8.javaconfig;import javax.sql.DataSource;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase;import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;@Configuration@ComponentScan(basePackages = "com.apress.prospring3.ch8.dao.jdbc.annotation")public class AppConfig { 45
  • 47. @Bean public DataSource dataSource() { EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder(); EmbeddedDatabase db = builder.setType(EmbeddedDatabaseType.H2) .addScript("schema.sql").addScript("test-data.sql").build(); return db; }} En el listado anterior, usamos EmbeddedDatabaseBuilder para construir la base de datos H2embebida, el efecto es el mismo como cuando se utiliza la etiqueta <jdbc:embedded-database> enla configuración XML. Usted también puede usar la característica @Profile para especificar que laconfiguración es el único blanco para un entorno específico (por ejemplo, dev).Proyecto Spring Data: JDBC ExtensionsComo mencionamos al comienzo de este capítulo, en los últimos años la tecnología de base de datosha evolucionado tan rápidamente con la aparición de tantas bases de datos de propósitos específicos,hoy día un RDBMS no es la única opción como un sistemas de gestión de bases de datos back-end deuna aplicación. Para responder a esta evolución tecnológica de las bases de datos y a la necesidad dela comunidad de desarrolladores, Spring creó el proyecto Spring Data (www.springsource.org/spring-data). El objetivo principal del proyecto es proporcionar extensiones útiles por encima de lafuncionalidad básica de acceso de datos de Spring para atender las necesidades de los desarrolladoresde Spring que interactúan con motores de bases de datos distintas a RDBMSs. También estándisponibles las características avanzadas para los estándares de acceso a datos (por ejemplo, JDBC,JPA). El proyecto Spring Data viene con un montón de extensiones. Una extensión que nos gustaríamencionar aquí es JDBC Extensions (www.springsource.org/spring-data/jdbc-extensions). Como sunombre lo indica, la extensión proporciona algunas características avanzadas que facilitan el desarrollode aplicaciones JDBC usando Spring. Al momento de escribir, la primera versión (versión 1.0.0) todavía estaba en su etapamilestone. Las características principales que proporciona la extensión son listadas aquí: Soporte de QueryDSL: QueryDSL (www.querydsl.com) es un lenguaje específico de dominio que establece el marco para el desarrollo de algunas consultas de tipo seguro. Spring Data JDBC Extensions Proporcionan extensiones QueryDslJdbcTemplate para facilitar el desarrollo de aplicaciones JDBC que utilizan QueryDSL en lugar de sentencias SQL. Soporte Avanzado para Bases de Datos de Oracle: La extensión proporciona una gran cantidad de características avanzadas para los usuarios de bases de datos de Oracle. Por el lado de la conexión a la base de datos, soporta configuraciones de sesión específicas de Oracle, así como la tecnología Fast Connection Failover cuando se trabaja con Oracle RAC. Además, se proporcionan las clases que se integran con Oracle Advanced Queuing. Del lado del tipo de dato, se proporciona el soporte nativo para tipos XML de Oracle, STRUCT y ARRAY, etcétera. Si está desarrollando aplicaciones JDBC usando Spring con Base de Datos de Oracle, JDBCExtensions merece realmente una oportunidad.Consideraciones para Usar JDBCDesde los debates anteriores, usted puede ver cómo Spring puede hacer su vida mucho más fácilusando JDBC para interactuar con un RDBMS. Sin embargo, todavía hay un montón de código que 46
  • 48. usted necesita desarrollar, especialmente cuando se transforma el conjunto de resultados en loscorrespondientes objetos de dominio. Además de JDBC, se han desarrollado una gran cantidad de librerías de código abierto paraayudar a cerrar la brecha entre la estructura de datos relacional y el modelo orientado a objetos deJava. Por ejemplo, MyBatis (anteriormente conocido como iBATIS) es un popular frameworkDataMapper que también se basa en el mapeo SQL. MyBatis le permite mapear objetos conprocedimientos almacenados o consultas a un archivo descriptor XML (también es soportadoanotaciones de Java). Al igual que Spring, MyBatis proporciona una forma declarativa para consultarel mapeo de objeto, le ahorra enormemente el tiempo que este toma para mantener consultas SQLque pueden ser dispersadas por diferentes clases DAO. También hay muchos otros frameworks ORM que se centran en el modelo de objetos, en lugarde la consulta. Los más populares son Hibernate, EclipseLink (también conocido como TopLink),y OpenJPA. Todos ellos cumplen con la especificación JPA del JCP. En los últimos años, las herramientas ORM y los frameworks de mapeo se han vuelto muchomás maduros por lo que la mayoría de los desarrolladores se decidirán por uno de ellos, en lugar deusar JDBC directamente. Sin embargo, por motivos de rendimiento, en los casos donde usted necesitetener un control absoluto sobre la consulta que se presentará a la base de datos (por ejemplo, usandouna consulta jerárquica en Oracle), Spring JDBC es realmente una opción viable. Y usando Spring,una gran ventaja es que puedes mezclar y combinar diferentes tecnologías de acceso de datos. Porejemplo, usted puede usar Hibernate como el ORM principal y luego JDBC como un suplementocomo parte de la lógica de consultas complejas u operaciones por lotes, usted puede mezclar ycombinar en una sola operación de negocio y luego envolverlas en la misma transacción de la base dedatos. Spring le ayudará a manejar estas situaciones con facilidad.ResumenEn este capítulo se demostró cómo usar Spring para simplificar la programación JDBC. Usted aprendiócómo conectarse a una base de datos y realizar operaciones de selección, actualización, eliminación, einserción, y llamar funciones almacenadas. Cómo usar la clase núcleo de Spring JDBC, JdbcTemplate,fue discutida en detalle. Además, cubrimos otras clases de Spring que se construyen a partir deJdbcTemplate y que le ayudará a modelar varias operaciones JDBC. En los próximos capítulos, vamosa discutir cómo utilizar tecnologías de Spring con ORM populares desarrollando la lógica de acceso adatos. 47