7. [TD]: Implementazione del livello [DAO] del TD con il API JDBC
Parole chiave: database relazionali, API, JDBC, SQLException.
Torniamo all’architettura a livelli della nostra applicazione:
![]() |
I dati necessari per le elezioni sono memorizzati in un database MySQL [dbelections]
7.1. Support
![]() |
La cartella [support / chap-07] [1] contiene:
- i progetti Eclipse di questo capitolo [2];
- lo script SQL per la creazione del database MySQL, [dbelections], [3];
7.2. Il database [dbelections]
Compito da svolgere: creare il database MySQL e [dblelections] seguendo la procedura descritta al paragrafo 6.4.2.
Il database [dbelections] è un database MySQL con due tabelle:
![]() |
La tabella [conf] contiene le informazioni di configurazione dell’elezione:
![]() |
- [id]: chiave primaria autoincrementale;
- [version]: numero di versione del record – in questo caso può essere ignorato;
- [sap]: numero di seggi da assegnare;
- [seuilelectoral]: la soglia al di sotto della quale un elenco viene eliminato;
Il suo contenuto è il seguente:
![]() |
La tabella [listes] contiene le liste candidate alle elezioni:
![]() |
- [id]: chiave primaria autoincrementale;
- [version]: numero di versione del record – in questo caso può essere ignorato;
- [nom]: nome della lista;
- [voix]: voce della lista - è nota solo dopo l'inserimento da parte dell'utente nel livello [présentation];
- [sieges]: numero di seggi ottenuti - è noto solo dopo il calcolo del livello [métier];
- [elimine]: pari a 1 se la lista è stata eliminata, pari a 0 in caso contrario - è noto solo dopo il calcolo del livello [métier];
Il contenuto della tabella [listes] è il seguente:
![]() |
Lo script SQL per generare la base [dbelections] si chiama [dbelections.sql] e si trova sul server. Il suo codice è il seguente:
-- phpMyAdmin SQL Dump
-- versione 4.0.4
-- http://www.phpmyadmin.net
--
-- Cliente: localhost
-- Generato il: mercoledì 11 marzo 2015 alle 12:20
-- Versione del server: 5.6.12-log
-- Versione di 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 */;
--
-- Database: `dbelections`
--
CREATE DATABASE IF NOT EXISTS `dbelections` DEFAULT CHARACTER SET utf8 COLLATE utf8_swedish_ci;
USE `dbelections`;
-- --------------------------------------------------------
--
-- Struttura della tabella `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 ;
--
-- Contenuto della tabella `conf`
--
INSERT INTO `conf` (`id`, `version`, `sap`, `seuilelectoral`) VALUES
(1, 1, 6, 0.05);
-- --------------------------------------------------------
--
-- Struttura della tabella `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 ;
--
-- Contenuto della tabella `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. Il progetto Eclipse
![]() |
Il progetto Eclipse del livello [DAO] sarà il seguente:
![]() |
- il pacchetto [elections.dao.entities] contiene gli oggetti gestiti dal livello [DAO];
- il pacchetto [elections.dao.service] contiene l'implementazione del livello [DAO];
- il pacchetto [elections.dao.config] contiene la configurazione del livello [DAO]
- il pacchetto [elections.dao.junit] contiene una classe di test JUnit del progetto;
- il pacchetto [elections.dao.console] contiene una classe di test eseguibile;
7.4. Configurazione del progetto Maven
![]() |
Il file [pom.xml] che configura il progetto Maven è il seguente:
<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>
<!-- libreria 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>
<!-- Test di Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Registrazione in Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- plugin -->
<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>
<!-- per l'installazione dell'artefatto del progetto nel repository Maven locale -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
Questo file è analogo a quello descritto al paragrafo 6.5.1. Sono state apportate le seguenti modifiche:
- righe 8-12: è stato definito un progetto Maven padre. Il progetto [spring-boot-starter-parent] (riga 10) definisce un numero molto elevato di dipendenze con le relative versioni. Quando se ne utilizza una (righe 19-57), non è necessario specificarne la versione, poiché questa è già definita nel progetto Maven padre;
- righe 40-51: dipendenze necessarie per la classe di test del progetto. Queste dipendenze hanno l’attributo [<scope>test</scope>], il che significa che sono necessarie solo per le classi della cartella [src / test / java]. Queste dipendenze non saranno incluse nell’archivio finale del progetto;
- righe 53-56: la libreria [spring-boot-starter-logging] verrà utilizzata da Spring per generare log sullo schermo;
- righe 14-17: proprietà di configurazione Maven:
- riga 15: indica che i file sorgente sono scritti in UTF-8;
- riga 16: indica che il compilatore da utilizzare deve essere di livello 1.8;
7.5. Le entità del livello [DAO]
![]() |
- [ElectionsConfig] è il modello oggetto associato a una riga della tabella [CONF];
- [ListeElectorale] è il modello oggetto associato a una riga della tabella [LISTES];
- [AbstactEntity] è la classe padre delle due classi precedenti. Essa raggruppa i campi [id, version] comuni alle due classi;
- [ElectionsException] è una classe di eccezione;
7.5.1. La classe [ElectionsException]
![]() |
La classe [ElectionsException] è stata descritta al paragrafo 4.3. Ne riportiamo nuovamente il codice:
package ...;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// classe di eccezione per l'applicazione Elezioni
// l'eccezione è non controllata
public class ElectionsException extends RuntimeException implements Serializable {
// seriale ID
private static final long serialVersionUID = 1L;
// campi locali
private int code;
private List<String> erreurs;
// costruttori
public ElectionsException() {
super();
}
public ElectionsException(int code, Throwable e) {
// genitore
super(e);
// locale
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message, Throwable e) {
// genitore
super(message,e);
// locale
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message) {
// genitore
super(message);
// locale
this.code = code;
List<String> erreurs = new ArrayList<>();
erreurs.add(message);
this.erreurs = erreurs;
}
public ElectionsException(int code, List<String> erreurs) {
// genitore
super();
// locale
this.code = code;
this.erreurs = erreurs;
}
// elenco dei messaggi di errore di un'eccezione
private List<String> getErreursForException(Throwable th) {
// si recupera l'elenco dei messaggi di errore dell'eccezione
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// si recupera il messaggio solo se !=null e non è vuoto
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// causa seguente
cause = cause.getCause();
}
return erreurs;
}
// getter e setter
...
}
Un oggetto [ElectionsException] è caratterizzato da due informazioni:
- riga 16: un codice di errore;
- riga 17: un elenco di messaggi di errore associati alla pila delle eccezioni che si sono verificate;
- la classe ha 5 costruttori (righe 20, 24, 32, 40, 50);
- righe 59-76: il metodo [getErreursForException] consente di recuperare i messaggi di errore dallo stack delle eccezioni;
7.5.2. La classe [AbstractEntity]
![]() |
La classe [AbstractEntity] è la seguente:
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;
// campi
protected Long id;
protected Long version;
// costruttori
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;
}
}
// getter e setter
...
}
Essa memorizza i campi [ID, NOM] delle righe delle tabelle [CONF] e [LISTES] (righe 8-9).
- riga 8: la classe presenta l'attributo [Abstract], che indica che non può essere istanziata. Può solo essere derivata;
- righe 26-33: firma jSON dell'oggetto;
- riga 28: viene restituita la stringa jSON da [this]. Se al momento dell'esecuzione [this] rappresenta un oggetto derivato da [AbstactEntity], si ottiene la stringa jSON dell'oggetto derivato. Le classi derivate non dovranno quindi definire un metodo [toString]. È sufficiente quello della classe padre;
7.5.3. La classe [ElectionsConfig]
![]() |
La classe [ElectionsConfig] è la seguente:
package elections.dao.entities;
public class ElectionsConfig extends AbstractEntity {
private static final long serialVersionUID = 1L;
// campi
private int nbSiegesAPourvoir;
private double seuilElectoral;
// costruttori
public ElectionsConfig() {
}
public ElectionsConfig(Long id, Long version, int nbSiegesAPourvoir, double seuilElectoral) {
// genitore
super(id, version);
// campi locali
this.nbSiegesAPourvoir = nbSiegesAPourvoir;
this.seuilElectoral = seuilElectoral;
}
// getter e setter
...
}
- riga 4: la classe estende la classe [AbstractEntity];
- righe 8-9: memorizzano le informazioni delle colonne [SAP, SEUILELECTORAL] della tabella [CONF];
7.5.4. La classe [ListeElectorale]
![]() |
La classe [ListeElectorale] è la seguente:
package elections.dao.entities;
public class ListeElectorale extends AbstractEntity {
// campi
private String nom;
private int voix;
private int sieges;
private boolean elimine;
// costruttori
public ListeElectorale() {
}
public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
// genitore
super();
// campi locali
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
public ListeElectorale(Long id, Long version, String nom, int voix, int sieges, boolean elimine) {
// genitore
super(id, version);
// campi locali
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
// metodi privati
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");
}
}
// getter e setter
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;
}
}
- riga 3: la classe estende la classe [AbstractEntity];
- righe 6-9: la classe memorizza le colonne [NOM, VOIX, SIEGES, ELIMINE] della tabella [LISTES];
7.6. Configurazione Spring del livello [DAO]
![]() |
![]() |
La classe [AppConfig] è una classe di configurazione Spring che configura l'accesso al database nel modo seguente:
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 {
// costanti
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() {
// fonte dati TomcatJdbc
DataSource dataSource = new DataSource();
// configurazione dell'accesso JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// una connessione inizialmente aperta
dataSource.setInitialSize(1);
// risultato
return dataSource;
}
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- righe 25-38: l'accesso a BD avverrà tramite una fonte di dati [tomcat-jdbc]. Questo tipo di fonte è stato utilizzato e spiegato nel paragrafo 6.5;
- righe 17-23: un insieme di costanti statiche accessibili a tutte le classi del progetto;
- riga 13: l'annotazione [@Configuration] rende la classe [AppConfig] una classe di configurazione Spring;
- riga 11: l'annotazione [@ComponentScan] indica i pacchetti in cui è possibile trovare oggetti Spring. In questo caso, definiremo un oggetto Spring nel pacchetto [dao]. L'annotazione [@ComponentScan] rende la classe una classe di configurazione, evitando di dover inserire l'annotazione [@Configuration];
- la riga 12 attiva la gestione di una cache. Il principio è il seguente:
- si applica l’annotazione [@Cacheable('nom_du_cache')] a un metodo M. Il ‘nome_della-cache’ è il nome utilizzato alla riga 41;
- quando il metodo M viene chiamato per la prima volta, i suoi risultati vengono restituiti e inseriti anche nella cache indicata dall’annotazione;
- quando il metodo M viene richiamato nelle volte successive con gli stessi parametri della prima volta, non viene eseguito e Spring si limita a restituire i valori memorizzati nella cache;
7.7. Configurazione dei log
![]() |
Le librerie di log sono definite dalla seguente dipendenza in [pom.xml]:
<!-- Registrazione in Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Questa dipendenza include le seguenti librerie:
![]() |
La libreria [logback] si occuperà dei log. È configurata da due file:
- [logback.xml] per il ramo principale del codice;
- [logback-test.xml] per il ramo di test del codice. In assenza di quest'ultimo, viene utilizzato il file precedente;
Entrambi i file devono trovarsi nella cartella [Classpath] del progetto. Per questo motivo, sono collocati nella cartella:
- [src / main / ressources] per il ramo principale del codice;
- [src / test / ressources] per il ramo di test del codice;
Il contenuto dei file è lo stesso in entrambi i casi:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- agli encoder viene assegnato per impostazione predefinita il tipo
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- controllo del livello dei log -->
<root level="info"> <!-- info, debug, warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
Tutto avviene alla riga 12, dove si imposta il livello di dettaglio desiderato:
- [debug]: il livello più dettagliato;
- [off]: nessun log;
- [info]: livello normale dei log;
- [warn]: come il livello [info] più i messaggi di avviso (warning). Questi messaggi indicano la possibilità di un errore;
Passare alla modalità [debug] non appena compaiono errori durante l'esecuzione.
7.8. Implementazione del livello [DAO]
![]() |
![]() |
L'interfaccia [IDao] del livello [DAO] è la seguente:
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 {
// Configurazione delle elezioni
public ElectionsConfig getElectionsConfig();
// le liste in lizza
public ListeElectorale[] getListesElectorales();
// salvataggio dei risultati elettorali
public void setListesElectorales(ListeElectorale[] listesElectorales);
}
Lo scheletro della classe [ElectionsDaoJdbc] che implementa il livello [dao] con un database sarà il seguente:
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")
// recupero della configurazione elettorale
public ElectionsConfig getElectionsConfig() {
throw new RuntimeException("[getElectionsConfig] not yet implemented");
}
// ottenimento delle liste
public ListeElectorale[] getListesElectorales() {
throw new RuntimeException("[getListesElectorales] not yet implemented");
}
// modifica delle liste [voix, sieges, elimine]
public void setListesElectorales(ListeElectorale[] listesElectorales) {
throw new RuntimeException("[setListesElectorales] not yet implemented");
}
// -------------------- metodi privati
// gestione del finally
private ElectionsException doFinally(int code, ResultSet rs, PreparedStatement ps, Connection connexion) {
// chiusura ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// chiusura [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// chiudere la connessione
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// viene restituito un [ElectionsException]
return new ElectionsException(code, e3);
}
}
// nessuna eccezione
return null;
}
// gestione del catch
private ElectionsException doCatchException(int code1, int code2, Connection connexion, Throwable th) {
// viene generato un [ElectionsException]
ElectionsException ex1 = new ElectionsException(code1, th);
// annullamento della transazione
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
}
// si restituisce l'eccezione
return ex1;
}
}
- riga 24: l'annotazione [@Cacheable] è un'annotazione Spring che richiede di memorizzare in cache i risultati del metodo [getElectionsConfig]. È possibile farlo in questo caso perché il contenuto della tabella [CONF] non cambia mai. Non sarebbe possibile applicare questa annotazione al metodo [getListesElectorales] poiché il contenuto della tabella [LISTES] cambia nel tempo;
- i metodi [doCatchException] e [doFinally] restituiscono un tipo [ElectionsException]. Il metodo [doFinally] restituisce un puntatore null se il rilascio delle risorse è avvenuto senza errori;
Compito da svolgere: scrivere la classe [ElectionDaoJdbc] ispirandosi alla classe [IntroJdbc02] studiata nel paragrafo 6.5.2.
7.9. La classe di test [Main]
![]() |
![]() |
La classe [Main] è la seguente:
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 {
// fonte dati
private static IElectionsDao dao;
public static void main(String[] args) {
// si recupera il riferimento al livello [DAO] dopo aver istanziato il contesto Spring
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class)) {
// recupero della fonte dati
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);
}
// fine
return;
}
// lettura di BD
ElectionsConfig electionsConfig = null;
ListeElectorale[] listes;
try {
// contenuto delle due tabelle
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);
}
// fine
return;
}
// tutto è andato bene - visualizzazione
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);
}
}
// metodi privati ------------------
private static List<String> getErreursForThrowable(Throwable th) {
// si recupera l'elenco dei messaggi di errore dell'eccezione
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// si recupera il messaggio solo se !=null e non è vuoto
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// causa successiva
cause = cause.getCause();
}
return erreurs;
}
}
- righe 21-31: recupero di un riferimento sul livello [DAO];
- riga 21: si utilizza una sintassi denominata try_with_resources. La sua sintassi è la seguente:
try (T ressource=expression) {
// utilizzo delle risorse
...
}
- (continua)
- il tipo T della riga 1 deve implementare l'interfaccia [java.lang.AutoCloseable];
- all'uscita dal blocco delle righe 1-4, la risorsa di tipo [java.lang.AutoCloseable] viene automaticamente liberata, indipendentemente dal fatto che si sia verificata o meno un'eccezione. Chi conosce il linguaggio C# riconoscerà in questo un equivalente sintattico e funzionale della clausola using (T risorsa=espressione);
- righe 40-49: utilizzo del livello [DAO] per ottenere il contenuto delle tabelle [CONF] e [LISTES];
- righe 41-47: si verifica la cache [electionsconfig]. Questa è stata definita in due punti:
- nella classe [ElectionsDaoJdbc]:
@Cacheable("electionsConfig")
public ElectionsConfig getElectionsConfig() {
- (continua)
- nella classe di configurazione [AppConfig]:
@EnableCaching
public class AppConfig {
...
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- riga 59: chiusura del contesto Spring;
- righe 62-67: visualizzazione delle informazioni ottenute;
I risultati ottenuti con un livello [DAO] implementato sono i seguenti:
- righe 2-4: mostrano l'influenza della cache:
- la richiesta 1 dura 80 millisecondi;
- la richiesta 2 dura 1 millisecondo;
Quando il database è disattivato, si ottengono i seguenti risultati:
7.10. Test JUnit della classe [ElectionsDaoJdbc]
![]() |
![]() |
La classe [Test01] è la classe di test successiva alla [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 {
// livello [DAO]
@Autowired
private IElectionsDao electionsDao;
@Before
public void init() {
// si pulisce la tabella [LISTES]
// liste in competizione
ListeElectorale[] listes = electionsDao.getListesElectorales();
// si azzerano i voti e i seggi e si imposta «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);
}
// si rendono persistenti questi dati tramite il livello [dao]
electionsDao.setListesElectorales(listes);
}
@Test
public void testElections01() {
...
}
}
- riga 17: l'annotazione [RunWith], che è un'annotazione [JUnit] (riga 6), garantisce l'integrazione con Spring tramite la classe [SpringJUnit4ClassRunner];
- riga 16: l'annotazione [SpringApplicationConfiguration] è un'annotazione [Spring] (riga 8) che consente di indicare le classi di configurazione del test JUnit. Qui si indica la classe [AppConfig] utilizzata per configurare il progetto. Si dispone quindi di tutti gli oggetti Spring definiti da questa classe di configurazione. In questo modo è possibile iniettare, alle righe 21-22, un riferimento al livello [DAO] che verrà testato;
- riga 25: l’annotazione [Before] indica un metodo che deve essere eseguito prima di ogni test;
- righe 26-41: il metodo [init] azzera i voti e i seggi delle liste presenti nella tabella [LISTES] e imposta il valore booleano [elimine] su [false];
L'unico metodo di test è il seguente:
@Test
public void testElections01() {
System.out.println("testElections01-------------------------------------");
// recupero della configurazione delle elezioni
ElectionsConfig electionsConfig = electionsDao.getElectionsConfig();
int nbSiegesAPourvoir = electionsConfig.getNbSiegesAPourvoir();
double seuilElectoral = electionsConfig.getSeuilElectoral();
Assert.assertEquals(6, nbSiegesAPourvoir);
Assert.assertEquals(0.05, seuilElectoral, 1E-6);
// liste in lizza
ListeElectorale[] listes = electionsDao.getListesElectorales();
// visualizzazione dei valori letti
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]);
}
// assegnazione di voti e seggi alle liste
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;
}
// si rendono permanenti questi dati tramite il livello [dao]
electionsDao.setListesElectorales(listes);
// si rileggono i dati
ListeElectorale[] listesElectorales2 = electionsDao.getListesElectorales();
// si verificano i dati letti
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]);
}
}
- righe 5-9: ci si assicura di poter recuperare il contenuto della tabella [CONF];
- righe 11-19: si visualizza il contenuto della tabella [LISTES]. Qui non vengono effettuati test, ma solo un controllo visivo;
- righe 21-35: si modifica nella base di dati la tabella [LISTES] assegnando alle liste i valori relativi ai campi [voix, sieges, elimine];
- righe 37-51: si rilegge la tabella [LISTES] e si verifica che il risultato ottenuto corrisponda effettivamente a quanto inserito;
- righe 52-55: verifica visiva;
I risultati in console ottenuti con un livello [DAO] implementato sono i seguenti:
- righe 1-23: log di Spring Test;
- righe 25-26: il contenuto della tabella [CONF];
- righe 27-34: il contenuto iniziale della tabella [LISTES];
- righe 35-42: il contenuto della tabella [LISTES] dopo l'assegnazione dei valori ai campi [voix, sieges, elimine];
Inoltre, il test JUnit ha esito positivo:
![]() |
7.11. Creazione dell'archivio [with-dependencies] dal livello [dao]
Il progetto finale presenta la seguente architettura:
![]() |
Ricordiamo la configurazione Maven del progetto:
<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>
<!-- libreria 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>
<!-- Test di Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Registrazione in Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- plugin -->
<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>
<!-- per l'installazione dell'artifact del progetto nel repository Maven locale -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
![]() |
Genereremo un unico file jar che conterrà le classi di tutti i file jar sopra indicati più quelle del progetto del livello [DAO]. Ciò avviene apportando una modifica al file [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>
<!-- plugin -->
<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>
- righe 25-37: configurano un plugin Maven per generare il file JAR;
- riga 15: indica la versione di Java da utilizzare per la compilazione;
Una volta apportata questa modifica, è possibile generare il file jar nel modo seguente [1-10]:
![]() |
![]() |
- in [5], selezionare la cartella del progetto utilizzando il pulsante [6];
- in [7], assegnare un nome alla configurazione di esecuzione;
- in [8], inserire l'elenco degli obiettivi Maven da eseguire:
- [clean]: svuota la cartella [target] del progetto in cui verrà inserito il file jar generato;
- [compile]: compila il progetto;
- [assembly:single]: genera un unico file JAR contenente tutte le classi del progetto e delle sue dipendenze;
![]() |
- in [9-10], assicurarsi di disporre di un JDK (Java Development Kit) e non di un JRE (Java Runtime Environment). La differenza sta nel fatto che il JDK include un compilatore, mentre il JRE no. Se non disponete di un JDK, dovete aggiungerne uno in [10] insieme a [11]. A tal fine, seguire la procedura descritta al paragrafo 3.1;
![]() |
- in [17], eseguite la configurazione di esecuzione;
- in [13], l'archivio generato;
È possibile aprire questo archivio con un programma di decompressione:
![]() |
7.12. Test dell'archivio del livello [DAO]
Creiamo un progetto Eclipse standard (non Maven) [1]:
![]() |
Copiamo e incolliamo il pacchetto [dao.console] dal progetto [elections-dao-jdbc-01] nel progetto [elections-dao-jdbc-02] [2]. Vengono visualizzati numerosi errori poiché la classe [Main] fa riferimento a classi che non sono presenti nel suo [Classpath]. Modificheremo quest’ultimo.
Per prima cosa creiamo una cartella [lib] nel nuovo progetto:
![]() |
![]() |
In [9], inseriamo l'archivio creato nel passaggio precedente nella cartella [lib], quindi modifichiamo il Build Path del progetto:
![]() |
![]() |
![]() |
- in [18], abbiamo importato l’archivio del livello [DAO] creato in precedenza;
![]() |
- in [19], il progetto non presenta più errori;
È quindi possibile eseguire la classe [Main]. Si ottengono gli stessi risultati di prima.







































