7. [TD]: Implementierung der Schicht [DAO] von TD mit API und JDBC
Stichworte: relationale Datenbanken, API, JDBC, SQLException.
Werfen wir noch einmal einen Blick auf die Schichtenarchitektur unserer Anwendung:
![]() |
Die für die Wahl erforderlichen Daten werden in einer Datenbank gespeichert: MySQL [dbelections]
7.1. Support
![]() |
Der Ordner [support / chap-07] [1] enthält:
- die Eclipse-Projekte dieses Kapitels [2];
- das Skript SQL zur Erstellung der Datenbank MySQL, [dbelections], [3];
7.2. Die Datenbank [dbelections]
Aufgabe: Erstellen Sie die Datenbanken MySQL und [dblelections] gemäß der in Abschnitt 6.4.2 beschriebenen Vorgehensweise.
Die Datenbank [dbelections] ist eine Datenbank vom Typ MySQL mit zwei Tabellen:
![]() |
Die Tabelle [conf] enthält die Konfigurationsinformationen zur Wahl:
![]() |
- [id]: automatisch inkrementierter Primärschlüssel;
- [version]: Versionsnummer des Datensatzes – kann hier ignoriert werden;
- [sap]: Anzahl der zu besetzenden Sitze;
- [seuilelectoral]: der Schwellenwert, unterhalb dessen eine Liste ausgeschlossen wird;
Der Inhalt lautet wie folgt:
![]() |
Die Tabelle [listes] enthält die Kandidatenlisten der Wahl:
![]() |
- [id]: automatisch inkrementierter Primärschlüssel;
- [version]: Versionsnummer des Datensatzes – kann hier ignoriert werden;
- [nom]: Name der Liste;
- [voix]: Stimme der Liste – ist erst nach Eingabe durch den Benutzer in der Ebene [présentation] bekannt;
- [sieges]: Anzahl der gewonnenen Sitze – ist erst nach der Berechnung der Ebene [métier] bekannt;
- [elimine]: 1, wenn die Liste ausgeschieden ist, andernfalls 0 – bekannt erst nach Berechnung der Ebene [métier];
Der Inhalt der Tabelle [listes] lautet wie folgt:
![]() |
Das Skript SQL zur Generierung der Datenbank [dbelections] heißt [dbelections.sql] und befindet sich auf dem Server. Sein Code lautet wie folgt:
-- phpMyAdmin SQL Dump
-- Version 4.0.4
-- http://www.phpmyadmin.net
--
-- Client: localhost
-- Erstellt am: Mi, 11. März 2015 um 12:20 Uhr
-- Serverversion: 5.6.12-log
-- Version von 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 */;
--
-- Datenbank: `dbelections`
--
CREATE DATABASE IF NOT EXISTS `dbelections` DEFAULT CHARACTER SET utf8 COLLATE utf8_swedish_ci;
USE `dbelections`;
-- --------------------------------------------------------
--
-- Struktur der Tabelle `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 ;
--
-- Inhalt der Tabelle `conf`
--
INSERT INTO `conf` (`id`, `version`, `sap`, `seuilelectoral`) VALUES
(1, 1, 6, 0.05);
-- --------------------------------------------------------
--
-- Struktur der Tabelle `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 ;
--
-- Inhalt der Tabelle `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. Das Eclipse-Projekt
![]() |
Das Eclipse-Projekt der Schicht [DAO] sieht wie folgt aus:
![]() |
- Das Paket [elections.dao.entities] enthält die Objekte, die von der Schicht [DAO] verarbeitet werden;
- Das Paket [elections.dao.service] enthält die Implementierung der Schicht [DAO];
- Das Paket [elections.dao.config] enthält die Konfiguration der Schicht [DAO]
- Das Paket [elections.dao.junit] enthält eine Testklasse JUnit des Projekts;
- Das Paket [elections.dao.console] enthält eine ausführbare Testklasse;
7.4. Maven-Projektkonfiguration
![]() |
Die Datei [pom.xml], die das Maven-Projekt konfiguriert, lautet wie folgt:
<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>
<!-- Bibliothek 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-Protokollierung -->
<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>
<!-- zur Installation des Projekt-Artefakts im lokalen 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 entspricht 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 sehr große Anzahl von Abhängigkeiten mit ihren Versionen. Wenn eine davon verwendet wird (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 tragen das Attribut „[<scope>test</scope>]“, was bedeutet, dass sie nur für die Klassen im Ordner „[src / test / java]“ benötigt werden. 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 Logs auf dem Bildschirm auszugeben;
- Zeilen 14–17: Maven-Konfigurationseigenschaften:
- Zeile 15: gibt an, dass die Quelldateien in UTF-8 programmiert sind;
- Zeile 16: gibt an, dass der Compiler der Version 1.8 verwendet werden soll;
7.5. Die Entitäten der [DAO]-Schicht
![]() |
- [ElectionsConfig] ist das Objektmodell, das einer Zeile der Tabelle [CONF] zugeordnet ist;
- [ListeElectorale] ist das Objektmodell, das einer Zeile der Tabelle [LISTES] zugeordnet ist;
- [AbstactEntity] ist die übergeordnete Klasse der beiden vorgenannten Klassen. Sie fasst die Felder von [id, version] zusammen, die beiden Klassen gemeinsam sind;
- [ElectionsException] ist eine Ausnahmeklasse;
7.5.1. Die Klasse [ElectionsException]
![]() |
Die Klasse [ElectionsException] wurde in Abschnitt 4.3 beschrieben. Hier noch einmal ihr Code:
package ...;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// Ausnahmeklasse für die Anwendung „Elections“
// Die Ausnahme ist unkontrolliert
public class ElectionsException extends RuntimeException implements Serializable {
// Seriennummer ID
private static final long serialVersionUID = 1L;
// Lokale Felder
private int code;
private List<String> erreurs;
// Konstruktoren
public ElectionsException() {
super();
}
public ElectionsException(int code, Throwable e) {
// übergeordnet
super(e);
// lokal
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message, Throwable e) {
// übergeordnet
super(message,e);
// lokal
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message) {
// übergeordnet
super(message);
// lokal
this.code = code;
List<String> erreurs = new ArrayList<>();
erreurs.add(message);
this.erreurs = erreurs;
}
public ElectionsException(int code, List<String> erreurs) {
// übergeordnet
super();
// lokal
this.code = code;
this.erreurs = erreurs;
}
// Liste der Fehlermeldungen einer Ausnahme
private List<String> getErreursForException(Throwable th) {
// Die Liste der Fehlermeldungen der Ausnahme wird abgerufen
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// Die Meldung wird nur abgerufen, wenn sie != null und nicht leer ist
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// nächste Ursache
cause = cause.getCause();
}
return erreurs;
}
// Getter und Setter
...
}
Ein Objekt [ElectionsException] wird durch zwei Angaben charakterisiert:
- Zeile 16: einen Fehlercode;
- Zeile 17: eine Liste von Fehlermeldungen, die mit dem Stapel der aufgetretenen Ausnahmen verknüpft sind;
- Die Klasse verfügt über 5 Konstruktoren (Zeilen 20, 24, 32, 40, 50);
- Zeilen 59–76: Mit der Methode [getErreursForException] können die Fehlermeldungen aus dem Ausnahmestapel abgerufen werden;
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;
// Felder
protected Long id;
protected Long version;
// Konstruktoren
public AbstractEntity() {
}
public AbstractEntity(Long id, Long version) {
this.id = id;
this.version = version;
}
// Signatur
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
// Getter und Setter
...
}
Sie speichert die Felder [ID, NOM] der Zeilen der Tabellen [CONF] und [LISTES] (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: Signatur jSON des Objekts;
- Zeile 28: Es wird die Zeichenkette jSON von [this] zurückgegeben. Wenn [this] zum Zeitpunkt der Ausführung ein von [AbstactEntity] abgeleitetes Objekt darstellt, wird die Zeichenfolge jSON des abgeleiteten Objekts zurückgegeben. Abgeleitete Klassen müssen daher keine Methode [toString] definieren. Die Methode der übergeordneten Klasse reicht aus;
7.5.3. Die Klasse [ElectionsConfig]
![]() |
Die Klasse [ElectionsConfig] lautet wie folgt:
package elections.dao.entities;
public class ElectionsConfig extends AbstractEntity {
private static final long serialVersionUID = 1L;
// Felder
private int nbSiegesAPourvoir;
private double seuilElectoral;
// Konstruktoren
public ElectionsConfig() {
}
public ElectionsConfig(Long id, Long version, int nbSiegesAPourvoir, double seuilElectoral) {
// übergeordnetes Element
super(id, version);
// lokale Felder
this.nbSiegesAPourvoir = nbSiegesAPourvoir;
this.seuilElectoral = seuilElectoral;
}
// Getter und Setter
...
}
- Zeile 4: Die Klasse erweitert die Klasse [AbstractEntity];
- Zeilen 8–9: Speichern die Informationen der Spalten [SAP, SEUILELECTORAL] aus der Tabelle [CONF];
7.5.4. Die Klasse [ListeElectorale]
![]() |
Die Klasse [ListeElectorale] lautet wie folgt:
package elections.dao.entities;
public class ListeElectorale extends AbstractEntity {
// Felder
private String nom;
private int voix;
private int sieges;
private boolean elimine;
// Konstruktoren
public ListeElectorale() {
}
public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
// übergeordnet
super();
// lokale Felder
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
public ListeElectorale(Long id, Long version, String nom, int voix, int sieges, boolean elimine) {
// übergeordnetes Objekt
super(id, version);
// lokale Felder
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
// private 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");
}
}
// Getter und 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;
}
}
- Zeile 3: Die Klasse erweitert die Klasse [AbstractEntity];
- Zeilen 6–9: Die Klasse speichert die Spalten [NOM, VOIX, SIEGES, ELIMINE] der Tabelle [LISTES];
7.6. Spring-Konfiguration der Schicht [DAO]
![]() |
![]() |
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 {
// Konstanten
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() {
// Datenquelle TomcatJdbc
DataSource dataSource = new DataSource();
// Zugriffskonfiguration JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// eine ursprünglich geöffnete Verbindung
dataSource.setInitialSize(1);
// Ergebnis
return dataSource;
}
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- Zeilen 25–38: Der Zugriff auf BD erfolgt über eine Datenquelle [tomcat-jdbc]. Dieser Datenquellentyp wurde in Abschnitt 6.5 verwendet und erläutert;
- Zeilen 17–23: Eine Reihe statischer Konstanten, auf die alle Klassen des Projekts 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 Setzen der Annotation [@Configuration] sparen;
- Zeile 12 aktiviert die Cache-Verwaltung. Das Prinzip ist wie folgt:
- Man fügt die Annotation [@Cacheable('nom_du_cache')] zu einer Methode M hinzu. 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 gleichzeitig in den durch die Annotation benannten Cache gespeichert;
- wird die Methode M bei den folgenden Aufrufen mit denselben Parametern wie beim ersten Mal nicht erneut ausgeführt, sondern Spring gibt lediglich die zwischengespeicherten Werte zurück;
7.7. Konfiguration der Protokolle
![]() |
Die Logging-Bibliotheken werden durch die folgende Abhängigkeit in [pom.xml] definiert:
<!-- Spring Boot-Protokollierung -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Diese Abhängigkeit führt zu folgenden Bibliotheken:
![]() |
Die Protokollierung wird von der Bibliothek [logback] übernommen. Sie wird über zwei Dateien konfiguriert:
- [logback.xml] für den Hauptzweig des Codes;
- [logback-test.xml] für den Testzweig des Codes. Fehlt diese Datei, wird die vorherige Datei verwendet;
Diese beiden Dateien müssen sich im Verzeichnis [Classpath] des Projekts befinden. Aus diesem Grund werden sie im folgenden Ordner abgelegt:
- [src / main / ressources] für den Hauptzweig des Codes;
- [src / test / ressources] für den Testzweig des Codes;
Der Inhalt der Dateien ist hier identisch:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- Encodern wird standardmäßig der Typ zugewiesen
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- Steuerung der Protokollierungsstufe -->
<root level="info"> <!-- Info, Debug, Warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
Entscheidend ist Zeile 12, in der die gewünschte Detailebene festgelegt wird:
- [debug]: die detaillierteste Stufe;
- [off]: keine Protokolle;
- [info]: normale Protokollstufe;
- [warn]: wie Stufe [info] plus Warnmeldungen. Diese Meldungen weisen auf einen möglichen Fehler hin;
Wechseln Sie in den Modus [debug], sobald bei der Ausführung Fehler auftreten.
7.8. Implementierung der Schicht [DAO]
![]() |
![]() |
Die Schnittstelle [IDao] der Schicht [DAO] lautet wie folgt:
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 {
// Konfiguration der Wahlen
public ElectionsConfig getElectionsConfig();
// die Kandidatenlisten
public ListeElectorale[] getListesElectorales();
// Speicherung der Wahlergebnisse
public void setListesElectorales(ListeElectorale[] listesElectorales);
}
Das Grundgerüst der Klasse [ElectionsDaoJdbc], die die Schicht [dao] 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")
// Abruf der Wahlkonfiguration
public ElectionsConfig getElectionsConfig() {
throw new RuntimeException("[getElectionsConfig] not yet implemented");
}
// Abruf der Listen
public ListeElectorale[] getListesElectorales() {
throw new RuntimeException("[getListesElectorales] not yet implemented");
}
// Änderung der Listen [voix, sieges, elimine]
public void setListesElectorales(ListeElectorale[] listesElectorales) {
throw new RuntimeException("[setListesElectorales] not yet implemented");
}
// -------------------- private Methoden
// Verwaltung des „finally“-Blocks
private ElectionsException doFinally(int code, ResultSet rs, PreparedStatement ps, Connection connexion) {
// Abschluss ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// Schließen [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// Verbindung schließen
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// Es wird ein [ElectionsException] zurückgegeben
return new ElectionsException(code, e3);
}
}
// keine Ausnahme
return null;
}
// Fehlerbehandlung
private ElectionsException doCatchException(int code1, int code2, Connection connexion, Throwable th) {
// Es wird ein [ElectionsException] generiert
ElectionsException ex1 = new ElectionsException(code1, th);
// Transaktion stornieren
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
}
// Die Ausnahme wird zurückgegeben
return ex1;
}
}
- Zeile 24: Die Annotation [@Cacheable] ist eine Spring-Annotation, die anweist, die Ergebnisse der Methode [getElectionsConfig] im Speicher zu halten („zu cachen“). 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 einen Typ [ElectionsException] zurück. Die Methode [doFinally] gibt einen Zeiger vom Typ null zurück, wenn die Freigabe der Ressourcen fehlerfrei erfolgt ist;
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] lautet wie folgt:
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 {
// Datenquelle
private static IElectionsDao dao;
public static void main(String[] args) {
// Die Referenz der Schicht [DAO] wird nach der Instanziierung des Spring-Kontexts abgerufen
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class)) {
// Abruf der Datenquelle
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);
}
// Ende
return;
}
// Auslesen der Tabelle BD
ElectionsConfig electionsConfig = null;
ListeElectorale[] listes;
try {
// Inhalt der beiden 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);
}
// Ende
return;
}
// Alles ist gut gelaufen – Anzeige
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 Methoden ------------------
private static List<String> getErreursForThrowable(Throwable th) {
// Die Liste der Fehlermeldungen der Ausnahme wird abgerufen
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// Die Meldung wird nur abgerufen, wenn sie != null und nicht leer ist
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// nächste Ursache
cause = cause.getCause();
}
return erreurs;
}
}
- Zeilen 21–31: Abruf einer Referenz auf die Ebene [DAO];
- Zeile 21: Es wird eine Syntax namens try_with_resources verwendet. Ihre Syntax lautet wie folgt:
try (T ressource=expression) {
// Ressourcenauslastung
...
}
- (Fortsetzung)
- Der Typ T in Zeile 1 muss die Schnittstelle [java.lang.AutoCloseable] implementieren;
- Am Ende des Blocks der Zeilen 1–4 wird die Ressource vom Typ [java.lang.AutoCloseable] automatisch freigegeben, unabhängig davon, ob eine Ausnahme aufgetreten ist oder nicht. Wer die Sprache C# kennt, wird hier einen syntaktischen und funktionalen Verwandten der using-Klausel (T Ressource = Ausdruck) erkennen;
- Zeilen 40–49: Verwendung der Schicht [DAO], um den Inhalt der Tabellen [CONF] und [LISTES] abzurufen;
- Zeilen 41–47: Der Cache [electionsconfig] wird getestet. Dieser 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 Schicht [DAO] erzielten Ergebnisse lauten wie folgt:
- Zeilen 2–4: zeigen den Einfluss des Caches:
- Anfrage 1 dauert 80 Millisekunden;
- Abfrage 2 dauert 1 Millisekunde;
Wenn die Datenbank abgeschaltet ist, erhält man folgende Ergebnisse:
7.10. Tests JUnit der Klasse [ElectionsDaoJdbc]
![]() |
![]() |
Die Klasse [Test01] ist die nachfolgende Testklasse der Klasse [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 {
// Schicht [DAO]
@Autowired
private IElectionsDao electionsDao;
@Before
public void init() {
// Die Tabelle [LISTES] wird bereinigt
// Wahllisten
ListeElectorale[] listes = electionsDao.getListesElectorales();
// Stimmen und Sitze werden auf 0 gesetzt und „false“ entfernt
int voix = 0;
int sièges = 0;
boolean elimine = false;
for (ListeElectorale liste : listes) {
liste.setVoix(voix);
liste.setSieges(sièges);
liste.setElimine(elimine);
}
// Diese Daten werden mithilfe der Schicht [dao] dauerhaft gespeichert
electionsDao.setListesElectorales(listes);
}
@Test
public void testElections01() {
...
}
}
- Zeile 17: Die Annotation [RunWith], die eine Annotation [JUnit] (Zeile 6) ist, sorgt über die Klasse [SpringJUnit4ClassRunner] für die Integration mit Spring;
- Zeile 16: Die Annotation [SpringApplicationConfiguration] ist eine Annotation [Spring] (Zeile 8), mit der die Konfigurationsklassen des Tests JUnit festgelegt werden können. Hier wird die Klasse [AppConfig] angegeben, die zur Konfiguration des Projekts verwendet wird. Damit stehen alle Spring-Objekte zur Verfügung, die durch diese Konfigurationsklasse definiert sind. Auf diese Weise kann in den Zeilen 21–22 eine Referenz auf die zu testende Schicht [DAO] injiziert werden;
- Zeile 25: Die Annotation [Before] bezeichnet eine Methode, die vor jedem Test ausgeführt werden muss;
- Zeilen 26–41: Die Methode [init] setzt die Stimmen und Sitze in den Listen der Tabelle [LISTES] auf Null und den Booleschen Wert [elimine] auf [false];
Die einzige Testmethode lautet wie folgt:
@Test
public void testElections01() {
System.out.println("testElections01-------------------------------------");
// Abruf der Wahlkonfiguration
ElectionsConfig electionsConfig = electionsDao.getElectionsConfig();
int nbSiegesAPourvoir = electionsConfig.getNbSiegesAPourvoir();
double seuilElectoral = electionsConfig.getSeuilElectoral();
Assert.assertEquals(6, nbSiegesAPourvoir);
Assert.assertEquals(0.05, seuilElectoral, 1E-6);
// Wahllisten
ListeElectorale[] listes = electionsDao.getListesElectorales();
// Anzeige der ausgelesenen Werte
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]);
}
// Stimmen und Sitze werden den Listen zugewiesen
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;
}
// Diese Daten werden mithilfe der Schicht [dao] dauerhaft gespeichert
electionsDao.setListesElectorales(listes);
// Die Daten werden erneut ausgelesen
ListeElectorale[] listesElectorales2 = electionsDao.getListesElectorales();
// Die gelesenen Daten werden überprüft
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: Es wird sichergestellt, dass der Inhalt der Tabelle [CONF] abgerufen werden kann;
- Zeilen 11–19: Der Inhalt der Tabelle [LISTES] wird angezeigt. Hier finden keine Tests statt, sondern lediglich eine Sichtprüfung;
- Zeilen 21–35: Man ändert in der Datenbank die Tabelle [LISTES], indem man den Listen Werte für ihre Felder [voix, sieges, elimine] zuweist;
- Zeilen 37–51: Die Tabelle [LISTES] wird erneut ausgelesen und überprüft, ob das Ergebnis tatsächlich mit den eingegebenen Werten übereinstimmt;
- Zeilen 52–55: Sichtprüfung;
Die mit einer implementierten Schicht [DAO] erzielten Konsolenergebnisse lauten wie folgt:
- Zeilen 1–23: Spring-Test-Protokolle;
- Zeilen 25–26: Inhalt der Tabelle [CONF];
- Zeilen 27–34: der ursprüngliche Inhalt der Tabelle [LISTES];
- Zeilen 35–42: Inhalt der Tabelle [LISTES] nach der Zuweisung von Werten zu den Feldern der Tabelle [voix, sieges, elimine];
Außerdem ist der Test JUnit erfolgreich:
![]() |
7.11. Erstellung des Archivs [with-dependencies] aus der Ebene [dao]
Das endgültige Projekt weist folgende Architektur auf:
![]() |
Zur Erinnerung: Die Maven-Konfiguration des Projekts lautet:
<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>
<!-- Bibliothek 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-Protokollierung -->
<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>
<!-- zur Installation des Projekt-Artefakts im lokalen 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 generieren, die die Klassen aller oben genannten JAR-Dateien sowie die des Projekts der Schicht [DAO] enthält. Dies geschieht durch eine Änderung in der Datei [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>
- Zeilen 25–37: Konfigurieren ein Maven-Plugin zur Erstellung der JAR-Datei;
- Zeile 15: gibt die für die Kompilierung zu verwendende Java-Version an;
Sobald diese Änderung vorgenommen wurde, kann die JAR-Datei wie folgt generiert werden: [1-10]:
![]() |
![]() |
- in [5], den Projektordner über die Schaltfläche [6] auswählen;
- in [7] einen Namen für die Ausführungskonfiguration vergeben;
- in [8] die Liste der auszuführenden Maven-Ziele angeben:
- [clean]: Leert den Ordner [target] des Projekts, in dem die generierte JAR-Datei abgelegt werden soll;
- [compile]: kompiliert das Projekt;
- [assembly:single]: Erzeugt eine einzige JAR-Datei, die alle Klassen des Projekts und seiner Abhängigkeiten enthält;
![]() |
- Bei [9-10] stellen Sie sicher, dass Sie über ein JDK (Java Development Kit) und nicht über ein JRE (Java Runtime Environment) verfügen. Der Unterschied besteht darin, dass JDK über einen Compiler verfügt, JRE hingegen nicht. Wenn Sie kein JDK haben, müssen Sie in [10] zusammen mit [11] eines hinzufügen. Befolgen Sie dazu die in Abschnitt 3.1 beschriebene Vorgehensweise;
![]() |
- in [17] führen Sie die Ausführungskonfiguration durch;
- in [13] das generierte Archiv;
Dieses Archiv kann mit einem Entpacker geöffnet werden:
![]() |
7.12. Test des Archivs der Schicht [DAO]
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] [2] ein. Es treten zahlreiche Fehler auf, da die Klasse „[Main]“ auf Klassen verweist, die nicht in ihrem [Classpath] enthalten sind. Wir werden dieses Paket nun anpassen.
Zunächst erstellen wir im neuen Projekt einen Ordner „[lib]“ und darin die Datei „[2-8]“:
![]() |
![]() |
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]“; wir haben das zuvor erstellte Archiv der Ebene „[DAO]“ importiert;
![]() |
- In [19] weist das Projekt keine Fehler mehr auf;
Nun kann die Klasse [Main] ausgeführt werden. Es werden die gleichen Ergebnisse wie zuvor erzielt.







































