Skip to content

7. [TD]: Implementation of the [DAO] layer of the TD using the JDBC API

Keywords: relational databases, JDBC API, SQLException.

Let’s revisit the layered architecture of our application:

The data required for the election is stored in a MySQL database [dbelections]

7.1. Support

The [support / chap-07] folder [1] contains:

  • the Eclipse projects for this chapter [2];
  • the SQL script for creating the MySQL database [dbelections] [3];

7.2. The [ dbelections] database


Task: Create the MySQL database [dblelections] by following the procedure described in section 6.4.2.


The [dbelections] database is a MySQL database with two tables:

  

The [conf] table contains the election configuration information:

 
  • [id]: auto-incrementing primary key;
  • [version]: record version number—can be ignored here;
  • [sap]: number of seats to be filled;
  • [votingThreshold]: the threshold below which a list is eliminated;

Its contents are as follows:

 

The [listes] table contains the candidate lists for the election:

 
  • [id]: auto-incrementing primary key;
  • [version]: record version number—can be ignored here;
  • [name]: name of the list;
  • [votes]: votes for the list—known only after user input in the [presentation] layer;
  • [seats]: number of seats won—known only after calculation by the [business] layer;
  • [eliminated]: 1 if the list is eliminated, 0 otherwise—known only after the [business] layer is calculated;

The contents of the [listes] table are as follows:

 

The SQL script to generate the [dbelections] database is named [dbelections.sql] and is on the server. Its code is as follows:


-- 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. The Eclipse Project

The Eclipse project for the [DAO] layer will be as follows:

  
  • the [elections.dao.entities] package contains the objects manipulated by the [DAO] layer;
  • the [elections.dao.service] package contains the implementation of the [DAO] layer;
  • the [elections.dao.config] package contains the configuration for the [DAO] layer
  • the [elections.dao.junit] package contains a JUnit test class for the project;
  • the [elections.dao.console] package contains an executable test class;

7.4. Maven Project Configuration

 

The [pom.xml] file that configures the Maven project is as follows:


<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>

This file is similar to the one described in Section 6.5.1. The following changes have been made:

  • lines 8–12: a parent Maven project has been defined. The [spring-boot-starter-parent] project (line 10) defines a large number of dependencies along with their versions. When using one of them (lines 19–57), there is no need to specify the version, as it is defined in the parent Maven project;
  • lines 40–51: dependencies required for the project’s test class. These dependencies have the [<scope>test</scope>] attribute, meaning they are only required for classes in the [src/test/java] folder. These dependencies will not be included in the final project archive;
  • lines 53–56: the [spring-boot-starter-logging] library will be used by Spring to log to the console;
  • lines 14–17: Maven configuration properties:
    • line 15: specifies that the source files are encoded in UTF-8;
    • line 16: specifies that the compiler version must be 1.8;

7.5. Entities in the [DAO] layer

  
  • [ElectionsConfig] is the object model associated with a row in the [CONF] table;
  • [VoterList] is the object model associated with a row in the [LISTS] table;
  • [AbstactEntity] is the parent class of the two preceding classes. It encapsulates the [id, version] fields common to both classes;
  • [ElectionsException] is an exception class;

7.5.1. The [ElectionsException] class

  

The [ElectionsException] class was described in Section 4.3. Here is its code again:


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
...
}

An [ElectionsException] object is characterized by two pieces of information:

  • line 16: an error code;
  • line 17: a list of error messages associated with the stack trace of the exceptions that occurred;
  • the class has 5 constructors (lines 20, 24, 32, 40, 50);
  • lines 59–76: the [getErrorsForException] method retrieves the error messages from the exception stack;

7.5.2. The [AbstractEntity] class

  

The [AbstractEntity] class is as follows:


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
...
}

It stores the [ID, NAME] fields of the rows in the [CONF] and [LISTS] tables (lines 8–9).

  • line 8: the class has the [Abstract] attribute indicating that it cannot be instantiated. It can only be derived;
  • lines 26–33: JSON signature of the object;
  • line 28: the JSON string of [this] is returned. If, at runtime, [this] represents an object derived from [AbstractEntity], the JSON string of the derived object is obtained. Derived classes will thus not need to define a [toString] method. The one from the parent class is sufficient;

7.5.3. The [ElectionsConfig] class

  

The [ElectionsConfig] class is as follows:


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
...
}
  • line 4: the class extends the [AbstractEntity] class;
  • lines 8-9: store the information from the [SAP, SEUILELECTORAL] columns of the [CONF] table;

7.5.4. The [VoterList] class

  

The [VoterList] class is as follows:


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;
    }
 
}
  • line 3: the class extends the [AbstractEntity] class;
  • lines 6–9: the class stores the columns [NAME, VOTES, SEATS, ELIMINATED] from the [LISTS] table;

7.6. Spring configuration of the [DAO] layer

 

The [AppConfig] class is a Spring configuration class that configures database access as follows:


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");
    }
}
  • lines 25–38: Access to the database will be via a data source [tomcat-jdbc]. This type of data source was used and explained in section 6.5;
  • lines 17–23: a set of static constants accessible by all classes in the project;
  • line 13: the [@Configuration] annotation makes the [AppConfig] class a Spring configuration class;
  • line 11: the [@ComponentScan] annotation specifies the packages where Spring objects can be found. Here, we will define a Spring object in the [dao] package. The [@ComponentScan] annotation makes the class a configuration class, saving us from having to add the [@Configuration] annotation;
  • Line 12 enables cache management. The principle is as follows:
    • we apply the [@Cacheable('cache_name')] annotation to a method M. The 'cache_name' is the name used on line 41;
    • when method M is called for the first time, its results are returned and also stored in the cache named by the annotation;
    • when method M is called subsequently with the same parameters as the first time, it is not executed, and Spring simply returns the cached values;

7.7. Log configuration

The logging libraries are defined by the following dependency in [pom.xml]:


        <!-- Spring Boot Logging -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

This dependency includes the following libraries:

  

The [logback] library handles logging. It is configured by two files:

  • [logback.xml] for the main code branch;
  • [logback-test.xml] for the test branch of the code. If this file is missing, the previous file is used instead;

These two files must be located in the project’s [Classpath]. For this reason, they are placed in the folder:

  • [src/main/resources] for the main code branch;
  • [src/test/resources] for the test branch of the code;

The contents of the files are the same here:


<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>

Everything happens on line 12, where we set the desired log level:

  • [debug]: the most detailed level;
  • [off]: no logs;
  • [info]: the normal logging level;
  • [warn]: same as [info] plus warning messages. These messages indicate a potential error;

Switch to [debug] mode as soon as errors appear during execution.

7.8. Implementation of the [DAO] layer

  

The [IDao] interface of the [DAO] layer is as follows:


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);
 
}

The skeleton of the [ElectionsDaoJdbc] class implementing the [dao] layer with a database will be as follows:


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;
    }
}
  • line 24: the [@Cacheable] annotation is a Spring annotation that instructs the system to cache the results of the [getElectionsConfig] method. This is possible here because the contents of the [CONF] table never change. It would not be possible to apply this annotation to the [getListesElectorales] method because the contents of the [LISTES] table change over time;
  • the [doCatchException] and [doFinally] methods return an [ElectionsException] type. The [doFinally] method returns a null pointer if the resources were released without errors;

Task: Write the [ElectionDaoJdbc] class, drawing inspiration from the [IntroJdbc02] class studied in Section 6.5.2.


7.9. The [Main] test class

  

The [Main] class is as follows:


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;
    }
 
}
  • lines 21–31: retrieving a reference to the [DAO] layer;
  • line 21: we use a syntax called try_with_resources. Its syntax is as follows:

try (T ressource=expression) {
            // exploitation de ressource
...
}
  • (continued)
    • The type T in line 1 must implement the [java.lang.AutoCloseable] interface;
    • Upon exiting the block of lines 1–4, the resource of type [java.lang.AutoCloseable] is automatically released, regardless of whether an exception occurred. Those familiar with the C# language will recognize this as a syntactic and functional counterpart to the using clause (T resource = expression);
  • Lines 40–49: Use of the [DAO] layer to retrieve the contents of the [CONF] and [LISTES] tables;
  • lines 41-47: the [electionsconfig] cache is tested. This cache has been defined in two places:
    • in the [ElectionsDaoJdbc] class:

  @Cacheable("electionsConfig")
  public ElectionsConfig getElectionsConfig() {
  • (continued)
    • in the [AppConfig] configuration class:

@EnableCaching
public class AppConfig {
...
  @Bean
  public CacheManager cacheManager() {
    return new ConcurrentMapCacheManager("electionsConfig");
  }
}
  • line 59: closing the Spring context;
  • lines 62–67: display of the retrieved information;

The results obtained with an implemented [DAO] layer are as follows:

...
début requête 1 : 11:09:29:752
fin requête 1 et début requête 2: 11:09:30:132
fin requête 2 : 11:09:30:133
...
Nombre de sièges à pourvoir : 6
Seuil électoral :  0,05
Listes candidates----------------
{"id":1,"version":9,"nom":"A","voix":32000,"sieges":2,"elimine":false}
{"id":2,"version":13,"nom":"B","voix":25000,"sieges":2,"elimine":false}
{"id":3,"version":14,"nom":"C","voix":16000,"sieges":1,"elimine":false}
{"id":4,"version":13,"nom":"D","voix":12000,"sieges":1,"elimine":false}
{"id":5,"version":14,"nom":"E","voix":8000,"sieges":0,"elimine":false}
{"id":6,"version":13,"nom":"F","voix":4500,"sieges":0,"elimine":true}
{"id":7,"version":13,"nom":"G","voix":2500,"sieges":0,"elimine":true}
  • Lines 2–4: show the impact of the cache:
    • Request 1 takes 80 milliseconds;
    • Query 2 takes 1 millisecond;

When the database is offline, the following results are obtained:

1
2
3
4
5
Les erreurs suivantes se sont produites lors de la lecture des tables ----------
Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
Connection refused: connect

7.10. JUnit tests for the [ ElectionsDaoJdbc] class

  

The [Test01] class is the following [JUnit] test class:


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() {
        ...
    }
}
  • line 17: the [RunWith] annotation, which is a [JUnit] annotation (line 6), ensures integration with Spring via the [SpringJUnit4ClassRunner] class;
  • line 16: the [SpringApplicationConfiguration] annotation is a [Spring] annotation (line 8) that allows you to specify the configuration classes for the JUnit test. Here, we specify the [AppConfig] class used to configure the project. We then have access to all the Spring objects defined by this configuration class. This is how we can inject, on lines 21–22, a reference to the [DAO] layer that will be tested;
  • line 25: The [Before] annotation indicates a method that must be executed before each test;
  • Lines 26–41: The [init] method sets the votes and seats in the [LISTES] table to zero and the [elimine] boolean to [false];

The only test method is as follows:


@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]);
        }
    }
  • lines 5-9: we ensure that we can retrieve the contents of the [CONF] table;
  • lines 11–19: we display the contents of the [LISTES] table. There are no tests here, only a visual check;
  • lines 21–35: we modify the [LISTES] table in the database by assigning values to the lists’ fields [voices, seats, eliminated];
  • lines 37–51: we read the [LISTES] table again and verify that the result matches what was entered;
  • lines 52–55: visual verification;

The console results obtained with an implemented [DAO] layer are as follows:

mars 11, 2015 4:50:00 PM org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
INFOS: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
mars 11, 2015 4:50:00 PM org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
INFOS: Could not instantiate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource]
mars 11, 2015 4:50:00 PM org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
INFOS: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
mars 11, 2015 4:50:00 PM org.springframework.test.context.support.DefaultTestContextBootstrapper instantiateListeners
INFOS: Could not instantiate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute]
mars 11, 2015 4:50:00 PM org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
INFOS: Using TestExecutionListeners: [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@483bf400, org.springframework.test.context.support.DirtiesContextTestExecutionListener@21a06946]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.2.RELEASE)

[2015-03-11 16:50:01.272] - 11696 INFOS [main] --- org.eclipse.jdt.internal.junit.runner.RemoteTestRunner: Starting RemoteTestRunner on Gportpers3 with PID 11696 (started by ST in D:\data\istia-1415\eclipse\intro-jdbc\elections-jdbc-01)
[2015-03-11 16:50:01.317] - 11696 INFOS [main] --- org.springframework.context.annotation.AnnotationConfigApplicationContext: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@74ad1f1f: startup date [Wed Mar 11 16:50:01 CET 2015]; root of context hierarchy
mars 11, 2015 4:50:01 PM org.eclipse.jdt.internal.junit.runner.RemoteTestRunner logStarted
INFOS: Started RemoteTestRunner in 0.775 seconds (JVM running for 1.433)
testElections01-------------------------------------
Nombre de sièges à pourvoir : 6
Seuil électoral : 0.05
Listes en compétition ---------------------
{"id":1,"version":21,"nom":"A","voix":0,"sieges":0,"elimine":false}
{"id":2,"version":22,"nom":"B","voix":0,"sieges":0,"elimine":false}
{"id":3,"version":21,"nom":"C","voix":0,"sieges":0,"elimine":false}
{"id":4,"version":13,"nom":"D","voix":0,"sieges":0,"elimine":false}
{"id":5,"version":17,"nom":"E","voix":0,"sieges":0,"elimine":false}
{"id":6,"version":18,"nom":"F","voix":0,"sieges":0,"elimine":false}
{"id":7,"version":19,"nom":"G","voix":0,"sieges":0,"elimine":false}
Listes en compétition ---------------------
{"id":1,"version":21,"nom":"A","voix":0,"sieges":0,"elimine":false}
{"id":2,"version":22,"nom":"B","voix":10,"sieges":1,"elimine":true}
{"id":3,"version":21,"nom":"C","voix":20,"sieges":2,"elimine":false}
{"id":4,"version":13,"nom":"D","voix":30,"sieges":3,"elimine":true}
{"id":5,"version":17,"nom":"E","voix":40,"sieges":4,"elimine":false}
{"id":6,"version":18,"nom":"F","voix":50,"sieges":5,"elimine":true}
{"id":7,"version":19,"nom":"G","voix":60,"sieges":6,"elimine":false}
  • lines 1-23: Spring Test logs;
  • lines 25-26: the contents of the [CONF] table;
  • lines 27-34: the initial contents of the [LISTES] table;
  • lines 35-42: the contents of the [LISTES] table after assigning values to the [voix, sieges, elimine] fields;

Additionally, the JUnit test passes:

 

7.11. Creation of the [with-dependencies] archive for the [dao] layer

The final project has the following architecture:

Let’s review the project’s Maven configuration:


<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>
  

We will generate a single JAR file that will contain the classes from all of the above JARs plus those from the [DAO] layer project. This is done by making a change to the [pom.xml] file:


<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>
  • lines 25–37: configure a Maven plugin to generate the JAR;
  • line 15: specifies the Java version to use for compilation;

Once this change is made, you can generate the JAR as follows [1-10]:

  • In [5], select the project folder using the button [6];
  • in [7], give the build configuration a name;
  • in [8], enter the list of Maven tasks to execute:
    • [clean]: clears the project’s [target] folder where the generated JAR will be placed;
    • [compile]: compiles the project;
    • [assembly:single]: generates a single JAR file containing all the classes of the project and its dependencies;
  • in [9-10], verify that you have a JDK (Java Development Kit) and not a JRE (Java Runtime Environment). The difference is that the JDK includes a compiler, whereas the JRE does not. If you do not have a JDK, you must add one in [10] using [11]. To do this, follow the procedure described in section 3.1;
  • In [17], run the build configuration;
  • in [13], the generated archive;

You can open this archive with an unzipper:

 

7.12. Testing the [DAO] layer archive

Let’s create a standard Eclipse project (not Maven) [1]:

Let's copy and paste the [dao.console] package from the [elections-dao-jdbc-01] project into the [elections-dao-jdbc-02] project [2]. Several errors appear because the [Main] class references classes that are not in its [Classpath]. We will modify the [Classpath].

First, we create [2-8] a [lib] folder in the new project:

In [9], we place the archive created in the previous step into the [lib] folder, then modify the project’s Build Path:

  • in [18], we imported the archive of the [DAO] layer created previously;
  • in [19], the project no longer shows any errors;

We can then run the [Main] class. We get the same results as before.