7. [TD]: Implementación de la capa [DAO] de TD con API y JDBC
Palabras clave: bases de datos relacionales, API, JDBC, SQLException.
Volvamos a la arquitectura en capas de nuestra aplicación:
![]() |
Los datos necesarios para la elección se almacenan en una base de datos MySQL [dbelections]
7.1. Support
![]() |
La carpeta [support / chap-07] [1] contiene:
- los proyectos de Eclipse de este capítulo [2];
- el script SQL para la creación de la base de datos MySQL, [dbelections] y [3];
7.2. La base de datos [dbelections]
Tarea a realizar: cree las bases de datos MySQL y [dblelections] siguiendo el procedimiento descrito en el párrafo 6.4.2.
La base [dbelections] es una base MySQL con dos tablas:
![]() |
La tabla [conf] contiene la información de configuración de la elección:
![]() |
- [id]: clave primaria autoincremental;
- [version]: número de versión del registro; aquí se puede omitir;
- [sap]: número de escaños por cubrir;
- [seuilelectoral]: el umbral por debajo del cual se elimina una lista;
Su contenido es el siguiente:
![]() |
La tabla [listes] contiene las listas de candidatos de la elección:
![]() |
- [id]: clave primaria autoincremental;
- [version]: número de versión del registro; aquí se puede omitir;
- [nom]: nombre de la lista;
- [voix]: opción de la lista —solo se conoce después de que el usuario la haya ingresado en la capa [présentation];
- [sieges]: número de escaños obtenidos —solo se conoce después de calcular la capa [métier];
- [elimine]: 1 si la lista queda eliminada, 0 en caso contrario; solo se conoce tras el cálculo de la capa [métier];
El contenido de la tabla [listes] es el siguiente:
![]() |
El script SQL para generar la base [dbelections] se llama [dbelections.sql] y se encuentra en el servidor. Su código es el siguiente:
-- phpMyAdmin SQL Volcado
-- versión 4.0.4
-- http://www.phpmyadmin.net
--
-- Cliente: localhost
-- Generado el: miércoles 11 de marzo de 2015 a las 12:20
-- Versión del servidor: 5.6.12-log
-- Versión de PHP: 5.4.12
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
-- Base de datos: `dbelections`
--
CREATE DATABASE IF NOT EXISTS `dbelections` DEFAULT CHARACTER SET utf8 COLLATE utf8_swedish_ci;
USE `dbelections`;
-- --------------------------------------------------------
--
-- Estructura de la tabla `conf`
--
CREATE TABLE IF NOT EXISTS `conf` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`version` int(11) NOT NULL DEFAULT '1',
`sap` tinyint(4) NOT NULL,
`seuilelectoral` double NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_swedish_ci AUTO_INCREMENT=2 ;
--
-- Contenido de la tabla `conf`
--
INSERT INTO `conf` (`id`, `version`, `sap`, `seuilelectoral`) VALUES
(1, 1, 6, 0.05);
-- --------------------------------------------------------
--
-- Estructura de la tabla `listas`
--
CREATE TABLE IF NOT EXISTS `listes` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`version` int(11) NOT NULL DEFAULT '1',
`nom` varchar(20) COLLATE utf8_swedish_ci NOT NULL,
`voix` int(11) NOT NULL,
`sieges` int(11) NOT NULL,
`elimine` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `nom` (`nom`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_swedish_ci AUTO_INCREMENT=8 ;
--
-- Contenido de la tabla `listas`
--
INSERT INTO `listes` (`id`, `version`, `nom`, `voix`, `sieges`, `elimine`) VALUES
(1, 21, 'A', 10, 1, 0),
(2, 22, 'B', 20, 2, 0),
(3, 21, 'C', 30, 3, 0),
(4, 13, 'D', 40, 3, 0),
(5, 17, 'E', 50, 6, 0),
(6, 18, 'F', 60, 1, 0),
(7, 19, 'G', 70, 2, 0);
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
7.3. El proyecto Eclipse
![]() |
El proyecto Eclipse de la capa [DAO] será el siguiente:
![]() |
- el paquete [elections.dao.entities] contiene los objetos manipulados por la capa [DAO];
- el paquete [elections.dao.service] contiene la implementación de la capa [DAO];
- el paquete [elections.dao.config] contiene la configuración de la capa [DAO]
- el paquete [elections.dao.junit] contiene una clase de prueba JUnit del proyecto;
- el paquete [elections.dao.console] contiene una clase ejecutable de prueba;
7.4. Configuración del proyecto Maven
![]() |
El archivo [pom.xml] que configura el proyecto Maven es el siguiente:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-dao-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<!-- biblioteca jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Prueba de Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Registro de Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- plugins -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>config.AppConfig</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- para instalar el artefacto del proyecto en el repositorio local de Maven -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
Este archivo es similar al descrito en el párrafo 6.5.1. Se le han realizado las siguientes modificaciones:
- líneas 8-12: se ha definido un proyecto Maven padre. El proyecto [spring-boot-starter-parent] (línea 10) define un gran número de dependencias con sus versiones. Cuando se utiliza una de ellas (líneas 19-57), no es necesario especificar la versión, ya que esta está definida en el proyecto Maven padre;
- líneas 40-51: dependencias necesarias para la clase de prueba del proyecto. Estas dependencias tienen el atributo [<scope>test</scope>], lo que significa que solo son necesarias para las clases de la carpeta [src / test / java]. Estas dependencias no se incluirán en el archivo final del proyecto;
- líneas 53-56: Spring utilizará la biblioteca [spring-boot-starter-logging] para generar registros en pantalla;
- líneas 14-17: propiedades de configuración de Maven:
- línea 15: indica que los archivos fuente están escritos en UTF-8;
- línea 16: indica que se debe utilizar el compilador de nivel 1.8;
7.5. Las entidades de la capa [DAO]
![]() |
- [ElectionsConfig] es el modelo de objeto asociado a una fila de la tabla [CONF];
- [ListeElectorale] es el modelo de objeto asociado a una fila de la tabla [LISTES];
- [AbstactEntity] es la clase padre de las dos clases anteriores. Agrupa los campos [id, version] que son comunes a ambas clases;
- [ElectionsException] es una clase de excepción;
7.5.1. La clase [ElectionsException]
![]() |
La clase [ElectionsException] se describió en el párrafo 4.3. A continuación, volvemos a presentar su código:
package ...;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// clase de excepción para la aplicación Elecciones
// la excepción es no controlada
public class ElectionsException extends RuntimeException implements Serializable {
// serial ID
private static final long serialVersionUID = 1L;
// campos locales
private int code;
private List<String> erreurs;
// constructores
public ElectionsException() {
super();
}
public ElectionsException(int code, Throwable e) {
// padre
super(e);
// local
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message, Throwable e) {
// padre
super(message,e);
// local
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message) {
// superior
super(message);
// local
this.code = code;
List<String> erreurs = new ArrayList<>();
erreurs.add(message);
this.erreurs = erreurs;
}
public ElectionsException(int code, List<String> erreurs) {
// superior
super();
// local
this.code = code;
this.erreurs = erreurs;
}
// lista de mensajes de error de una excepción
private List<String> getErreursForException(Throwable th) {
// se recupera la lista de mensajes de error de la excepción
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// se recupera el mensaje solo si no es nulo y no está en blanco
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// causa siguiente
cause = cause.getCause();
}
return erreurs;
}
// getters y setters
...
}
Un objeto [ElectionsException] se caracteriza por dos datos:
- línea 16: un código de error;
- línea 17: una lista de mensajes de error asociados a la pila de excepciones que se han producido;
- la clase tiene 5 constructores (líneas 20, 24, 32, 40, 50);
- líneas 59-76: el método [getErreursForException] permite recuperar los mensajes de error de la pila de excepciones;
7.5.2. La clase [AbstractEntity]
![]() |
La clase [AbstractEntity] es la siguiente:
package elections.dao.entities;
import java.io.Serializable;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public abstract class AbstractEntity implements Serializable {
private static final long serialVersionUID = 1L;
// campos
protected Long id;
protected Long version;
// constructores
public AbstractEntity() {
}
public AbstractEntity(Long id, Long version) {
this.id = id;
this.version = version;
}
// firma
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
// getters y setters
...
}
Almacena los campos [ID, NOM] de las filas de las tablas [CONF] y [LISTES] (filas 8-9).
- línea 8: la clase tiene el atributo [Abstract], lo que indica que no se puede instanciar. Solo puede derivarse;
- líneas 26-33: firma jSON del objeto;
- línea 28: se devuelve la cadena jSON de [this]. Si en el momento de la ejecución, [this] representa un objeto derivado de [AbstactEntity], se obtiene la cadena jSON del objeto derivado. De esta manera, las clases derivadas no necesitarán definir un método [toString]. El de la clase padre es suficiente;
7.5.3. La clase [ElectionsConfig]
![]() |
La clase [ElectionsConfig] es la siguiente:
package elections.dao.entities;
public class ElectionsConfig extends AbstractEntity {
private static final long serialVersionUID = 1L;
// campos
private int nbSiegesAPourvoir;
private double seuilElectoral;
// constructores
public ElectionsConfig() {
}
public ElectionsConfig(Long id, Long version, int nbSiegesAPourvoir, double seuilElectoral) {
// padre
super(id, version);
// campos locales
this.nbSiegesAPourvoir = nbSiegesAPourvoir;
this.seuilElectoral = seuilElectoral;
}
// getters y setters
...
}
- línea 4: la clase extiende la clase [AbstractEntity];
- líneas 8-9: almacenan la información de las columnas [SAP, SEUILELECTORAL] de la tabla [CONF];
7.5.4. La clase [ListeElectorale]
![]() |
La clase [ListeElectorale] es la siguiente:
package elections.dao.entities;
public class ListeElectorale extends AbstractEntity {
// campos
private String nom;
private int voix;
private int sieges;
private boolean elimine;
// constructores
public ListeElectorale() {
}
public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
// padre
super();
// campos locales
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
public ListeElectorale(Long id, Long version, String nom, int voix, int sieges, boolean elimine) {
// padre
super(id, version);
// campos locales
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
// métodos privados
private void initNom(String nom) {
this.nom = nom.trim();
if ("".equals(nom)) {
throw new ElectionsException(10, "Le nom ne peut être vide");
}
}
private void initVoix(int voix) {
this.voix = voix;
if (voix < 0) {
throw new ElectionsException(11, "Le nombre de voix ne peut être <0");
}
}
private void initSieges(int sieges) {
this.sieges = sieges;
if (sieges < 0) {
throw new ElectionsException(12, "Le nombre de sièges ne peut être <0");
}
}
// getters y setters
public String getNom() {
return nom;
}
public void setNom(String nom) {
initNom(nom);
}
public int getVoix() {
return voix;
}
public void setVoix(int voix) {
initVoix(voix);
}
public int getSieges() {
return sieges;
}
public void setSieges(int sieges) {
initSieges(sieges);
}
public boolean isElimine() {
return elimine;
}
public void setElimine(boolean elimine) {
this.elimine = elimine;
}
}
- línea 3: la clase extiende la clase [AbstractEntity];
- líneas 6-9: la clase almacena las columnas [NOM, VOIX, SIEGES, ELIMINE] de la tabla [LISTES];
7.6. Configuración de Spring para la capa [DAO]
![]() |
![]() |
La clase [AppConfig] es una clase de configuración de Spring que configura el acceso a la base de datos de la siguiente manera:
package elections.dao.config;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan(basePackages = { "elections.dao.service" })
@EnableCaching
public class AppConfig {
// constantes
public final static String URL = "jdbc:mysql://localhost:3306/dbelections";
public final static String USER = "root";
public final static String PASSWD = "";
public final static String DRIVER_CLASSNAME = "com.mysql.jdbc.Driver";
public final static String SELECT_LISTES = "SELECT ID, VERSION, NOM, VOIX, SIEGES, ELIMINE FROM LISTES";
public final static String SELECT_CONF = "SELECT ID, VERSION, SAP, SEUILELECTORAL, SAP FROM CONF";
public final static String UPDATE_LISTES = "UPDATE LISTES SET VOIX=?, SIEGES=?, ELIMINE=? WHERE ID=?";
@Bean
public DataSource dataSource() {
// fuente de datos TomcatJdbc
DataSource dataSource = new DataSource();
// configuración de acceso JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// una conexión abierta inicialmente
dataSource.setInitialSize(1);
// resultado
return dataSource;
}
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- líneas 25-38: el acceso a BD se realizará a través de una fuente de datos [tomcat-jdbc]. Este tipo de fuente se utilizó y explicó en el párrafo 6.5;
- líneas 17-23: un conjunto de constantes estáticas accesibles para todas las clases del proyecto;
- línea 13: la anotación [@Configuration] convierte a la clase [AppConfig] en una clase de configuración de Spring;
- línea 11: la anotación [@ComponentScan] indica los paquetes donde se pueden encontrar objetos de Spring. Aquí, vamos a definir un objeto de Spring en el paquete [dao]. La anotación [@ComponentScan] convierte a la clase en una clase de configuración, lo que nos ahorra tener que incluir la anotación [@Configuration];
- la línea 12 activa la gestión de una caché. El principio es el siguiente:
- se aplica la anotación [@Cacheable('nom_du_cache')] a un método M. El «nombre_de_la_caché» es el nombre utilizado en la línea 41;
- cuando se invoca el método M por primera vez, se devuelven sus resultados y también se almacenan en la caché indicada por la anotación;
- cuando el método M se invoca las siguientes veces con los mismos parámetros que la primera vez, no se ejecuta y Spring simplemente devuelve los valores almacenados en la caché;
7.7. Configuración de los registros
![]() |
Las bibliotecas de registros se definen mediante la siguiente dependencia en [pom.xml]:
<!-- Registro de Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Esta dependencia incluye las siguientes bibliotecas:
![]() |
La biblioteca [logback] se encargará de los registros. Se configura mediante dos archivos:
- [logback.xml] para la rama principal del código;
- [logback-test.xml] para la rama de prueba del código. Si no existe este archivo, se utiliza el anterior;
Estos dos archivos deben encontrarse en el directorio [Classpath] del proyecto. Por esta razón, se colocan en la carpeta:
- [src / main / ressources] para la rama principal del código;
- [src / test / ressources] para la rama de prueba del código;
El contenido de los archivos es el mismo en ambos casos:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- a los codificadores se les asigna por defecto el tipo
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- control del nivel de los registros -->
<root level="info"> <!-- info, debug, warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
Todo ocurre en la línea 12, donde se establece el nivel de información deseado:
- [debug]: el nivel más detallado;
- [off]: sin registros;
- [info]: el nivel normal de registros;
- [warn]: igual que el nivel [info], pero con mensajes de advertencia (warning). Estos mensajes indican la posibilidad de un error;
Cambie al modo [debug] tan pronto como aparezcan errores durante la ejecución.
7.8. Implementación de la capa [DAO]
![]() |
![]() |
La interfaz [IDao] de la capa [DAO] es la siguiente:
package istia.st.elections.webapi.client.dao;
import istia.st.elections.webapi.client.entities.ElectionsConfig;
import istia.st.elections.webapi.client.entities.ListeElectorale;
public interface IDao {
// Configuración de las elecciones
public ElectionsConfig getElectionsConfig();
// las listas en contienda
public ListeElectorale[] getListesElectorales();
// almacenamiento de los resultados de la elección
public void setListesElectorales(ListeElectorale[] listesElectorales);
}
El esqueleto de la clase [ElectionsDaoJdbc] que implementa la capa [dao] con una base de datos será el siguiente:
package elections.dao.service;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ElectionsException;
import elections.dao.entities.ListeElectorale;
@Component
@SuppressWarnings("unused")
public class ElectionDaoJdbc implements IElectionsDao {
@Autowired
private DataSource dataSource;
@Cacheable("electionsConfig")
// obtención de la configuración de la elección
public ElectionsConfig getElectionsConfig() {
throw new RuntimeException("[getElectionsConfig] not yet implemented");
}
// obtención de las listas
public ListeElectorale[] getListesElectorales() {
throw new RuntimeException("[getListesElectorales] not yet implemented");
}
// modificación de las listas [voix, sieges, elimine]
public void setListesElectorales(ListeElectorale[] listesElectorales) {
throw new RuntimeException("[setListesElectorales] not yet implemented");
}
// -------------------- métodos privados
// gestión del finally
private ElectionsException doFinally(int code, ResultSet rs, PreparedStatement ps, Connection connexion) {
// cierre ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// cierre [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// cerrar la conexión
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// se devuelve un [ElectionsException]
return new ElectionsException(code, e3);
}
}
// sin excepción
return null;
}
// gestión de la captura
private ElectionsException doCatchException(int code1, int code2, Connection connexion, Throwable th) {
// se genera un [ElectionsException]
ElectionsException ex1 = new ElectionsException(code1, th);
// anulación de la transacción
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
}
// se devuelve la excepción
return ex1;
}
}
- línea 24: la anotación [@Cacheable] es una anotación de Spring que solicita almacenar en memoria («almacenar en caché») los resultados del método [getElectionsConfig]. Es posible hacer esto aquí porque el contenido de la tabla [CONF] nunca cambia. No sería posible aplicar esta anotación al método [getListesElectorales], ya que el contenido de la tabla [LISTES] cambia con el tiempo;
- los métodos [doCatchException] y [doFinally] devuelven un tipo [ElectionsException]. El método [doFinally] devuelve un puntero null si la liberación de recursos se realizó sin errores;
Tarea: escriba la clase [ElectionDaoJdbc] inspirándose en la clase [IntroJdbc02] estudiada en el párrafo 6.5.2.
7.9. La clase de prueba [Main]
![]() |
![]() |
La clase [Main] es la siguiente:
package elections.dao.console;
import java.util.ArrayList;
import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import elections.dao.config.AppConfig;
import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ElectionsException;
import elections.dao.entities.ListeElectorale;
import elections.dao.service.IElectionsDao;
public class Main {
// fuente de datos
private static IElectionsDao dao;
public static void main(String[] args) {
// se recupera la referencia de la capa [DAO] después de instanciar el contexto de Spring
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class)) {
// recuperación de la fuente de datos
dao = ctx.getBean(IElectionsDao.class);
} catch (Exception e) {
System.out.println("Les erreurs suivantes se sont produites lors de l'initialisation du contexte Spring -------");
for (String erreur : getErreursForThrowable(e)) {
System.out.println(erreur);
}
// fin
return;
}
// lectura de la tabla BD
ElectionsConfig electionsConfig = null;
ListeElectorale[] listes;
try {
// contenido de las dos tablas
electionsConfig = dao.getElectionsConfig();
listes = dao.getListesElectorales();
} catch (ElectionsException e) {
System.out.println("Les erreurs suivantes se sont produites lors de la lecture des tables ----------");
for (String erreur : e.getErreurs()) {
System.out.println(erreur);
}
// fin
return;
}
// todo salió bien - visualización
System.out.println(String.format("Nombre de sièges à pourvoir : %d", electionsConfig.getNbSiegesAPourvoir()));
System.out.println(String.format("Seuil électoral : %5.2f", electionsConfig.getSeuilElectoral()));
System.out.println("Listes candidates----------------");
for (ListeElectorale liste : listes) {
System.out.println(liste);
}
}
// métodos privados ------------------
private static List<String> getErreursForThrowable(Throwable th) {
// se recupera la lista de mensajes de error de la excepción
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// se recupera el mensaje solo si !=null y no está vacío
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// causa siguiente
cause = cause.getCause();
}
return erreurs;
}
}
- líneas 21-31: recuperación de una referencia en la capa [DAO];
- línea 21: se utiliza una sintaxis llamada try_with_resources. Su sintaxis es la siguiente:
try (T ressource=expression) {
// uso excesivo de recursos
...
}
- (continuación)
- el tipo T de la línea 1 debe implementar la interfaz [java.lang.AutoCloseable];
- al salir del bloque de las líneas 1 a 4, el recurso de tipo [java.lang.AutoCloseable] se libera automáticamente, haya habido una excepción o no. Quienes conozcan el lenguaje C# reconocerán aquí un equivalente sintáctico y funcional de la cláusula using (T recurso = expresión);
- líneas 40-49: uso de la capa [DAO] para obtener el contenido de las tablas [CONF] y [LISTES];
- líneas 41-47: se comprueba la caché [electionsconfig]. Esta se ha definido en dos lugares:
- en la clase [ElectionsDaoJdbc]:
@Cacheable("electionsConfig")
public ElectionsConfig getElectionsConfig() {
- (continuación)
- en la clase de configuración [AppConfig]:
@EnableCaching
public class AppConfig {
...
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- línea 59: cierre del contexto de Spring;
- líneas 62-67: visualización de la información obtenida;
Los resultados obtenidos con una capa [DAO] implementada son los siguientes:
- líneas 2-4: muestran la influencia de la caché:
- la consulta 1 dura 80 milisegundos;
- la consulta 2 dura 1 milisegundo;
Cuando la base de datos está desactivada, se obtienen los siguientes resultados:
7.10. Pruebas JUnit de la clase [ElectionsDaoJdbc]
![]() |
![]() |
La clase [Test01] es la siguiente clase de prueba de [JUnit]:
package elections.dao.junit;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import elections.dao.config.AppConfig;
import elections.dao.entities.ElectionsConfig;
import elections.dao.entities.ListeElectorale;
import elections.dao.service.IElectionsDao;
@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {
// capa [DAO]
@Autowired
private IElectionsDao electionsDao;
@Before
public void init() {
// se limpia la tabla [LISTES]
// listas en competencia
ListeElectorale[] listes = electionsDao.getListesElectorales();
// se ponen a 0 los votos y los escaños y se elimina a «false»
int voix = 0;
int sièges = 0;
boolean elimine = false;
for (ListeElectorale liste : listes) {
liste.setVoix(voix);
liste.setSieges(sièges);
liste.setElimine(elimine);
}
// se hacen persistentes estos datos mediante la capa [dao]
electionsDao.setListesElectorales(listes);
}
@Test
public void testElections01() {
...
}
}
- línea 17: la anotación [RunWith], que es una anotación [JUnit] (línea 6), garantiza la integración con Spring a través de la clase [SpringJUnit4ClassRunner];
- línea 16: la anotación [SpringApplicationConfiguration] es una anotación [Spring] (línea 8) que permite designar las clases de configuración de la prueba JUnit. Aquí se designa la clase [AppConfig] utilizada para configurar el proyecto. De esta manera, se dispone de todos los objetos de Spring definidos por esta clase de configuración. Así es como se puede inyectar, en las líneas 21-22, una referencia a la capa [DAO] que se va a probar;
- línea 25: la anotación [Before] designa un método que debe ejecutarse antes de cada prueba;
- líneas 26-41: el método [init] pone a cero los votos y escaños de las listas de la tabla [LISTES] y establece el valor booleano [elimine] en [false];
El único método de prueba es el siguiente:
@Test
public void testElections01() {
System.out.println("testElections01-------------------------------------");
// recuperación de la configuración de las elecciones
ElectionsConfig electionsConfig = electionsDao.getElectionsConfig();
int nbSiegesAPourvoir = electionsConfig.getNbSiegesAPourvoir();
double seuilElectoral = electionsConfig.getSeuilElectoral();
Assert.assertEquals(6, nbSiegesAPourvoir);
Assert.assertEquals(0.05, seuilElectoral, 1E-6);
// listas en contienda
ListeElectorale[] listes = electionsDao.getListesElectorales();
// Visualización de los valores leídos
System.out.println("Nombre de sièges à pourvoir : " + nbSiegesAPourvoir);
System.out.println("Seuil électoral : " + seuilElectoral);
System.out.println("Listes en compétition ---------------------");
for (int i = 0; i < listes.length; i++) {
System.out.println(listes[i]);
}
// se asignan votos y escaños a las listas
int voix = 0;
int sièges = 0;
boolean elimine = false;
for (ListeElectorale liste : listes) {
liste.setVoix(voix);
liste.setSieges(sièges);
liste.setElimine(elimine);
voix += 10;
sièges += 1;
elimine = !elimine;
}
// se guardan estos datos de forma permanente mediante la capa [dao]
electionsDao.setListesElectorales(listes);
// se vuelven a leer los datos
ListeElectorale[] listesElectorales2 = electionsDao.getListesElectorales();
// se verifican los datos leídos
Assert.assertEquals(7, listesElectorales2.length);
voix = 0;
sièges = 0;
elimine = false;
for (ListeElectorale liste : listesElectorales2) {
Assert.assertEquals(voix, liste.getVoix());
Assert.assertEquals(sièges, liste.getSieges());
Assert.assertEquals(elimine, liste.isElimine());
voix += 10;
sièges += 1;
elimine = !elimine;
}
System.out.println("Listes en compétition ---------------------");
for (int i = 0; i < listes.length; i++) {
System.out.println(listes[i]);
}
}
- líneas 5-9: se verifica que se pueda recuperar el contenido de la tabla [CONF];
- líneas 11-19: se muestra el contenido de la tabla [LISTES]. Aquí no hay pruebas, solo una verificación visual;
- líneas 21-35: se modifica en la base de datos la tabla [LISTES] asignando a las listas los valores correspondientes a sus campos [voix, sieges, elimine];
- líneas 37-51: se vuelve a leer la tabla [LISTES] y se verifica que lo obtenido sea igual a lo que se ingresó;
- líneas 52-55: verificación visual;
Los resultados de la consola obtenidos con una capa [DAO] implementada son los siguientes:
- líneas 1-23: registros de Spring Test;
- líneas 25-26: el contenido de la tabla [CONF];
- líneas 27-34: el contenido inicial de la tabla [LISTES];
- líneas 35-42: el contenido de la tabla [LISTES] después de asignar valores a los campos de la tabla [voix, sieges, elimine];
Por otra parte, la prueba JUnit se realiza con éxito:
![]() |
7.11. Creación del archivo [with-dependencies] a partir de la capa [dao]
El proyecto final tiene la siguiente arquitectura:
![]() |
Recordemos la configuración de Maven del proyecto:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-dao-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
<!-- biblioteca jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Prueba de Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Registro de Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- plugins -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>config.AppConfig</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<!-- para instalar el artefacto del proyecto en el repositorio local de Maven -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
![]() |
Vamos a generar un único archivo .jar que contendrá las clases de todos los archivos .jar anteriores, además de las del proyecto de la capa [DAO]. Esto se logra mediante una modificación en el archivo [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.elections</groupId>
<artifactId>elections-jdbc-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.2.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
...
</dependencies>
<!-- plugins -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>console.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>
- líneas 25-37: configuran un complemento de Maven para generar el archivo JAR;
- línea 15: indica la versión de Java que se debe usar para la compilación;
Una vez realizada esta modificación, se puede generar el archivo JAR de la siguiente manera [1-10]:
![]() |
![]() |
- en [5], selecciona la carpeta del proyecto utilizando el botón [6];
- en [7], asigna un nombre a la configuración de ejecución;
- en [8], ingrese la lista de objetivos de Maven que se deben ejecutar:
- [clean]: vacía la carpeta [target] del proyecto en la que se colocará el archivo JAR generado;
- [compile]: compila el proyecto;
- [assembly:single]: genera un único archivo JAR que incluye todas las clases del proyecto y sus dependencias;
![]() |
- En [9-10], asegúrate de que tengas un JDK (Java Development Kit) y no un JRE (Java Runtime Environment). La diferencia es que el JDK incluye un compilador, mientras que el JRE no lo tiene. Si no tienes un JDK, debes agregar uno en [10] junto con [11]. Para ello, sigue el procedimiento descrito en el párrafo 3.1;
![]() |
- en [17], ejecute la configuración de ejecución;
- en [13], el archivo comprimido generado;
Este archivo se puede abrir con un programa de descompresión:
![]() |
7.12. Prueba del archivo de la capa [DAO]
Creemos un proyecto estándar de Eclipse (no Maven) [1]:
![]() |
Copiemos y peguemos el paquete [dao.console] del proyecto [elections-dao-jdbc-01] en el proyecto [elections-dao-jdbc-02] [2]. Aparecen numerosos errores porque la clase [Main] hace referencia a clases que no se encuentran en su [Classpath]. Vamos a modificar este último.
Primero creamos una carpeta [lib] en el nuevo proyecto:
![]() |
![]() |
En [9], colocamos el archivo creado en el paso anterior en la carpeta [lib] y luego modificamos la ruta de compilación del proyecto:
![]() |
![]() |
![]() |
- por [18]; hemos importado el archivo de la capa [DAO] creado anteriormente;
![]() |
- en [19], el proyecto ya no presenta errores;
Ahora se puede ejecutar la clase [Main]. Se obtienen los mismos resultados que anteriormente.







































