7. [TD]: Implementierung der [DAO]-Schicht des TD unter Verwendung der JDBC-API
Stichworte: relationale Datenbanken, JDBC-API, SQLException.
Werfen wir noch einmal einen Blick auf die Schichtenarchitektur unserer Anwendung:
![]() |
Die für die Wahl erforderlichen Daten werden in einer MySQL-Datenbank [dbelections] gespeichert
7.1. Support
![]() |
Der Ordner [support / chap-07] [1] enthält:
- die Eclipse-Projekte für dieses Kapitel [2];
- das SQL-Skript zum Anlegen der MySQL-Datenbank [dbelections] [3];
7.2. Die Datenbank [ dbelections]
Aufgabe: Erstellen Sie die MySQL-Datenbank [dbelections], indem Sie die in Abschnitt 6.4.2 beschriebene Vorgehensweise befolgen.
Die Datenbank [dbelections] ist eine MySQL-Datenbank mit zwei Tabellen:
![]() |
Die Tabelle [conf] enthält die Konfigurationsinformationen für die Wahl:
![]() |
- [id]: automatisch inkrementierender Primärschlüssel;
- [version]: Versionsnummer des Datensatzes – kann hier ignoriert werden;
- [sap]: Anzahl der zu besetzenden Sitze;
- [votingThreshold]: die Schwelle, unterhalb derer eine Liste ausscheidet;
Der Inhalt lautet wie folgt:
![]() |
Die Tabelle [listes] enthält die Kandidatenlisten für die Wahl:
![]() |
- [id]: automatisch inkrementierter Primärschlüssel;
- [version]: Versionsnummer des Datensatzes – kann hier ignoriert werden;
- [name]: Name der Liste;
- [votes]: Stimmen für die Liste – bekannt erst nach Benutzereingabe in der [presentation]-Schicht;
- [seats]: Anzahl der gewonnenen Sitze – bekannt erst nach Berechnung durch die [business]-Schicht;
- [eliminated]: 1, wenn die Liste ausgeschieden ist, andernfalls 0 – bekannt erst nach Berechnung der [business]-Schicht;
Der Inhalt der Tabelle [listes] ist wie folgt:
![]() |
Das SQL-Skript zum Erstellen der Datenbank [dbelections] heißt [dbelections.sql] und befindet sich auf dem Server. Der Code lautet wie folgt:
-- phpMyAdmin SQL Dump
-- version 4.0.4
-- http://www.phpmyadmin.net
--
-- Customer: localhost
-- Generated on: Wed March 11, 2015 at 12:20 pm
-- Server version: 5.6.12-log
-- Version of 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`;
-- --------------------------------------------------------
--
-- Structure of the `conf` table
--
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 ;
--
-- Contents of the `conf` table
--
INSERT INTO `conf` (`id`, `version`, `sap`, `seuilelectoral`) VALUES
(1, 1, 6, 0.05);
-- --------------------------------------------------------
--
-- Structure of the `lists` table
--
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 ;
--
-- Contents of the `lists` table
--
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. Das Eclipse-Projekt
![]() |
Das Eclipse-Projekt für die [DAO]-Schicht wird wie folgt aussehen:
![]() |
- Das Paket [elections.dao.entities] enthält die Objekte, die von der [DAO]-Schicht bearbeitet werden;
- Das Paket [elections.dao.service] enthält die Implementierung der [DAO]-Schicht;
- Das Paket [elections.dao.config] enthält die Konfiguration für die [DAO]-Schicht
- Das Paket [elections.dao.junit] enthält eine JUnit-Testklasse für das Projekt;
- Das Paket [elections.dao.console] enthält eine ausführbare Testklasse;
7.4. Maven-Projektkonfiguration
![]() |
Die Datei [pom.xml], die das Maven-Projekt konfiguriert, sieht wie folgt aus:
<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>
<!-- library 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>
<!-- to install the project artifact in the local Maven repository -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
Diese Datei ähnelt der in Abschnitt 6.5.1 beschriebenen. Es wurden folgende Änderungen vorgenommen:
- Zeilen 8–12: Es wurde ein übergeordnetes Maven-Projekt definiert. Das Projekt [spring-boot-starter-parent] (Zeile 10) definiert eine große Anzahl von Abhängigkeiten zusammen mit deren Versionen. Bei der Verwendung einer dieser Abhängigkeiten (Zeilen 19–57) muss die Version nicht angegeben werden, da sie im übergeordneten Maven-Projekt definiert ist;
- Zeilen 40–51: Für die Testklasse des Projekts erforderliche Abhängigkeiten. Diese Abhängigkeiten haben das Attribut [<scope>test</scope>], was bedeutet, dass sie nur für Klassen im Ordner [src/test/java] erforderlich sind. Diese Abhängigkeiten werden nicht in das endgültige Projektarchiv aufgenommen;
- Zeilen 53–56: Die Bibliothek [spring-boot-starter-logging] wird von Spring verwendet, um Protokolle in der Konsole auszugeben;
- Zeilen 14–17: Maven-Konfigurationseigenschaften:
- Zeile 15: Gibt an, dass die Quelldateien in UTF-8 kodiert sind;
- Zeile 16: gibt an, dass die Compiler-Version 1.8 sein muss;
7.5. Entitäten in der [DAO]-Schicht
![]() |
- [ElectionsConfig] ist das Objektmodell, das einer Zeile in der Tabelle [CONF] zugeordnet ist;
- [VoterList] ist das Objektmodell, das einer Zeile in der Tabelle [LISTS] zugeordnet ist;
- [AbstactEntity] ist die übergeordnete Klasse der beiden vorangehenden Klassen. Sie kapselt die beiden Klassen gemeinsamen Felder [id, version];
- [ElectionsException] ist eine Ausnahmeklasse;
7.5.1. Die Klasse [ElectionsException]
![]() |
Die Klasse [ElectionsException] wurde in Abschnitt 4.3 beschrieben. Hier ist der Code noch einmal:
package ...;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// exception class for the Elections application
// the exception is uncontrolled
public class ElectionsException extends RuntimeException implements Serializable {
// serial ID
private static final long serialVersionUID = 1L;
// local fields
private int code;
private List<String> erreurs;
// manufacturers
public ElectionsException() {
super();
}
public ElectionsException(int code, Throwable e) {
// parent
super(e);
// local
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message, Throwable e) {
// parent
super(message,e);
// local
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message) {
// parent
super(message);
// local
this.code = code;
List<String> erreurs = new ArrayList<>();
erreurs.add(message);
this.erreurs = erreurs;
}
public ElectionsException(int code, List<String> erreurs) {
// parent
super();
// local
this.code = code;
this.erreurs = erreurs;
}
// list of exception error messages
private List<String> getErreursForException(Throwable th) {
// retrieve the list of exception error messages
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// the message is retrieved only if it is !=null and not blank
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// next cause
cause = cause.getCause();
}
return erreurs;
}
// getters and setters
...
}
Ein [ElectionsException]-Objekt wird durch zwei Informationen charakterisiert:
- Zeile 16: ein Fehlercode;
- Zeile 17: eine Liste von Fehlermeldungen, die mit dem Stack-Trace der aufgetretenen Ausnahmen verknüpft sind;
- die Klasse verfügt über 5 Konstruktoren (Zeilen 20, 24, 32, 40, 50);
- Zeilen 59–76: Die Methode [getErrorsForException] ruft die Fehlermeldungen aus dem Ausnahmestapel ab;
7.5.2. Die Klasse [AbstractEntity]
![]() |
Die Klasse [AbstractEntity] sieht wie folgt aus:
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;
// fields
protected Long id;
protected Long version;
// manufacturers
public AbstractEntity() {
}
public AbstractEntity(Long id, Long version) {
this.id = id;
this.version = version;
}
// signature
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
// getters and setters
...
}
Es speichert die Felder [ID, NAME] der Zeilen in den Tabellen [CONF] und [LISTS] (Zeilen 8–9).
- Zeile 8: Die Klasse verfügt über das Attribut [Abstract], das angibt, dass sie nicht instanziiert werden kann. Sie kann nur abgeleitet werden;
- Zeilen 26–33: JSON-Signatur des Objekts;
- Zeile 28: Die JSON-Zeichenkette von [this] wird zurückgegeben. Wenn [this] zur Laufzeit ein von [AbstractEntity] abgeleitetes Objekt darstellt, wird die JSON-Zeichenkette des abgeleiteten Objekts abgerufen. Abgeleitete Klassen müssen daher keine [toString]-Methode definieren. Die der übergeordneten Klasse ist ausreichend;
7.5.3. Die Klasse [ElectionsConfig]
![]() |
Die Klasse [ElectionsConfig] sieht wie folgt aus:
package elections.dao.entities;
public class ElectionsConfig extends AbstractEntity {
private static final long serialVersionUID = 1L;
// fields
private int nbSiegesAPourvoir;
private double seuilElectoral;
// manufacturers
public ElectionsConfig() {
}
public ElectionsConfig(Long id, Long version, int nbSiegesAPourvoir, double seuilElectoral) {
// parent
super(id, version);
// local fields
this.nbSiegesAPourvoir = nbSiegesAPourvoir;
this.seuilElectoral = seuilElectoral;
}
// getters and setters
...
}
- Zeile 4: Die Klasse erweitert die Klasse [AbstractEntity];
- Zeilen 8–9: Speichern der Informationen aus den Spalten [SAP, SEUILELECTORAL] der Tabelle [CONF];
7.5.4. Die Klasse [VoterList]
![]() |
Die Klasse [VoterList] sieht wie folgt aus:
package elections.dao.entities;
public class ListeElectorale extends AbstractEntity {
// fields
private String nom;
private int voix;
private int sieges;
private boolean elimine;
// manufacturers
public ListeElectorale() {
}
public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
// parent
super();
// local fields
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
public ListeElectorale(Long id, Long version, String nom, int voix, int sieges, boolean elimine) {
// parent
super(id, version);
// local fields
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
// private methods
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 and 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;
}
}
- Zeile 3: Die Klasse erweitert die Klasse [AbstractEntity];
- Zeilen 6–9: Die Klasse speichert die Spalten [NAME, VOTES, SEATS, ELIMINATED] aus der Tabelle [LISTS];
7.6. Spring-Konfiguration der [DAO]-Schicht
![]() |
![]() |
Die Klasse [AppConfig] ist eine Spring-Konfigurationsklasse, die den Datenbankzugriff wie folgt konfiguriert:
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 {
// constants
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() {
// data source TomcatJdbc
DataSource dataSource = new DataSource();
// configuration access JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// an initially open connection
dataSource.setInitialSize(1);
// result
return dataSource;
}
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- Zeilen 25–38: Der Zugriff auf die Datenbank erfolgt über eine Datenquelle [tomcat-jdbc]. Diese Art von Datenquelle wurde in Abschnitt 6.5 verwendet und erläutert;
- Zeilen 17–23: Eine Reihe statischer Konstanten, auf die alle Klassen im Projekt zugreifen können;
- Zeile 13: Die Annotation [@Configuration] macht die Klasse [AppConfig] zu einer Spring-Konfigurationsklasse;
- Zeile 11: Die Annotation [@ComponentScan] gibt die Pakete an, in denen Spring-Objekte zu finden sind. Hier definieren wir ein Spring-Objekt im Paket [dao]. Die Annotation [@ComponentScan] macht die Klasse zu einer Konfigurationsklasse, wodurch wir uns das Hinzufügen der Annotation [@Configuration] sparen;
- Zeile 12 aktiviert die Cache-Verwaltung. Das Prinzip ist wie folgt:
- Wir wenden die Annotation [@Cacheable('cache_name')] auf eine Methode M an. Der „cache_name“ ist der in Zeile 41 verwendete Name;
- Wenn die Methode M zum ersten Mal aufgerufen wird, werden ihre Ergebnisse zurückgegeben und zusätzlich im durch die Annotation benannten Cache gespeichert;
- Wenn die Methode M anschließend mit denselben Parametern wie beim ersten Mal aufgerufen wird, wird sie nicht ausgeführt, und Spring gibt einfach die zwischengespeicherten Werte zurück;
7.7. Protokollkonfiguration
![]() |
Die Logging-Bibliotheken werden durch die folgende Abhängigkeit in [pom.xml] definiert:
<!-- Spring Boot Logging -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Diese Abhängigkeit umfasst die folgenden Bibliotheken:
![]() |
Die [logback]-Bibliothek übernimmt die Protokollierung. Sie wird über zwei Dateien konfiguriert:
- [logback.xml] für den Hauptcodezweig;
- [logback-test.xml] für den Testzweig des Codes. Fehlt diese Datei, wird stattdessen die erstgenannte Datei verwendet;
Diese beiden Dateien müssen sich im [Classpath] des Projekts befinden. Aus diesem Grund werden sie im Ordner abgelegt:
- [src/main/resources] für den Hauptcodezweig;
- [src/test/resources] für den Testzweig des Codes;
Der Inhalt der Dateien ist hier identisch:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are by default assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- log level control -->
<root level="info"> <!-- info, debug, warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
Alles geschieht in Zeile 12, wo wir die gewünschte Protokollierungsstufe festlegen:
- [debug]: die detaillierteste Stufe;
- [off]: keine Protokolle;
- [info]: die normale Protokollierungsstufe;
- [warn]: wie [info] plus Warnmeldungen. Diese Meldungen weisen auf einen möglichen Fehler hin;
Wechseln Sie in den [debug]-Modus, sobald während der Ausführung Fehler auftreten.
7.8. Implementierung der [DAO]-Schicht
![]() |
![]() |
Die [IDao]-Schnittstelle der [DAO]-Schicht sieht wie folgt aus:
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 {
// election configuration
public ElectionsConfig getElectionsConfig();
// competing lists
public ListeElectorale[] getListesElectorales();
// save election results
public void setListesElectorales(ListeElectorale[] listesElectorales);
}
Das Grundgerüst der Klasse [ElectionsDaoJdbc], die die [dao]-Schicht mit einer Datenbank implementiert, sieht wie folgt aus:
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")
// obtaining conf of the election
public ElectionsConfig getElectionsConfig() {
throw new RuntimeException("[getElectionsConfig] not yet implemented");
}
// obtaining lists
public ListeElectorale[] getListesElectorales() {
throw new RuntimeException("[getListesElectorales] not yet implemented");
}
// list modification [votes, seats, eliminated]
public void setListesElectorales(ListeElectorale[] listesElectorales) {
throw new RuntimeException("[setListesElectorales] not yet implemented");
}
// -------------------- private methods
// end-of-life management
private ElectionsException doFinally(int code, ResultSet rs, PreparedStatement ps, Connection connexion) {
// closure ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// closure [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// close connection
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// returns a [ElectionsException]
return new ElectionsException(code, e3);
}
}
// no exceptions
return null;
}
// wrestling management
private ElectionsException doCatchException(int code1, int code2, Connection connexion, Throwable th) {
// we generate a [ElectionsException]
ElectionsException ex1 = new ElectionsException(code1, th);
// cancel transaction
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
}
// we return the exception
return ex1;
}
}
- Zeile 24: Die Annotation [@Cacheable] ist eine Spring-Annotation, die das System anweist, die Ergebnisse der Methode [getElectionsConfig] zwischenzuspeichern. Dies ist hier möglich, da sich der Inhalt der Tabelle [CONF] nie ändert. Es wäre nicht möglich, diese Annotation auf die Methode [getListesElectorales] anzuwenden, da sich der Inhalt der Tabelle [LISTES] im Laufe der Zeit ändert;
- Die Methoden [doCatchException] und [doFinally] geben den Typ [ElectionsException] zurück. Die Methode [doFinally] gibt einen Null-Zeiger zurück, wenn die Ressourcen fehlerfrei freigegeben wurden;
Aufgabe: Schreiben Sie die Klasse [ElectionDaoJdbc] in Anlehnung an die in Abschnitt 6.5.2 behandelte Klasse [IntroJdbc02].
7.9. Die Testklasse [Main]
![]() |
![]() |
Die Klasse [Main] sieht wie folgt aus:
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 {
// data source
private static IElectionsDao dao;
public static void main(String[] args) {
// retrieve the [DAO] layer reference after instantiating the Spring context
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class)) {
// data source recovery
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);
}
// end
return;
}
// reading the BD
ElectionsConfig electionsConfig = null;
ListeElectorale[] listes;
try {
// contents of both tables
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);
}
// end
return;
}
// all went well - display
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);
}
}
// private methods ------------------
private static List<String> getErreursForThrowable(Throwable th) {
// retrieve the list of exception error messages
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// the message is retrieved only if it is !=null and not blank
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// next cause
cause = cause.getCause();
}
return erreurs;
}
}
- Zeilen 21–31: Abrufen einer Referenz auf die [DAO]-Ebene;
- Zeile 21: Wir verwenden eine Syntax namens „try_with_resources“. Die Syntax lautet wie folgt:
try (T ressource=expression) {
// exploitation de ressource
...
}
- (Fortsetzung)
- Der Typ T in Zeile 1 muss die Schnittstelle [java.lang.AutoCloseable] implementieren;
- Beim Verlassen des Blocks der Zeilen 1–4 wird die Ressource vom Typ [java.lang.AutoCloseable] automatisch freigegeben, unabhängig davon, ob eine Ausnahme aufgetreten ist. Wer mit der Sprache C# vertraut ist, wird dies als syntaktisches und funktionales Pendant zur using-Klausel (T resource = expression) erkennen;
- Zeilen 40–49: Verwendung der [DAO]-Schicht zum Abrufen des Inhalts der Tabellen [CONF] und [LISTES];
- Zeilen 41–47: Der Cache [electionsconfig] wird getestet. Dieser Cache wurde an zwei Stellen definiert:
- in der Klasse [ElectionsDaoJdbc]:
@Cacheable("electionsConfig")
public ElectionsConfig getElectionsConfig() {
- (Fortsetzung)
- in der Konfigurationsklasse [AppConfig]:
@EnableCaching
public class AppConfig {
...
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- Zeile 59: Schließen des Spring-Kontexts;
- Zeilen 62–67: Anzeige der abgerufenen Informationen;
Die mit einer implementierten [DAO]-Schicht erzielten Ergebnisse lauten wie folgt:
- Zeilen 2–4: zeigen die Auswirkungen des Caches:
- Anfrage 1 dauert 80 Millisekunden;
- Abfrage 2 dauert 1 Millisekunde;
Wenn die Datenbank offline ist, werden folgende Ergebnisse erzielt:
7.10. JUnit-Tests für die Klasse [ ElectionsDaoJdbc]
![]() |
![]() |
Die Klasse [Test01] ist die folgende [JUnit]-Testklasse:
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 {
// layer [DAO]
@Autowired
private IElectionsDao electionsDao;
@Before
public void init() {
// the table is cleaned [LISTES]
// competing lists
ListeElectorale[] listes = electionsDao.getListesElectorales();
// set votes and seats to 0 and eliminate 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 make this data persistent using the [dao] layer
electionsDao.setListesElectorales(listes);
}
@Test
public void testElections01() {
...
}
}
- Zeile 17: Die Annotation [RunWith], bei der es sich um eine [JUnit]-Annotation (Zeile 6) handelt, gewährleistet die Integration mit Spring über die Klasse [SpringJUnit4ClassRunner];
- Zeile 16: Die Annotation [SpringApplicationConfiguration] ist eine [Spring]-Annotation (Zeile 8), mit der Sie die Konfigurationsklassen für den JUnit-Test angeben können. Hier geben wir die Klasse [AppConfig] an, die zur Konfiguration des Projekts verwendet wird. Wir haben dann Zugriff auf alle Spring-Objekte, die durch diese Konfigurationsklasse definiert sind. Auf diese Weise können wir in den Zeilen 21–22 eine Referenz auf die [DAO]-Schicht injizieren, die getestet werden soll;
- Zeile 25: Die Annotation [Before] kennzeichnet eine Methode, die vor jedem Test ausgeführt werden muss;
- Zeilen 26–41: Die [init]-Methode setzt die Stimmen und Sitze in der [LISTES]-Tabelle auf Null und den booleschen Wert [elimine] auf [false];
Die einzige Testmethode lautet wie folgt:
@Test
public void testElections01() {
System.out.println("testElections01-------------------------------------");
// election configuration recovery
ElectionsConfig electionsConfig = electionsDao.getElectionsConfig();
int nbSiegesAPourvoir = electionsConfig.getNbSiegesAPourvoir();
double seuilElectoral = electionsConfig.getSeuilElectoral();
Assert.assertEquals(6, nbSiegesAPourvoir);
Assert.assertEquals(0.05, seuilElectoral, 1E-6);
// competing lists
ListeElectorale[] listes = electionsDao.getListesElectorales();
// display read values
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]);
}
// votes and seats are allocated to lists
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;
}
// we make this data persistent using the [dao] layer
electionsDao.setListesElectorales(listes);
// data re-reading
ListeElectorale[] listesElectorales2 = electionsDao.getListesElectorales();
// check data read
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]);
}
}
- Zeilen 5–9: Wir stellen sicher, dass wir den Inhalt der Tabelle [CONF] abrufen können;
- Zeilen 11–19: Wir zeigen den Inhalt der Tabelle [LISTES] an. Hier gibt es keine Tests, sondern nur eine visuelle Überprüfung;
- Zeilen 21–35: Wir ändern die Tabelle [LISTES] in der Datenbank, indem wir den Feldern der Listen [voices, seats, eliminated] Werte zuweisen;
- Zeilen 37–51: Wir lesen die Tabelle [LISTES] erneut aus und überprüfen, ob das Ergebnis mit den eingegebenen Werten übereinstimmt;
- Zeilen 52–55: visuelle Überprüfung;
Die mit einer implementierten [DAO]-Schicht erzielten Konsolenergebnisse lauten wie folgt:
- Zeilen 1–23: Spring-Testprotokolle;
- Zeilen 25–26: Inhalt der Tabelle [CONF];
- Zeilen 27–34: der ursprüngliche Inhalt der Tabelle [LISTES];
- Zeilen 35–42: der Inhalt der Tabelle [LISTES] nach der Zuweisung von Werten zu den Feldern [voix, sieges, elimine];
Außerdem besteht der JUnit-Test:
![]() |
7.11. Erstellung des [with-dependencies]-Archivs für die [dao]-Schicht
Das endgültige Projekt weist folgende Architektur auf:
![]() |
Sehen wir uns die Maven-Konfiguration des Projekts an:
<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>
<!-- library 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>
<!-- to install the project artifact in the local Maven repository -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
![]() |
Wir werden eine einzige JAR-Datei erstellen, die die Klassen aus allen oben genannten JARs sowie diejenigen aus dem [DAO]-Layer-Projekt enthält. Dazu nehmen wir eine Änderung an der [pom.xml]-Datei vor:
<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>
- Zeilen 25–37: Konfigurieren eines Maven-Plugins zur Erstellung der JAR-Datei;
- Zeile 15: gibt die für die Kompilierung zu verwendende Java-Version an;
Sobald diese Änderung vorgenommen wurde, können Sie die JAR-Datei wie folgt generieren [1–10]:
![]() |
![]() |
- Wählen Sie in [5] den Projektordner über die Schaltfläche [6] aus;
- Geben Sie in [7] einen Namen für die Build-Konfiguration ein;
- Geben Sie unter [8] die Liste der auszuführenden Maven-Aufgaben ein:
- [clean]: löscht den [target]-Ordner des Projekts, in dem die generierte JAR-Datei abgelegt wird;
- [compile]: kompiliert das Projekt;
- [assembly:single]: Erzeugt eine einzelne JAR-Datei, die alle Klassen des Projekts und dessen Abhängigkeiten enthält;
![]() |
- Überprüfen Sie in [9-10], ob Sie über ein JDK (Java Development Kit) und nicht über eine JRE (Java Runtime Environment) verfügen. Der Unterschied besteht darin, dass das JDK einen Compiler enthält, die JRE hingegen nicht. Wenn Sie kein JDK haben, müssen Sie in [10] mithilfe von [11] eines hinzufügen. Befolgen Sie dazu die in Abschnitt 3.1 beschriebene Vorgehensweise;
![]() |
- Führen Sie in [17] die Build-Konfiguration aus;
- in [13] das generierte Archiv;
Sie können dieses Archiv mit einem Entpacker öffnen:
![]() |
7.12. Testen des [DAO]-Layer-Archivs
Erstellen wir ein Standard-Eclipse-Projekt (kein Maven) [1]:
![]() |
Kopieren wir das Paket [dao.console] aus dem Projekt [elections-dao-jdbc-01] und fügen es in das Projekt [elections-dao-jdbc-02] ein [2]. Es treten mehrere Fehler auf, da die Klasse [Main] auf Klassen verweist, die sich nicht in ihrem [Classpath] befinden. Wir werden den [Classpath] anpassen.
Zunächst erstellen wir [2-8] einen Ordner [lib] im neuen Projekt:
![]() |
![]() |
In [9] legen wir das im vorherigen Schritt erstellte Archiv im Ordner [lib] ab und ändern anschließend den Build-Pfad des Projekts:
![]() |
![]() |
![]() |
- In [18] haben wir das zuvor erstellte Archiv der [DAO]-Ebene importiert;
![]() |
- in [19] weist das Projekt keine Fehler mehr auf;
Wir können nun die [Main]-Klasse ausführen. Wir erhalten die gleichen Ergebnisse wie zuvor.







































