7. [TD]: Implementatie van de laag [DAO] van TD met API JDBC
Trefwoorden: relationele databases, API, JDBC, SQLException.
Laten we nog eens terugkomen op de gelaagde architectuur van onze applicatie:
![]() |
De gegevens die nodig zijn voor de verkiezing worden opgeslagen in een database MySQL [dbelections]
7.1. Support
![]() |
De map [support / chap-07] [1] bevat:
- de Eclipse-projecten van dit hoofdstuk [2];
- het script SQL voor het aanmaken van de database MySQL, [dbelections] en [3];
7.2. De database [dbelections]
Te doen: maak de database MySQL en [dblelections] aan volgens de procedure beschreven in paragraaf 6.4.2.
De database [dbelections] is een database MySQL met twee tabellen:
![]() |
De tabel [conf] bevat de configuratiegegevens van de verkiezing:
![]() |
- [id]: automatisch oplopende primaire sleutel;
- [version]: versienummer van het record – kan hier worden genegeerd;
- [sap]: aantal te vervullen zetels;
- [seuilelectoral]: de drempel waaronder een lijst wordt verwijderd;
De inhoud is als volgt:
![]() |
De tabel [listes] bevat de kandidatenlijsten voor de verkiezing:
![]() |
- [id]: automatisch oplopende primaire sleutel;
- [version]: versienummer van het record – kan hier worden genegeerd;
- [nom]: naam van de lijst;
- [voix]: stem van de lijst – is pas bekend nadat de gebruiker deze heeft ingevoerd in laag [présentation];
- [sieges]: aantal behaalde zetels – is pas bekend na berekening van de laag [métier];
- [elimine]: 1 als de lijst is uitgeschakeld, 0 anders – wordt pas bekend na berekening van de laag [métier];
De inhoud van de tabel [listes] is als volgt:
![]() |
Het script SQL voor het genereren van de database [dbelections] heet [dbelections.sql] en staat op de server. De code ervan is als volgt:
-- phpMyAdmin SQL Dump
-- versie 4.0.4
-- http://www.phpmyadmin.net
--
-- Client: localhost
-- Gegenereerd op: wo 11 maart 2015 om 12:20
-- Serverversie: 5.6.12-log
-- Versie van 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`;
-- --------------------------------------------------------
--
-- Structuur van de tabel `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 ;
--
-- Inhoud van de tabel `conf`
--
INSERT INTO `conf` (`id`, `version`, `sap`, `seuilelectoral`) VALUES
(1, 1, 6, 0.05);
-- --------------------------------------------------------
--
-- Structuur van de tabel `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 ;
--
-- Inhoud van de tabel `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. Het Eclipse-project
![]() |
Het Eclipse-project van de laag [DAO] ziet er als volgt uit:
![]() |
- het pakket [elections.dao.entities] bevat de objecten die door de laag [DAO] worden beheerd;
- het pakket [elections.dao.service] bevat de implementatie van de laag [DAO];
- het pakket [elections.dao.config] bevat de configuratie van de laag [DAO]
- het pakket [elections.dao.junit] bevat een testklasse JUnit van het project;
- het pakket [elections.dao.console] bevat een uitvoerbare testklasse;
7.4. Maven-projectconfiguratie
![]() |
Het bestand [pom.xml] dat het Maven-project configureert, is als volgt:
<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>
<!-- bibliotheek 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>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot-logging -->
<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>
<!-- voor het installeren van het projectartefact in de lokale Maven-repository -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
Dit bestand is vergelijkbaar met het bestand dat in paragraaf 6.5.1 wordt beschreven. Er zijn de volgende wijzigingen aangebracht:
- regels 8-12: er is een bovenliggend Maven-project gedefinieerd. Het project [spring-boot-starter-parent] (regel 10) definieert een zeer groot aantal afhankelijkheden met hun versies. Wanneer een van deze afhankelijkheden wordt gebruikt (regels 19-57), is het niet nodig de versie ervan te specificeren, aangezien deze is gedefinieerd in het bovenliggende Maven-project;
- regels 40-51: afhankelijkheden die nodig zijn voor de testklasse van het project. Deze afhankelijkheden hebben het attribuut [<scope>test</scope>], wat betekent dat ze alleen nodig zijn voor de klassen in de map [src / test / java]. Deze afhankelijkheden worden niet opgenomen in het uiteindelijke archief van het project;
- regels 53-56: de bibliotheek [spring-boot-starter-logging] wordt door Spring gebruikt om logberichten op het scherm weer te geven;
- regels 14-17: Maven-configuratie-eigenschappen:
- regel 15: geeft aan dat de bronbestanden zijn geschreven in UTF-8;
- regel 16: geeft aan dat de te gebruiken compiler versie 1.8 moet zijn;
7.5. De entiteiten van de [DAO]-laag
![]() |
- [ElectionsConfig] is het objectmodel dat gekoppeld is aan een rij in de tabel [CONF];
- [ListeElectorale] is het objectmodel dat gekoppeld is aan een rij in de tabel [LISTES];
- [AbstactEntity] is de bovenliggende klasse van de twee voorgaande klassen. Deze klasse bundelt de velden van [id, version] die beide klassen gemeen hebben;
- [ElectionsException] is een uitzonderingsklasse;
7.5.1. De klasse [ElectionsException]
![]() |
De klasse [ElectionsException] is beschreven in paragraaf 4.3. We geven de code ervan nogmaals weer:
package ...;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// uitzonderingsklasse voor de applicatie 'Elections'
// de uitzondering is ongecontroleerd
public class ElectionsException extends RuntimeException implements Serializable {
// serial ID
private static final long serialVersionUID = 1L;
// lokale velden
private int code;
private List<String> erreurs;
// constructors
public ElectionsException() {
super();
}
public ElectionsException(int code, Throwable e) {
// bovenliggend
super(e);
// lokaal
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message, Throwable e) {
// bovenliggend
super(message,e);
// lokaal
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message) {
// bovenliggend
super(message);
// lokaal
this.code = code;
List<String> erreurs = new ArrayList<>();
erreurs.add(message);
this.erreurs = erreurs;
}
public ElectionsException(int code, List<String> erreurs) {
// bovenliggend
super();
// lokaal
this.code = code;
this.erreurs = erreurs;
}
// lijst met foutmeldingen van een uitzondering
private List<String> getErreursForException(Throwable th) {
// de lijst met foutmeldingen van de uitzondering wordt opgehaald
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// de melding wordt alleen opgehaald als deze !=null is en niet leeg is
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// volgende oorzaak
cause = cause.getCause();
}
return erreurs;
}
// getters en setters
...
}
Een object [ElectionsException] wordt gekenmerkt door twee gegevens:
- regel 16: een foutcode;
- regel 17: een lijst met foutmeldingen die verband houden met de stapel uitzonderingen die zich hebben voorgedaan;
- de klasse heeft 5 constructors (regels 20, 24, 32, 40, 50);
- regels 59-76: met de methode [getErreursForException] kunnen de foutmeldingen uit de uitzonderingsstack worden opgehaald;
7.5.2. De klasse [AbstractEntity]
![]() |
De klasse [AbstractEntity] ziet er als volgt uit:
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;
// velden
protected Long id;
protected Long version;
// constructors
public AbstractEntity() {
}
public AbstractEntity(Long id, Long version) {
this.id = id;
this.version = version;
}
// signatuur
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
// getters en setters
...
}
Deze slaat de velden [ID, NOM] van de rijen in de tabellen [CONF] en [LISTES] op (rijen 8-9).
- regel 8: de klasse heeft het attribuut [Abstract], wat aangeeft dat er geen instantie van kan worden gemaakt. De klasse kan alleen worden afgeleid;
- regels 26-33: handtekening jSON van het object;
- regel 28: de tekenreeks jSON van [this] wordt geretourneerd. Als [this] op het moment van uitvoering een van [AbstactEntity] afgeleid object vertegenwoordigt, wordt de tekenreeks jSON van het afgeleide object verkregen. Afgeleide klassen hoeven dus geen methode [toString] te definiëren. Die van de bovenliggende klasse is voldoende;
7.5.3. De klasse [ElectionsConfig]
![]() |
De klasse [ElectionsConfig] is als volgt:
package elections.dao.entities;
public class ElectionsConfig extends AbstractEntity {
private static final long serialVersionUID = 1L;
// velden
private int nbSiegesAPourvoir;
private double seuilElectoral;
// constructors
public ElectionsConfig() {
}
public ElectionsConfig(Long id, Long version, int nbSiegesAPourvoir, double seuilElectoral) {
// ouder
super(id, version);
// lokale velden
this.nbSiegesAPourvoir = nbSiegesAPourvoir;
this.seuilElectoral = seuilElectoral;
}
// getters en setters
...
}
- regel 4: de klasse is een uitbreiding van de klasse [AbstractEntity];
- regels 8-9: slaan de informatie van de kolommen [SAP, SEUILELECTORAL] uit de tabel [CONF] op;
7.5.4. De klasse [ListeElectorale]
![]() |
De klasse [ListeElectorale] is als volgt:
package elections.dao.entities;
public class ListeElectorale extends AbstractEntity {
// velden
private String nom;
private int voix;
private int sieges;
private boolean elimine;
// constructors
public ListeElectorale() {
}
public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
// ouder
super();
// lokale velden
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
public ListeElectorale(Long id, Long version, String nom, int voix, int sieges, boolean elimine) {
// ouder
super(id, version);
// lokale velden
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
// privé-methoden
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 en 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;
}
}
- regel 3: de klasse is een uitbreiding van de klasse [AbstractEntity];
- regels 6-9: de klasse slaat de kolommen [NOM, VOIX, SIEGES, ELIMINE] van de tabel [LISTES] op;
7.6. Spring-configuratie van de laag [DAO]
![]() |
![]() |
De klasse [AppConfig] is een Spring-configuratieklasse die de toegang tot de database als volgt configureert:
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 {
// constanten
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() {
// gegevensbron TomcatJdbc
DataSource dataSource = new DataSource();
// toegangscontrole JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// een aanvankelijk geopende verbinding
dataSource.setInitialSize(1);
// resultaat
return dataSource;
}
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- regels 25-38: de toegang tot BD verloopt via een gegevensbron [tomcat-jdbc]. Dit type gegevensbron is in paragraaf 6.5 gebruikt en uitgelegd;
- regels 17-23: een reeks statische constanten die toegankelijk zijn voor alle klassen van het project;
- regel 13: de annotatie [@Configuration] maakt van de klasse [AppConfig] een Spring-configuratieklasse;
- regel 11: de annotatie [@ComponentScan] geeft aan in welke pakketten Spring-objecten te vinden zijn. Hier gaan we een Spring-object definiëren in het pakket [dao]. De annotatie [@ComponentScan] maakt van de klasse een configuratieklasse, waardoor we de annotatie [@Configuration] niet hoeven toe te voegen;
- regel 12 schakelt cachebeheer in. Het principe is als volgt:
- we plaatsen de annotatie [@Cacheable('nom_du_cache')] op een methode M. De 'naam_van-de-cache' is de naam die in regel 41 wordt gebruikt;
- wanneer methode M voor de eerste keer wordt aangeroepen, worden de resultaten weergegeven en tevens opgeslagen in de cache die door de annotatie wordt aangeduid;
- wanneer methode M de volgende keren wordt aangeroepen met dezelfde parameters als de eerste keer, wordt deze niet uitgevoerd en geeft Spring alleen de in de cache opgeslagen waarden weer;
7.7. Configuratie van de logbestanden
![]() |
De logbibliotheken worden gedefinieerd door de volgende afhankelijkheid in [pom.xml]:
<!-- Spring Boot-logboekregistratie -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Deze afhankelijkheid levert de volgende bibliotheken op:
![]() |
De bibliotheek [logback] zorgt voor de logbestanden. Deze wordt geconfigureerd via twee bestanden:
- [logback.xml] voor de hoofdtak van de code;
- [logback-test.xml] voor de testtak van de code. Als dit bestand ontbreekt, wordt het vorige bestand gebruikt;
Deze twee bestanden moeten zich in de map [Classpath] van het project bevinden. Daarom worden ze in de map geplaatst:
- [src / main / ressources] voor de hoofdtak van de code;
- [src / test / ressources] voor de testtak van de code;
De inhoud van de bestanden is hier hetzelfde:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders krijgen standaard het type toegewezen
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- logniveau-controle -->
<root level="info"> <!-- info, debug, warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
Alles gebeurt op regel 12, waar het gewenste informatieniveau wordt ingesteld:
- [debug]: het meest gedetailleerde niveau;
- [off]: geen logbestanden;
- [info]: het normale logniveau;
- [warn]: hetzelfde als niveau [info] plus de waarschuwingsberichten (warning). Deze berichten duiden op een mogelijke fout;
Schakel over naar de modus [debug] zodra er fouten optreden tijdens de uitvoering.
7.8. Implementatie van de laag [DAO]
![]() |
![]() |
De interface [IDao] van de laag [DAO] is als volgt:
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 {
// configuratie van de verkiezingen
public ElectionsConfig getElectionsConfig();
// de deelnemende lijsten
public ListeElectorale[] getListesElectorales();
// opslag van de verkiezingsresultaten
public void setListesElectorales(ListeElectorale[] listesElectorales);
}
Het skelet van de klasse [ElectionsDaoJdbc], die de laag [dao] met een database implementeert, ziet er als volgt uit:
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")
// verkiezingsconfiguratie ophalen
public ElectionsConfig getElectionsConfig() {
throw new RuntimeException("[getElectionsConfig] not yet implemented");
}
// lijsten opvragen
public ListeElectorale[] getListesElectorales() {
throw new RuntimeException("[getListesElectorales] not yet implemented");
}
// wijziging van de lijsten [voix, sieges, elimine]
public void setListesElectorales(ListeElectorale[] listesElectorales) {
throw new RuntimeException("[setListesElectorales] not yet implemented");
}
// -------------------- privé-methoden
// beheer van de finally-blok
private ElectionsException doFinally(int code, ResultSet rs, PreparedStatement ps, Connection connexion) {
// afsluiting ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// afsluiten [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// verbinding verbreken
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// er wordt een [ElectionsException] teruggestuurd
return new ElectionsException(code, e3);
}
}
// geen uitzondering
return null;
}
// afhandeling van de catch
private ElectionsException doCatchException(int code1, int code2, Connection connexion, Throwable th) {
// er wordt een [ElectionsException] gegenereerd
ElectionsException ex1 = new ElectionsException(code1, th);
// transactie annuleren
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
}
// de uitzondering wordt teruggegeven
return ex1;
}
}
- regel 24: de annotatie [@Cacheable] is een Spring-annotatie die vraagt om de resultaten van de methode [getElectionsConfig] in het geheugen op te slaan ('te cachen'). Dit is hier mogelijk omdat de inhoud van de tabel [CONF] nooit verandert. Het zou niet mogelijk zijn om deze annotatie op de methode [getListesElectorales] te plaatsen, omdat de inhoud van de tabel [LISTES] in de loop van de tijd verandert;
- de methoden [doCatchException] en [doFinally] retourneren een type [ElectionsException]. De methode [doFinally] retourneert een pointer van het type null als het vrijgeven van de resources zonder fouten is verlopen;
Opdracht: schrijf de klasse [ElectionDaoJdbc], waarbij je je laat inspireren door de klasse [IntroJdbc02] die in paragraaf 6.5.2 is behandeld.
7.9. De testklasse [Main]
![]() |
![]() |
De klasse [Main] is als volgt:
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 {
// gegevensbron
private static IElectionsDao dao;
public static void main(String[] args) {
// de referentie van de laag wordt opgehaald [DAO] na het instantiëren van de Spring-context
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class)) {
// ophalen van de gegevensbron
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);
}
// einde
return;
}
// het lezen van de BD
ElectionsConfig electionsConfig = null;
ListeElectorale[] listes;
try {
// inhoud van de twee tabellen
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);
}
// einde
return;
}
// alles is goed verlopen - weergave
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);
}
}
// privé-methoden ------------------
private static List<String> getErreursForThrowable(Throwable th) {
// we halen de lijst met foutmeldingen van de uitzondering op
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// we halen het bericht alleen op als het !=null is en niet leeg
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// volgende oorzaak
cause = cause.getCause();
}
return erreurs;
}
}
- regels 21-31: ophalen van een referentie op de laag [DAO];
- regel 21: er wordt een syntaxis gebruikt die try_with_resources heet. De syntaxis is als volgt:
try (T ressource=expression) {
// gebruik van bron
...
}
- (vervolg)
- het type T van regel 1 moet de interface [java.lang.AutoCloseable] implementeren;
- bij het verlaten van het blok van de regels 1-4 wordt de bron van het type [java.lang.AutoCloseable] automatisch vrijgegeven, ongeacht of er een uitzondering is opgetreden of niet. Degenen die bekend zijn met de taal C# zullen hierin een syntactische en functionele tegenhanger herkennen van de using-clausule (T bron=uitdrukking);
- regels 40-49: gebruik van de laag [DAO] om de inhoud van de tabellen [CONF] en [LISTES] op te halen;
- regels 41-47: de cache [electionsconfig] wordt getest. Deze is op twee plaatsen gedefinieerd:
- in de klasse [ElectionsDaoJdbc]:
@Cacheable("electionsConfig")
public ElectionsConfig getElectionsConfig() {
- (vervolg)
- in de configuratieklasse [AppConfig]:
@EnableCaching
public class AppConfig {
...
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- regel 59: sluiten van de Spring-context;
- regels 62-67: weergave van de verkregen informatie;
De resultaten die worden verkregen met een geïmplementeerde laag [DAO] zijn als volgt:
- regels 2-4: tonen de invloed van de cache:
- verzoek 1 duurt 80 milliseconden;
- verzoek 2 duurt 1 milliseconde;
Wanneer de database is uitgeschakeld, worden de volgende resultaten verkregen:
7.10. Tests JUnit van de klasse [ElectionsDaoJdbc]
![]() |
![]() |
De klasse [Test01] is de volgende testklasse van [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 {
// laag [DAO]
@Autowired
private IElectionsDao electionsDao;
@Before
public void init() {
// de tabel wordt opgeschoond [LISTES]
// concurrerende lijsten
ListeElectorale[] listes = electionsDao.getListesElectorales();
// we zetten het aantal stemmen en zetels op 0 en verwijderen de waarde '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);
}
// we maken deze gegevens persistent via de laag [dao]
electionsDao.setListesElectorales(listes);
}
@Test
public void testElections01() {
...
}
}
- regel 17: de annotatie [RunWith], die een annotatie [JUnit] (regel 6) is, zorgt voor de integratie met Spring via de klasse [SpringJUnit4ClassRunner];
- regel 16: de annotatie [SpringApplicationConfiguration] is een annotatie [Spring] (regel 8) waarmee de configuratieklassen van de test JUnit kunnen worden aangeduid. Hier wordt de klasse [AppConfig] aangeduid die wordt gebruikt om het project te configureren. We beschikken dan over alle Spring-objecten die door deze configuratieklasse zijn gedefinieerd. Op deze manier kunnen we in de regels 21-22 een verwijzing injecteren naar de laag [DAO] die getest gaat worden;
- regel 25: de annotatie [Before] verwijst naar een methode die vóór elke test moet worden uitgevoerd;
- regels 26-41: de methode [init] zet de stemmen en zetels in de lijsten van de tabel [LISTES] op nul en de booleaanse waarde [elimine] op [false];
De enige testmethode is de volgende:
@Test
public void testElections01() {
System.out.println("testElections01-------------------------------------");
// ophalen van de verkiezingsconfiguratie
ElectionsConfig electionsConfig = electionsDao.getElectionsConfig();
int nbSiegesAPourvoir = electionsConfig.getNbSiegesAPourvoir();
double seuilElectoral = electionsConfig.getSeuilElectoral();
Assert.assertEquals(6, nbSiegesAPourvoir);
Assert.assertEquals(0.05, seuilElectoral, 1E-6);
// deelnemende lijsten
ListeElectorale[] listes = electionsDao.getListesElectorales();
// weergave van uitgelezen waarden
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]);
}
// stemmen en zetels worden toegewezen aan de lijsten
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;
}
// deze gegevens worden opgeslagen via de laag [dao]
electionsDao.setListesElectorales(listes);
// de gegevens worden opnieuw uitgelezen
ListeElectorale[] listesElectorales2 = electionsDao.getListesElectorales();
// de gelezen gegevens worden gecontroleerd
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]);
}
}
- regels 5-9: we controleren of we de inhoud van de tabel [CONF] kunnen ophalen;
- regels 11-19: de inhoud van de tabel [LISTES] wordt weergegeven. Hier vinden geen tests plaats, alleen een visuele controle;
- regels 21-35: we wijzigen in de database de tabel [LISTES] door de lijsten waarden te geven voor hun velden [voix, sieges, elimine];
- regels 37-51: de tabel [LISTES] wordt opnieuw gelezen en er wordt gecontroleerd of het resultaat inderdaad overeenkomt met wat er is ingevoerd;
- regels 52-55: visuele controle;
De console-resultaten die worden verkregen met een geïmplementeerde laag [DAO] zijn als volgt:
- regels 1-23: logbestanden van Spring Test;
- regels 25-26: de inhoud van de tabel [CONF];
- regels 27-34: de oorspronkelijke inhoud van de tabel [LISTES];
- regels 35-42: de inhoud van de tabel [LISTES] na het toewijzen van waarden aan de velden van [voix, sieges, elimine];
Bovendien slaagt de test JUnit:
![]() |
7.11. Aanmaken van het archief [with-dependencies] van de laag [dao]
Het uiteindelijke project heeft de volgende architectuur:
![]() |
Ter herinnering: de Maven-configuratie van het project:
<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>
<!-- bibliotheek 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>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot-logging -->
<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>
<!-- voor het installeren van het projectartefact in de lokale Maven-repository -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
![]() |
We gaan één enkele jar genereren die de klassen van alle bovenstaande jars bevat, plus die van het project van de laag [DAO]. Dit doen we door een wijziging aan te brengen in het bestand [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>
- regels 25-37: configureren een Maven-plugin om de jar te genereren;
- regel 15: geeft de te gebruiken Java-versie voor de compilatie aan;
Zodra deze wijziging is doorgevoerd, kan de jar als volgt worden gegenereerd: [1-10]:
![]() |
![]() |
- in [5], selecteer de projectmap met behulp van de knop [6];
- in [7], geef de uitvoeringsconfiguratie een naam;
- in [8], voer de lijst met uit te voeren Maven-doelen in:
- [clean]: maakt de map [target] van het project leeg waarin het gegenereerde JAR-bestand zal worden geplaatst;
- [compile]: compileert het project;
- [assembly:single]: genereert één enkele JAR-bestand met alle klassen van het project en de bijbehorende afhankelijkheden;
![]() |
- bij [9-10]: controleer of u een JDK (Java Development Kit) hebt en geen JRE (Java Runtime Environment). Het verschil is dat JDK een compiler bevat en JRE niet. Als u geen JDK hebt, moet u er een toevoegen in [10] met [11]. Volg hiervoor de procedure die wordt beschreven in paragraaf 3.1;
![]() |
- in [17], voer de uitvoerconfiguratie uit;
- in [13], het gegenereerde archief;
Dit archief kan met een ontpakprogramma worden geopend:
![]() |
7.12. Het archief van de laag [DAO] testen
Laten we een standaard Eclipse-project (geen Maven) [1] aanmaken:
![]() |
Laten we het pakket [dao.console] uit het project [elections-dao-jdbc-01] kopiëren en plakken in het project [elections-dao-jdbc-02] [2]. Er verschijnen veel fouten omdat de klasse [Main] verwijst naar klassen die niet in zijn [Classpath] staan. We gaan dit aanpassen.
Eerst maken we een map [lib] aan in het nieuwe project:
![]() |
![]() |
In [9] plaatsen we het in de vorige stap aangemaakte archief in de map [lib] en passen we vervolgens het Build Path van het project aan:
![]() |
![]() |
![]() |
- in [18]. We hebben het eerder aangemaakte archief van de laag [DAO] geïmporteerd;
![]() |
- in [19] vertoont het project geen fouten meer;
Vervolgens kan de klasse [Main] worden uitgevoerd. We krijgen dezelfde resultaten als eerder.







































