7. [TD]: Implementacja warstwy [DAO] klasy TD przy użyciu klasy API i JDBC
Słowa kluczowe: relacyjne bazy danych, API, JDBC, SQLException.
Wróćmy do architektury warstwowej naszej aplikacji:
![]() |
Dane niezbędne do przeprowadzenia wyborów są zapisane w bazie danych MySQL [dbelections]
7.1. Support
![]() |
Folder [support / chap-07] [1] zawiera:
- projekty Eclipse z tego rozdziału [2];
- skrypt SQL służący do tworzenia bazy MySQL, [dbelections], [3];
7.2. Baza danych [dbelections]
Zadanie: należy utworzyć bazę danych MySQL oraz [dblelections], postępując zgodnie z procedurą opisaną w punkcie 6.4.2.
Baza danych [dbelections] jest bazą danych MySQL zawierającą dwie tabele:
![]() |
Tabela [conf] zawiera informacje dotyczące konfiguracji wyborów:
![]() |
- [id]: autoinkrementujący klucz główny;
- [version]: numer wersji rekordu – można go tutaj pominąć;
- [sap]: liczba mandatów do obsadzenia;
- [seuilelectoral]: próg, poniżej którego lista jest eliminowana;
Jej zawartość jest następująca:
![]() |
Tabela [listes] zawiera listy kandydatów w wyborach:
![]() |
- [id]: autoinkrementujący się klucz główny;
- [version]: numer wersji rekordu – można go tutaj pominąć;
- [nom]: nazwa listy;
- [voix]: głos z listy – znany dopiero po wprowadzeniu danych przez użytkownika w warstwie [présentation];
- [sieges]: liczba uzyskanych mandatów – znana dopiero po obliczeniu warstwy [métier];
- [elimine]: przyjmuje wartość 1, jeśli lista została wyeliminowana, w przeciwnym razie przyjmuje wartość 0 – znana jest dopiero po obliczeniu warstwy [métier];
Zawartość tabeli [listes] jest następująca:
![]() |
Skrypt SQL służący do generowania bazy [dbelections] nosi nazwę [dbelections.sql] i znajduje się na serwerze. Jego kod jest następujący:
-- phpMyAdmin SQL Zrzut
-- wersja 4.0.4
-- http://www.phpmyadmin.net
--
-- Klient: localhost
-- Wygenerowano: środa, 11 marca 2015 r. o godz. 12:20
-- Wersja serwera: 5.6.12-log
-- Wersja 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 */;
--
-- Baza danych: `dbelections`
--
CREATE DATABASE IF NOT EXISTS `dbelections` DEFAULT CHARACTER SET utf8 COLLATE utf8_swedish_ci;
USE `dbelections`;
-- --------------------------------------------------------
--
-- Struktura tabeli `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 ;
--
-- Zawartość tabeli `conf`
--
INSERT INTO `conf` (`id`, `version`, `sap`, `seuilelectoral`) VALUES
(1, 1, 6, 0.05);
-- --------------------------------------------------------
--
-- Struktura tabeli `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 ;
--
-- Zawartość tabeli `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. Projekt Eclipse
![]() |
Projekt Eclipse warstwy [DAO] będzie wyglądał następująco:
![]() |
- pakiet [elections.dao.entities] zawiera obiekty przetwarzane przez warstwę [DAO];
- pakiet [elections.dao.service] zawiera implementację warstwy [DAO];
- pakiet [elections.dao.config] zawiera konfigurację warstwy [DAO]
- pakiet [elections.dao.junit] zawiera klasę testową JUnit z projektu;
- pakiet [elections.dao.console] zawiera klasę testową do uruchomienia;
7.4. Konfiguracja projektu Maven
![]() |
Plik [pom.xml], który konfiguruje projekt Maven, wygląda następująco:
<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>
<!-- biblioteka jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Test Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Rejestrowanie w Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- wtyczki -->
<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>
<!-- do instalacji artefaktu projektu w lokalnym repozytorium Maven -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
Plik ten jest analogiczny do opisanego w punkcie 6.5.1. Wprowadzono w nim następujące zmiany:
- wiersze 8–12: zdefiniowano nadrzędny projekt Maven. Projekt [spring-boot-starter-parent] (wiersz 10) definiuje bardzo dużą liczbę zależności wraz z ich wersjami. W przypadku korzystania z jednej z nich (wiersze 19–57) nie ma potrzeby podawania jej wersji, ponieważ jest ona zdefiniowana w nadrzędnym projekcie Maven;
- wiersze 40–51: zależności niezbędne dla klasy testowej projektu. Zależności te mają atrybut [<scope>test</scope>], co oznacza, że są one wymagane wyłącznie dla klas z folderu [src / test / java]. Zależności te nie zostaną uwzględnione w ostatecznym archiwum projektu;
- wiersze 53–56: biblioteka [spring-boot-starter-logging] będzie wykorzystywana przez Spring do wyświetlania komunikatów na ekranie;
- wiersze 14–17: właściwości konfiguracyjne Mavena:
- wiersz 15: wskazuje, że pliki źródłowe są napisane w języku UTF-8;
- wiersz 16: wskazuje, że należy użyć kompilatora w wersji 1.8;
7.5. Elementy warstwy [DAO]
![]() |
- [ElectionsConfig] to model obiektowy powiązany z wierszem tabeli [CONF];
- [ListeElectorale] to model obiektowy powiązany z wierszem tabeli [LISTES];
- [AbstactEntity] jest klasą nadrzędną dla obu poprzednich klas. Wyodrębnia ona pola [id, version] wspólne dla obu klas;
- [ElectionsException] jest klasą wyjątków;
7.5.1. Klasa [ElectionsException]
![]() |
Klasa [ElectionsException] została opisana w punkcie 4.3. Poniżej podajemy jej kod:
package ...;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
// klasa wyjątku dla aplikacji „Elections”
// wyjątek jest niekontrolowany
public class ElectionsException extends RuntimeException implements Serializable {
// serial ID
private static final long serialVersionUID = 1L;
// pola lokalne
private int code;
private List<String> erreurs;
// konstruktory
public ElectionsException() {
super();
}
public ElectionsException(int code, Throwable e) {
// element nadrzędny
super(e);
// lokalny
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message, Throwable e) {
// nadrzędny
super(message,e);
// lokalny
this.code = code;
this.erreurs = getErreursForException(e);
}
public ElectionsException(int code, String message) {
// nadrzędny
super(message);
// lokalny
this.code = code;
List<String> erreurs = new ArrayList<>();
erreurs.add(message);
this.erreurs = erreurs;
}
public ElectionsException(int code, List<String> erreurs) {
// nadrzędny
super();
// lokalny
this.code = code;
this.erreurs = erreurs;
}
// lista komunikatów o błędach związanych z wyjątkiem
private List<String> getErreursForException(Throwable th) {
// pobierana jest lista komunikatów o błędach związanych z wyjątkiem
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// pobieramy komunikat tylko wtedy, gdy !=null i nie jest pusty
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// następująca przyczyna
cause = cause.getCause();
}
return erreurs;
}
// metody pobierające i ustawiające
...
}
Obiekt [ElectionsException] charakteryzują dwie informacje:
- wiersz 16: kod błędu;
- wiersz 17: lista komunikatów o błędach powiązanych ze stosem wystąpionych wyjątków;
- klasa posiada 5 konstruktorów (wiersze 20, 24, 32, 40, 50);
- wiersze 59–76: metoda [getErreursForException] pozwala pobrać komunikaty o błędach ze stosu wyjątków;
7.5.2. Klasa [AbstractEntity]
![]() |
Klasa [AbstractEntity] ma następującą postać:
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;
// pola
protected Long id;
protected Long version;
// konstruktory
public AbstractEntity() {
}
public AbstractEntity(Long id, Long version) {
this.id = id;
this.version = version;
}
// sygnatura
public String toString() {
try {
return new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
// metody pobierające i ustawiające
...
}
Zapisuje ona pola [ID, NOM] z wierszy tabel [CONF] i [LISTES] (wiersze 8–9).
- wiersz 8: klasa posiada atrybut [Abstract] wskazujący, że nie można jej instancjonować. Można ją jedynie dziedziczyć;
- wiersze 26–33: sygnatura obiektu jSON;
- wiersz 28: zwracany jest ciąg znaków jSON z [this]. Jeśli w momencie wykonania [this] reprezentuje obiekt pochodny od [AbstactEntity], uzyskuje się ciąg jSON obiektu pochodnego. Klasy pochodne nie będą zatem musiały definiować metody [toString]. Wystarczy metoda klasy nadrzędnej;
7.5.3. Klasa [ElectionsConfig]
![]() |
Klasa [ElectionsConfig] ma następującą postać:
package elections.dao.entities;
public class ElectionsConfig extends AbstractEntity {
private static final long serialVersionUID = 1L;
// pola
private int nbSiegesAPourvoir;
private double seuilElectoral;
// konstruktory
public ElectionsConfig() {
}
public ElectionsConfig(Long id, Long version, int nbSiegesAPourvoir, double seuilElectoral) {
// klasa nadrzędna
super(id, version);
// pola lokalne
this.nbSiegesAPourvoir = nbSiegesAPourvoir;
this.seuilElectoral = seuilElectoral;
}
// metody pobierające i ustawiające
...
}
- wiersz 4: klasa ta rozszerza klasę [AbstractEntity];
- wiersze 8–9: zapisują informacje z kolumn [SAP, SEUILELECTORAL] tabeli [CONF];
7.5.4. Klasa [ListeElectorale]
![]() |
Klasa [ListeElectorale] ma następującą postać:
package elections.dao.entities;
public class ListeElectorale extends AbstractEntity {
// pola
private String nom;
private int voix;
private int sieges;
private boolean elimine;
// konstruktory
public ListeElectorale() {
}
public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
// klasa nadrzędna
super();
// pola lokalne
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
public ListeElectorale(Long id, Long version, String nom, int voix, int sieges, boolean elimine) {
// klasa nadrzędna
super(id, version);
// pola lokalne
initNom(nom);
initVoix(voix);
initSieges(sieges);
this.elimine=elimine;
}
// metody prywatne
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");
}
}
// metody pobierające i ustawiające
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;
}
}
- wiersz 3: klasa rozszerza klasę [AbstractEntity];
- wiersze 6–9: klasa przechowuje kolumny [NOM, VOIX, SIEGES, ELIMINE] z tabeli [LISTES];
7.6. Konfiguracja Spring dla warstwy [DAO]
![]() |
![]() |
Klasa [AppConfig] jest klasą konfiguracyjną Springa, która konfiguruje dostęp do bazy danych w następujący sposób:
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 {
// stałe
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() {
// źródło danych TomcatJdbc
DataSource dataSource = new DataSource();
// konfiguracja dostępu JDBC
dataSource.setDriverClassName(DRIVER_CLASSNAME);
dataSource.setUsername(USER);
dataSource.setPassword(PASSWD);
dataSource.setUrl(URL);
// początkowo otwarte połączenie
dataSource.setInitialSize(1);
// wynik
return dataSource;
}
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- wiersze 25–38: dostęp do klasy BD będzie realizowany za pośrednictwem źródła danych [tomcat-jdbc]. Ten typ źródła został wykorzystany i wyjaśniony w paragrafie 6.5;
- wiersze 17–23: zbiór stałych statycznych dostępnych dla wszystkich klas projektu;
- wiersz 13: adnotacja [@Configuration] sprawia, że klasa [AppConfig] staje się klasą konfiguracyjną Springa;
- wiersz 11: adnotacja [@ComponentScan] wskazuje pakiety, w których można znaleźć obiekty Spring. W tym przypadku zdefiniujemy obiekt Spring w pakiecie [dao]. Adnotacja [@ComponentScan] sprawia, że klasa staje się klasą konfiguracyjną, co pozwala nam uniknąć umieszczania adnotacji [@Configuration];
- wiersz 12 włącza obsługę pamięci podręcznej. Zasada działania jest następująca:
- umieszczamy adnotację [@Cacheable('nom_du_cache')] na metodzie M. „nazwa_pamięci_podręcznej” to nazwa użyta w wierszu 41;
- gdy metoda M jest wywoływana po raz pierwszy, jej wyniki są zwracane, a także umieszczane w pamięci podręcznej o nazwie podanej w adnotacji;
- gdy metoda M jest wywoływana kolejno z tymi samymi parametrami co za pierwszym razem, nie jest ona wykonywana, a Spring po prostu zwraca wartości z pamięci podręcznej;
7.7. Konfiguracja logów
![]() |
Biblioteki logów są zdefiniowane przez następującą zależność w pliku [pom.xml]:
<!-- Rejestrowanie w Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Ta zależność powoduje załadowanie następujących bibliotek:
![]() |
Za generowanie logów odpowiada biblioteka [logback]. Jest ona konfigurowana za pomocą dwóch plików:
- [logback.xml] dla głównej gałęzi kodu;
- [logback-test.xml] dla gałęzi testowej kodu. W przypadku braku tego pliku wykorzystywany jest plik poprzedni;
Oba te pliki muszą znajdować się w katalogu [Classpath] projektu. Z tego powodu są one umieszczone w folderze:
- [src / main / ressources] dla głównej gałęzi kodu;
- [src / test / ressources] dla gałęzi testowej kodu;
Zawartość obu plików jest taka sama:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- kodery mają domyślnie przypisany typ
ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- kontrola poziomu logów -->
<root level="info"> <!-- info, debug, warn -->
<appender-ref ref="STDOUT" />
</root>
</configuration>
Wszystko dzieje się w wierszu 12, gdzie ustala się pożądany poziom szczegółowości:
- [debug]: najbardziej szczegółowy poziom;
- [off]: brak logów;
- [info]: normalny poziom logów;
- [warn]: taki sam poziom jak [info] plus komunikaty ostrzegawcze (warning). Komunikaty te wskazują na możliwość wystąpienia błędu;
Należy przejść do trybu [debug], gdy tylko podczas wykonywania pojawią się błędy.
7.8. Implementacja warstwy [DAO]
![]() |
![]() |
Interfejs [IDao] warstwy [DAO] wygląda następująco:
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 {
// konfiguracja wyborów
public ElectionsConfig getElectionsConfig();
// listy startujące w wyborach
public ListeElectorale[] getListesElectorales();
// zapisywanie wyników wyborów
public void setListesElectorales(ListeElectorale[] listesElectorales);
}
Szkielet klasy [ElectionsDaoJdbc] implementującej warstwę [dao] z bazą danych będzie wyglądał następująco:
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")
// pobieranie konfiguracji wyborów
public ElectionsConfig getElectionsConfig() {
throw new RuntimeException("[getElectionsConfig] not yet implemented");
}
// pobieranie list
public ListeElectorale[] getListesElectorales() {
throw new RuntimeException("[getListesElectorales] not yet implemented");
}
// modyfikacja list [voix, sieges, elimine]
public void setListesElectorales(ListeElectorale[] listesElectorales) {
throw new RuntimeException("[setListesElectorales] not yet implemented");
}
// -------------------- metody prywatne
// obsługa bloku finally
private ElectionsException doFinally(int code, ResultSet rs, PreparedStatement ps, Connection connexion) {
// zamykanie ResultSet
if (rs != null) {
try {
rs.close();
} catch (SQLException e1) {
}
}
// zamykanie [PreparedStatement]
if (ps != null) {
try {
ps.close();
} catch (SQLException e2) {
}
}
// zamknięcie połączenia
if (connexion != null) {
try {
connexion.close();
} catch (SQLException e3) {
// zwracamy [ElectionsException]
return new ElectionsException(code, e3);
}
}
// brak wyjątku
return null;
}
// obsługa catch
private ElectionsException doCatchException(int code1, int code2, Connection connexion, Throwable th) {
// generowany jest kod [ElectionsException]
ElectionsException ex1 = new ElectionsException(code1, th);
// anulowanie transakcji
try {
if (connexion != null) {
connexion.rollback();
}
} catch (SQLException e2) {
}
// zwracamy wyjątek
return ex1;
}
}
- wiersz 24: adnotacja [@Cacheable] jest adnotacją Springa, która nakazuje zapisanie w pamięci („zakrycie”) wyników metody [getElectionsConfig]. Jest to możliwe w tym przypadku, ponieważ zawartość tabeli [CONF] nigdy się nie zmienia. Nie byłoby możliwe umieszczenie tej adnotacji na metodzie [getListesElectorales], ponieważ zawartość tabeli [LISTES] zmienia się w czasie;
- metody [doCatchException] i [doFinally] zwracają typ [ElectionsException]. Metoda [doFinally] zwraca wskaźnik typu null, jeśli zwolnienie zasobów przebiegło bezbłędnie;
Zadanie: napisz klasę [ElectionDaoJdbc], wzorując się na klasie [IntroJdbc02] omówionej w paragrafie 6.5.2.
7.9. Klasa testowa [Main]
![]() |
![]() |
Klasa [Main] ma następującą postać:
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 {
// źródło danych
private static IElectionsDao dao;
public static void main(String[] args) {
// pobieranie referencji warstwy [DAO] po utworzeniu instancji kontekstu Spring
try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class)) {
// pobieranie źródła danych
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);
}
// koniec
return;
}
// odczyt tabeli BD
ElectionsConfig electionsConfig = null;
ListeElectorale[] listes;
try {
// zawartość obu tabel
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);
}
// koniec
return;
}
// wszystko poszło dobrze – wyświetlanie
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);
}
}
// metody prywatne ------------------
private static List<String> getErreursForThrowable(Throwable th) {
// pobieramy listę komunikatów o błędach związanych z wyjątkiem
Throwable cause = th;
List<String> erreurs = new ArrayList<>();
while (cause != null) {
// pobieramy komunikat tylko wtedy, gdy !=null i nie jest pusty
String message = cause.getMessage();
if (message != null) {
message = message.trim();
if (message.length() != 0) {
erreurs.add(message);
}
}
// następna przyczyna
cause = cause.getCause();
}
return erreurs;
}
}
- wiersze 21–31: pobieranie odwołania z warstwy [DAO];
- wiersz 21: stosuje się składnię o nazwie try_with_resources. Jej składnia jest następująca:
try (T ressource=expression) {
// wykorzystanie zasobów
...
}
- (ciąg dalszy)
- typ T z wiersza 1 musi implementować interfejs [java.lang.AutoCloseable];
- po zakończeniu bloku wierszy 1–4 zasób typu [java.lang.AutoCloseable] jest automatycznie zwalniany, niezależnie od tego, czy wystąpił wyjątek, czy nie. Osoby znające język C# rozpoznają w tym składniowego i funkcjonalnego odpowiednika klauzuli using (T zasób = wyrażenie);
- wiersze 40–49: wykorzystanie warstwy [DAO] w celu uzyskania zawartości tabel [CONF] i [LISTES];
- wiersze 41–47: sprawdzamy pamięć podręczną [electionsconfig]. Została ona zdefiniowana w dwóch miejscach:
- w klasie [ElectionsDaoJdbc]:
@Cacheable("electionsConfig")
public ElectionsConfig getElectionsConfig() {
- (ciąg dalszy)
- w klasie konfiguracyjnej [AppConfig]:
@EnableCaching
public class AppConfig {
...
@Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("electionsConfig");
}
}
- wiersz 59: zamknięcie kontekstu Spring;
- wiersze 62–67: wyświetlenie uzyskanych informacji;
Wyniki uzyskane przy zaimplementowanej warstwie [DAO] są następujące:
- wiersze 2–4: pokazują wpływ pamięci podręcznej:
- zapytanie 1 trwa 80 milisekund;
- zapytanie 2 trwa 1 milisekundę;
Gdy baza danych jest wyłączona, uzyskujemy następujące wyniki:
7.10. Testy JUnit klasy [ElectionsDaoJdbc]
![]() |
![]() |
Klasa [Test01] jest następcą klasy testowej [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 {
// warstwa [DAO]
@Autowired
private IElectionsDao electionsDao;
@Before
public void init() {
// czyścimy tabelę [LISTES]
// listy startowe
ListeElectorale[] listes = electionsDao.getListesElectorales();
// ustawiamy liczbę głosów i mandatów na 0 oraz ustawiamy wartość „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);
}
// zapisujemy te dane na stałe za pomocą warstwy [dao]
electionsDao.setListesElectorales(listes);
}
@Test
public void testElections01() {
...
}
}
- wiersz 17: adnotacja [RunWith], która jest adnotacją [JUnit] (wiersz 6), zapewnia integrację ze Springiem za pośrednictwem klasy [SpringJUnit4ClassRunner];
- wiersz 16: adnotacja [SpringApplicationConfiguration] jest adnotacją [Spring] (wiersz 8), która pozwala wskazać klasy konfiguracyjne testu JUnit. W tym miejscu wskazujemy klasę [AppConfig] używaną do konfiguracji projektu. Uzyskujemy w ten sposób dostęp do wszystkich obiektów Spring zdefiniowanych przez tę klasę konfiguracyjną. W ten sposób możemy w wierszach 21–22 wstrzyknąć odwołanie do warstwy [DAO], która ma zostać przetestowana;
- wiersz 25: adnotacja [Before] wskazuje metodę, która musi zostać wykonana przed każdym testem;
- wiersze 26–41: metoda [init] zeruje liczbę głosów i miejsc z list w tabeli [LISTES] oraz ustawia wartość logicznego [elimine] na [false];
Jedyna metoda testowa jest następująca:
@Test
public void testElections01() {
System.out.println("testElections01-------------------------------------");
// pobieranie konfiguracji wyborów
ElectionsConfig electionsConfig = electionsDao.getElectionsConfig();
int nbSiegesAPourvoir = electionsConfig.getNbSiegesAPourvoir();
double seuilElectoral = electionsConfig.getSeuilElectoral();
Assert.assertEquals(6, nbSiegesAPourvoir);
Assert.assertEquals(0.05, seuilElectoral, 1E-6);
// listy startujące w wyborach
ListeElectorale[] listes = electionsDao.getListesElectorales();
// wyświetlanie odczytanych wartości
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]);
}
// przypisywanie głosów i mandatów listom
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;
}
// zapisywanie tych danych w pamięci trwałej za pomocą warstwy [dao]
electionsDao.setListesElectorales(listes);
// ponowne odczytanie danych
ListeElectorale[] listesElectorales2 = electionsDao.getListesElectorales();
// sprawdza się odczytane dane
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]);
}
}
- wiersze 5–9: upewniamy się, że jesteśmy w stanie pobrać zawartość tabeli [CONF];
- wiersze 11–19: wyświetla się zawartość tabeli [LISTES]. Nie przeprowadza się tu żadnych testów, a jedynie kontrolę wizualną;
- wiersze 21–35: modyfikujemy w bazie tabelę [LISTES], nadając listom wartości dla ich pól [voix, sieges, elimine];
- wiersze 37–51: ponownie odczytuje się tabelę [LISTES] i sprawdza, czy uzyskany wynik jest zgodny z wprowadzonymi danymi;
- wiersze 52–55: weryfikacja wizualna;
Wyniki wyświetlane w konsoli po zaimplementowaniu warstwy [DAO] są następujące:
- wiersze 1–23: logi Spring Test;
- wiersze 25–26: zawartość tabeli [CONF];
- wiersze 27–34: początkowa zawartość tabeli [LISTES];
- wiersze 35–42: zawartość tabeli [LISTES] po przypisaniu wartości do pól [voix, sieges, elimine];
Ponadto test JUnit zakończył się powodzeniem:
![]() |
7.11. Utworzenie archiwum [with-dependencies] z warstwy [dao]
Ostateczny projekt ma następującą architekturę:
![]() |
Przypomnijmy konfigurację projektu w Maven:
<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>
<!-- biblioteka jSON -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
<!-- Test Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Rejestrowanie w Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
</dependencies>
<!-- wtyczki -->
<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>
<!-- do instalacji artefaktu projektu w lokalnym repozytorium Maven -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
![]() |
Wygenerujemy pojedynczy plik jar, który będzie zawierał klasy ze wszystkich powyższych plików jar oraz klasy z projektu warstwy [DAO]. Odbywa się to poprzez wprowadzenie zmiany w pliku [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>
<!-- wtyczki -->
<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>
- wiersze 25–37: konfigurują wtyczkę Maven do generowania pliku JAR;
- wiersz 15: określa wersję Javy, która ma być użyta do kompilacji;
Po wprowadzeniu tej zmiany można wygenerować plik jar w następujący sposób: [1-10]:
![]() |
![]() |
- w [5] należy wskazać folder projektu za pomocą przycisku [6];
- w pliku [7] nadać nazwę konfiguracji uruchomieniowej;
- w pliku [8] należy podać listę celów Maven do wykonania:
- [clean]: opróżnia folder [target] projektu, w którym zostanie umieszczony wygenerowany plik jar;
- [compile]: kompiluje projekt;
- [assembly:single]: generuje pojedynczy plik JAR zawierający wszystkie klasy projektu i jego zależności;
![]() |
- w przypadku [9-10] należy sprawdzić, czy posiadamy plik JDK (Java Development Kit), a nie JRE (Java Runtime Environment). Różnica polega na tym, że JDK zawiera kompilator, a JRE nie. Jeśli nie masz pakietu JDK, musisz dodać go do pakietu [10] wraz z pakietem [11]. W tym celu postępuj zgodnie z procedurą opisaną w punkcie 3.1;
![]() |
- w pliku [17] należy przeprowadzić konfigurację uruchomienia;
- w pliku [13] – wygenerowany plik archiwum;
Archiwum to można otworzyć za pomocą programu do rozpakowywania:
![]() |
7.12. Test archiwum warstwy [DAO]
Utwórzmy standardowy projekt Eclipse (nie Maven) [1]:
![]() |
Skopiujmy i wklejmy pakiet [dao.console] z projektu [elections-dao-jdbc-01] do projektu [elections-dao-jdbc-02] [2]. Pojawia się wiele błędów, ponieważ klasa [Main] odwołuje się do klas, których nie ma w jej pliku [Classpath]. Zmodyfikujemy ten plik.
Najpierw utworzymy klasę [2-8] oraz folder [lib] w nowym projekcie:
![]() |
![]() |
W [9] umieszczamy archiwum utworzone w poprzednim kroku w folderze [lib], a następnie modyfikujemy ścieżkę kompilacji projektu:
![]() |
![]() |
![]() |
- na [18], zaimportowaliśmy archiwum warstwy [DAO] utworzone wcześniej;
![]() |
- w pliku [19] projekt nie zawiera już żadnych błędów;
Można zatem uruchomić klasę [Main]. Uzyskuje się te same wyniki, co poprzednio.







































