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 por capas de nuestra aplicación:
![]() |
Los datos necesarios para las elecciones 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 apartado 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 autoincrementada;
- [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 descarta una lista;
Su contenido es el siguiente:
![]() |
La tabla [listes] contiene las listas de candidatos de las elecciones:
![]() |
- [id]: clave primaria autoincrementada;
- [version]: número de versión del registro; aquí se puede omitir;
- [nom]: nombre de la lista;
- [voix]: voz de la lista —solo se conoce tras la introducción de datos por parte del usuario en la capa [présentation]—;
- [sieges]: número de escaños obtenidos —solo se conoce tras el cálculo de 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 `listes`
--
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 `listes`
--
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>
<!-- complementos -->
<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 la instalación del 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 apartado 6.5.1. Se le han realizado las siguientes modificaciones:
- líneas 8-12: se ha definido un proyecto Maven principal. El proyecto [spring-boot-starter-parent] (línea 10) define un gran número de dependencias con sus respectivas versiones. Cuando se utiliza una de ellas (líneas 19-57), no es necesario especificar la versión, ya que esta queda definida en el proyecto Maven principal;
- 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 el compilador que se debe utilizar debe ser 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 objetos asociado a una fila de la tabla [LISTES];
- [AbstactEntity] es la clase padre de las dos clases anteriores. Agrupa los campos [id, version] comunes a ambas clases;
- [ElectionsException] es una clase de excepción;
7.5.1. La clase [ElectionsException]
![]() |
La clase [ElectionsException] se ha descrito en el apartado 4.3. A continuación, volvemos a indicar su código:
package ...;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// Clase de excepción para la aplicación «Elections»
// 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 !=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;
}
// 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 líneas de las tablas [CONF] y [LISTES] (líneas 8-9).
- línea 8: la clase tiene el atributo [Abstract], 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 este modo, las clases derivadas no tendrán que definir un método [toString]. Basta con el de la clase padre;
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 hereda de 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 hereda de 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 de 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 ha utilizado y explicado en el apartado 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 la clase [AppConfig] en una clase de configuración de Spring;
- línea 11: la anotación [@ComponentScan] indica los paquetes en los que se pueden encontrar objetos de Spring. En este caso, vamos a definir un objeto de Spring en el paquete [dao]. La anotación [@ComponentScan] convierte 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 se invoca el método M las veces siguientes con los mismos parámetros que la primera vez, no se ejecuta y Spring se limita a devolver 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 pruebas del código. Si no existe este archivo, se utiliza el anterior;
Estos dos archivos deben encontrarse en el directorio [Classpath] del proyecto. Por este motivo, se colocan en la carpeta:
- [src / main / ressources] para la rama principal del código;
- [src / test / ressources] para la rama de pruebas 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 se decide 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 los mensajes de advertencia (warning) añadidos. Estos mensajes indican la posibilidad de que se produzca 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 que se presentan a las elecciones
public ListeElectorale[] getListesElectorales();
// guardado de los resultados de las elecciones
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 excepciones
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 hacerlo 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 ha realizado sin errores;
Tarea: escribe la clase [ElectionDaoJdbc] inspirándote en la clase [IntroJdbc02] estudiada en el apartado 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] tras 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 ha ido 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 denominada 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-4, el recurso de tipo [java.lang.AutoCloseable] se libera automáticamente, haya habido o no una excepción. 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 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 tarda 1 milisegundo;
Cuando la base de datos está desconectada, 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 liza
ListeElectorale[] listes = electionsDao.getListesElectorales();
// se ponen a 0 los votos y los escaños y se eliminan 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 este modo, se dispone de todos los objetos 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 someter a prueba;
- 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 liza
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 comprueban 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 comprueba que se puede 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 comprobació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 comprueba que el resultado obtenido coincide con lo que se ha introducido;
- líneas 52-55: comprobació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] tras asignar valores a los campos de [voix, sieges, elimine];
Por otra parte, la prueba JUnit se ha superado:
![]() |
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>
<!-- complementos -->
<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]. Para ello, hay que realizar 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 plugin de Maven para generar el archivo JAR;
- línea 15: indica la versión de Java que se debe utilizar para la compilación;
Una vez realizada esta modificación, se puede generar el archivo jar de la siguiente manera [1-10]:
![]() |
![]() |
- en [5], seleccionar la carpeta del proyecto utilizando el botón [6];
- en [7], asigna un nombre a la configuración de ejecución;
- en [8], introduce la lista de objetivos de Maven que se van a 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], comprueba que tienes 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 incluye. Si no dispone de un JDK, deberá añadir uno en [10] junto con [11]. Para ello, siga el procedimiento descrito en el apartado 3.1;
![]() |
- en [17], ejecute la configuración de ejecución;
- en [13], el archivo comprimido generado;
Este archivo comprimido 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]:
![]() |
Copiamos y pegamos 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.
En primer lugar, creamos una carpeta [lib] en el nuevo proyecto:
![]() |
![]() |
En [9], colocamos el archivo creado en el paso anterior en la carpeta [lib] y, a continuación, 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;
A continuación, se puede ejecutar la clase [Main]. Se obtienen los mismos resultados que anteriormente.







































