Skip to content

7. [TD]: Implementação da camada [DAO] do TD com o API JDBC

Palavras-chave: bancos de dados relacionais, API, JDBC, SQLException.

Vamos revisar a arquitetura em camadas do nosso aplicativo:

Os dados necessários para a eleição são armazenados em um banco de dados MySQL [dbelections]

7.1. Support

A pasta [support / chap-07] [1] contém:

  • os projetos Eclipse deste capítulo [2];
  • o script SQL para a criação das bases de dados MySQL, [dbelections] e [3];

7.2. O banco de dados [dbelections]


Tarefa a ser realizada: crie as bases de dados MySQL e [dblelections] seguindo o procedimento descrito no parágrafo 6.4.2.


O banco de dados [dbelections] é um banco de dados MySQL com duas tabelas:

  

A tabela [conf] contém as informações de configuração da eleição:

 
  • [id]: chave primária autoincrementada;
  • [version]: número da versão do registro — pode ser ignorado aqui;
  • [sap]: número de vagas a serem preenchidas;
  • [seuilelectoral]: o limite abaixo do qual uma lista é eliminada;

Seu conteúdo é o seguinte:

 

A tabela [listes] contém as listas de candidatos da eleição:

 
  • [id]: chave primária autoincrementada;
  • [version]: número da versão do registro — pode ser ignorado aqui;
  • [nom]: nome da lista;
  • [voix]: voz da lista — só é conhecida após a entrada do usuário na camada [présentation];
  • [sieges]: número de cadeiras obtidas — só é conhecido após o cálculo da camada [métier];
  • [elimine]: igual a 1 se a lista for eliminada, igual a 0 caso contrário — só é conhecido após o cálculo da camada [métier];

O conteúdo da tabela [listes] é o seguinte:

 

O script SQL para gerar a base [dbelections] chama-se [dbelections.sql] e está no servidor. Seu código é o seguinte:


-- phpMyAdmin SQL Dump
-- versão 4.0.4
-- http://www.phpmyadmin.net
--
-- Cliente: localhost
-- Gerado em: quarta-feira, 11 de março de 2015, às 12h20
-- Versão do servidor: 5.6.12-log
-- Versão do 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 */;

--
-- Banco de dados: `dbelections`
--
CREATE DATABASE IF NOT EXISTS `dbelections` DEFAULT CHARACTER SET utf8 COLLATE utf8_swedish_ci;
USE `dbelections`;

-- --------------------------------------------------------

--
-- Estrutura da tabela `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 ;

--
-- Conteúdo da tabela `conf`
--

INSERT INTO `conf` (`id`, `version`, `sap`, `seuilelectoral`) VALUES
(1, 1, 6, 0.05);

-- --------------------------------------------------------

--
-- Estrutura da tabela `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 ;

--
-- Conteúdo da tabela `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. O projeto Eclipse

O projeto Eclipse da camada [DAO] será o seguinte:

  
  • o pacote [elections.dao.entities] contém os objetos manipulados pela camada [DAO];
  • o pacote [elections.dao.service] contém a implementação da camada [DAO];
  • o pacote [elections.dao.config] contém a configuração da camada [DAO]
  • o pacote [elections.dao.junit] contém uma classe de teste JUnit do projeto;
  • o pacote [elections.dao.console] contém uma classe executável de teste;

7.4. Configuração do projeto Maven

 

O arquivo [pom.xml] que configura o projeto Maven é o seguinte:


<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>
        <!-- biblioteca 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>
        <!-- Teste do Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Logging do Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
    </dependencies>

    <!-- plug-ins -->
    <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>
            <!-- para a instalação do artefato do projeto no repositório local do Maven -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
</project>

Este arquivo é semelhante ao descrito no parágrafo 6.5.1. Foram feitas as seguintes alterações:

  • linhas 8-12: foi definido um projeto Maven pai. O projeto [spring-boot-starter-parent] (linha 10) define um grande número de dependências com suas respectivas versões. Ao utilizar uma delas (linhas 19-57), não é necessário especificar a versão, uma vez que esta já está definida no projeto Maven pai;
  • linhas 40-51: dependências necessárias para a classe de teste do projeto. Essas dependências possuem o atributo [<scope>test</scope>], o que significa que são necessárias apenas para as classes da pasta [src / test / java]. Essas dependências não serão incluídas no arquivo final do projeto;
  • linhas 53-56: a biblioteca [spring-boot-starter-logging] será usada pelo Spring para gerar logs na tela;
  • linhas 14-17: propriedades de configuração do Maven:
    • linha 15: indica que os arquivos-fonte estão codificados em UTF-8;
    • linha 16: indica que o compilador a ser utilizado deve ser da versão 1.8;

7.5. As entidades da camada [DAO]

  
  • [ElectionsConfig] é o modelo de objeto associado a uma linha da tabela [CONF];
  • [ListeElectorale] é o modelo de objeto associado a uma linha da tabela [LISTES];
  • [AbstactEntity] é a classe pai das duas classes anteriores. Ela agrupa os campos [id, version] comuns às duas classes;
  • [ElectionsException] é uma classe de exceção;

7.5.1. A classe [ElectionsException]

  

A classe [ElectionsException] foi descrita no parágrafo 4.3. Repetimos seu código:


package ...;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

// classe de exceção para o aplicativo Eleições
// a exceção é não controlada

public class ElectionsException extends RuntimeException implements Serializable {

    // série ID
    private static final long serialVersionUID = 1L;

    // campos locais
    private int code;
    private List<String> erreurs;

    // construtores
    public ElectionsException() {
        super();
    }

    public ElectionsException(int code, Throwable e) {
        // pai
        super(e);
        // local
        this.code = code;
        this.erreurs = getErreursForException(e);
    }

    public ElectionsException(int code, String message, Throwable e) {
        // pai
        super(message,e);
        // local
        this.code = code;
        this.erreurs = getErreursForException(e);
    }

    public ElectionsException(int code, String message) {
        // pai
        super(message);
        // local
        this.code = code;
        List<String> erreurs = new ArrayList<>();
        erreurs.add(message);
        this.erreurs = erreurs;
    }

    public ElectionsException(int code, List<String> erreurs) {
        // pai
        super();
        // local
        this.code = code;
        this.erreurs = erreurs;
    }

    // lista de mensagens de erro de uma exceção
    private List<String> getErreursForException(Throwable th) {
        // recupera-se a lista de mensagens de erro da exceção
        Throwable cause = th;
        List<String> erreurs = new ArrayList<>();
        while (cause != null) {
            // recupera-se a mensagem somente se ela != null e não estiver vazia
            String message = cause.getMessage();
            if (message != null) {
                message = message.trim();
                if (message.length() != 0) {
                    erreurs.add(message);
                }
            }
            // causa a seguir
            cause = cause.getCause();
        }
        return erreurs;
    }

    // getters e setters
...
}

Um objeto [ElectionsException] é caracterizado por duas informações:

  • linha 16: um código de erro;
  • linha 17: uma lista de mensagens de erro associadas à pilha de exceções que ocorreram;
  • a classe possui 5 construtores (linhas 20, 24, 32, 40, 50);
  • linhas 59-76: o método [getErreursForException] permite recuperar as mensagens de erro da pilha de exceções;

7.5.2. A classe [AbstractEntity]

  

A classe [AbstractEntity] é a seguinte:


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;

    // campos
    protected Long id;
    protected Long version;

    // construtores
    public AbstractEntity() {

    }

    public AbstractEntity(Long id, Long version) {
        this.id = id;
        this.version = version;
    }

    // assinatura
    public String toString() {
        try {
            return new ObjectMapper().writeValueAsString(this);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }
    
    // getters e setters
...
}

Ela armazena os campos [ID, NOM] das linhas das tabelas [CONF] e [LISTES] (linhas 8-9).

  • linha 8: a classe possui o atributo [Abstract], indicando que não pode ser instanciada. Ela só pode ser derivada;
  • linhas 26-33: assinatura jSON do objeto;
  • linha 28: retorna-se a cadeia jSON de [this]. Se, no momento da execução, [this] representar um objeto derivado de [AbstactEntity], é a cadeia jSON do objeto derivado que é obtida. Assim, as classes derivadas não precisarão definir um método [toString]. O método da classe pai é suficiente;

7.5.3. A classe [ElectionsConfig]

  

A classe [ElectionsConfig] é a seguinte:


package elections.dao.entities;


public class ElectionsConfig extends AbstractEntity {

    private static final long serialVersionUID = 1L;
    // campos
    private int nbSiegesAPourvoir;
    private double seuilElectoral;

    // construtores
    public ElectionsConfig() {
    }

    public ElectionsConfig(Long id, Long version, int nbSiegesAPourvoir, double seuilElectoral) {
        // pai
        super(id, version);
        // campos locais
        this.nbSiegesAPourvoir = nbSiegesAPourvoir;
        this.seuilElectoral = seuilElectoral;
    }

    // getters e setters
...
}
  • linha 4: a classe estende a classe [AbstractEntity];
  • linhas 8-9: armazenam as informações das colunas [SAP, SEUILELECTORAL] da tabela [CONF];

7.5.4. A classe [ListeElectorale]

  

A classe [ListeElectorale] é a seguinte:


package elections.dao.entities;

public class ListeElectorale extends AbstractEntity {

    // campos
    private String nom;
    private int voix;
    private int sieges;
    private boolean elimine;

    // construtores
    public ListeElectorale() {
    }

    public ListeElectorale(String nom, int voix, int sieges, boolean elimine) {
        // pai
        super();
        // campos locais
        initNom(nom);
        initVoix(voix);
        initSieges(sieges);
        this.elimine=elimine;
    }

    public ListeElectorale(Long id, Long version, String nom, int voix, int sieges, boolean elimine) {
        // pai
        super(id, version);
        // campos locais
        initNom(nom);
        initVoix(voix);
        initSieges(sieges);
        this.elimine=elimine;
    }

    // métodos privados
    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 e 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;
    }

}
  • linha 3: a classe estende a classe [AbstractEntity];
  • linhas 6-9: a classe armazena as colunas [NOM, VOIX, SIEGES, ELIMINE] da tabela [LISTES];

7.6. Configuração Spring da camada [DAO]

 

A classe [AppConfig] é uma classe de configuração do Spring que configura o acesso ao banco de dados da seguinte maneira:


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 {

    // constantes
    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() {
        // fonte de dados TomcatJdbc
        DataSource dataSource = new DataSource();
        // configuração de acesso JDBC
        dataSource.setDriverClassName(DRIVER_CLASSNAME);
        dataSource.setUsername(USER);
        dataSource.setPassword(PASSWD);
        dataSource.setUrl(URL);
        // uma conexão inicialmente aberta
        dataSource.setInitialSize(1);
        // resultado
        return dataSource;
    }

    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("electionsConfig");
    }
}
  • linhas 25-38: o acesso à BD será feito por meio de uma fonte de dados [tomcat-jdbc]. Esse tipo de fonte foi utilizado e explicado no parágrafo 6.5;
  • linhas 17-23: um conjunto de constantes estáticas acessíveis por todas as classes do projeto;
  • linha 13: a anotação [@Configuration] transforma a classe [AppConfig] em uma classe de configuração do Spring;
  • linha 11: a anotação [@ComponentScan] indica os pacotes onde é possível encontrar objetos Spring. Aqui, vamos definir um objeto Spring no pacote [dao]. A anotação [@ComponentScan] transforma a classe em uma classe de configuração, o que nos poupa de ter que colocar a anotação [@Configuration];
  • a linha 12 ativa o gerenciamento de um cache. O princípio é o seguinte:
    • colocamos a anotação [@Cacheable('nom_du_cache')] em um método M. O 'nome_do-cache' é o nome utilizado na linha 41;
    • quando o método M é chamado pela primeira vez, seus resultados são retornados e também armazenados no cache nomeado pela anotação;
    • quando o método M é chamado nas vezes seguintes com os mesmos parâmetros da primeira vez, ele não é executado e o Spring se limita a retornar os valores armazenados no cache;

7.7. Configuração dos logs

As bibliotecas de logs são definidas pela seguinte dependência em [pom.xml]:


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

Essa dependência traz as seguintes bibliotecas:

  

A biblioteca [logback] é responsável pelos logs. Ela é configurada por dois arquivos:

  • [logback.xml] para o ramo principal do código;
  • [logback-test.xml] para o ramo de teste do código. Na ausência desse arquivo, o arquivo anterior é utilizado;

Esses dois arquivos devem estar localizados no diretório [Classpath] do projeto. Por esse motivo, eles são colocados na pasta:

  • [src / main / ressources] para o ramo principal do código;
  • [src / test / ressources] para o ramo de teste do código;

O conteúdo dos arquivos é o mesmo:


<configuration> 

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 
    <!-- os codificadores recebem, por padrão, o tipo
         ch.qos.logback.classic.encoder.PatternLayoutEncoder -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- controle do nível dos logs -->
  <root level="info"> <!-- info, debug, warn -->
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Tudo acontece na linha 12, onde se define o nível de informação desejado:

  • [debug]: o nível mais detalhado;
  • [off]: sem logs;
  • [info]: nível normal de logs;
  • [warn]: igual ao nível [info], mais as mensagens de aviso (warning). Essas mensagens indicam a possibilidade de um erro;

Mude para o modo [debug] assim que surgirem erros durante a execução.

7.8. Implementação da camada [DAO]

  

A interface [IDao] da camada [DAO] é a seguinte:


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 {
    // configuração das eleições
    public ElectionsConfig getElectionsConfig();

    // as listas concorrentes
    public ListeElectorale[] getListesElectorales();

    // salvamento dos resultados da eleição
    public void setListesElectorales(ListeElectorale[] listesElectorales);

}

O esboço da classe [ElectionsDaoJdbc] que implementa a camada [dao] com um banco de dados será o seguinte:


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")
    // obtenção da configuração da eleição
    public ElectionsConfig getElectionsConfig() {
        throw new RuntimeException("[getElectionsConfig] not yet implemented");
    }

    // obtenção das listas
    public ListeElectorale[] getListesElectorales() {
        throw new RuntimeException("[getListesElectorales] not yet implemented");
    }

    // alteração das listas [voix, sieges, elimine]
    public void setListesElectorales(ListeElectorale[] listesElectorales) {
        throw new RuntimeException("[setListesElectorales] not yet implemented");
    }

    // -------------------- métodos privados

    // gerenciamento do finally
    private ElectionsException doFinally(int code, ResultSet rs, PreparedStatement ps, Connection connexion) {
        // fechamento ResultSet
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e1) {

            }
        }
        // fechamento [PreparedStatement]
        if (ps != null) {
            try {
                ps.close();
            } catch (SQLException e2) {

            }
        }
        // encerrar a conexão
        if (connexion != null) {
            try {
                connexion.close();
            } catch (SQLException e3) {
                // retorna um [ElectionsException]
                return new ElectionsException(code, e3);
            }
        }
        // sem exceção
        return null;
    }

    // gestão do catch
    private ElectionsException doCatchException(int code1, int code2, Connection connexion, Throwable th) {
        // é gerado um [ElectionsException]
        ElectionsException ex1 = new ElectionsException(code1, th);
        // cancelamento da transação
        try {
            if (connexion != null) {
                connexion.rollback();
            }
        } catch (SQLException e2) {
        }
        // retorna a exceção
        return ex1;
    }
}
  • linha 24: a anotação [@Cacheable] é uma anotação do Spring que solicita que os resultados do método [getElectionsConfig] sejam armazenados em memória (“armazenados em cache”). É possível fazer isso aqui porque o conteúdo da tabela [CONF] nunca muda. Não seria possível aplicar essa anotação ao método [getListesElectorales], pois o conteúdo da tabela [LISTES] muda ao longo do tempo;
  • os métodos [doCatchException] e [doFinally] retornam um tipo [ElectionsException]. O método [doFinally] retorna um ponteiro null se a liberação dos recursos ocorreu sem erros;

Tarefa: escreva a classe [ElectionDaoJdbc] inspirando-se na classe [IntroJdbc02] estudada no parágrafo 6.5.2.


7.9. A classe de teste [Main]

  

A classe [Main] é a seguinte:


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 {

    // fonte de dados
    private static IElectionsDao dao;

    public static void main(String[] args) {
        // recupera-se a referência da camada [DAO] após instanciar o contexto Spring
        try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class)) {
            // recuperação da fonte de dados
            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);
            }
            // fim
            return;
        }
        // leitura da BD
        ElectionsConfig electionsConfig = null;
        ListeElectorale[] listes;
        try {
            // conteúdo das duas tabelas
            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);
            }
            // fim
            return;
        }
        // tudo correu bem — exibição
        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);
        }
    }

    // métodos privados ------------------
    private static List<String> getErreursForThrowable(Throwable th) {
        // recuperamos a lista de mensagens de erro da exceção
        Throwable cause = th;
        List<String> erreurs = new ArrayList<>();
        while (cause != null) {
            // recuperamos a mensagem somente se ela for !=null e não estiver vazia
            String message = cause.getMessage();
            if (message != null) {
                message = message.trim();
                if (message.length() != 0) {
                    erreurs.add(message);
                }
            }
            // próxima causa
            cause = cause.getCause();
        }
        return erreurs;
    }

}
  • linhas 21-31: recuperação de uma referência na camada [DAO];
  • linha 21: utiliza-se uma sintaxe chamada try_with_resources. Sua sintaxe é a seguinte:

try (T ressource=expression) {
            // exploração de recurso
...
}
  • (continuação)
    • o tipo T da linha 1 deve implementar a interface [java.lang.AutoCloseable];
    • ao sair do bloco das linhas 1 a 4, o recurso do tipo [java.lang.AutoCloseable] é automaticamente liberado, independentemente de ter ocorrido uma exceção ou não. Quem conhece a linguagem C# reconhecerá aqui um equivalente sintático e funcional da cláusula using (T recurso = expressão);
  • linhas 40-49: uso da camada [DAO] para obter o conteúdo das tabelas [CONF] e [LISTES];
  • linhas 41-47: testa-se o cache [electionsconfig]. Este foi definido em dois locais:
    • na classe [ElectionsDaoJdbc]:

  @Cacheable("electionsConfig")
  public ElectionsConfig getElectionsConfig() {
  • (continuação)
    • na classe de configuração [AppConfig]:

@EnableCaching
public class AppConfig {
...
  @Bean
  public CacheManager cacheManager() {
    return new ConcurrentMapCacheManager("electionsConfig");
  }
}
  • linha 59: fechamento do contexto Spring;
  • linhas 62-67: exibição das informações obtidas;

Os resultados obtidos com uma camada [DAO] implementada são os seguintes:

...
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}
  • linhas 2-4: mostram a influência do cache:
    • a consulta 1 leva 80 milissegundos;
    • a consulta 2 leva 1 milissegundo;

Quando o banco de dados está desativado, obtêm-se os seguintes resultados:

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. Testes JUnit da classe [ElectionsDaoJdbc]

  

A classe [Test01] é a classe de teste seguinte à [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 {

    // camada [DAO]
    @Autowired
    private IElectionsDao electionsDao;

    @Before
    public void init() {
        // limpa-se a tabela [LISTES]
        // listas concorrentes
        ListeElectorale[] listes = electionsDao.getListesElectorales();
        // zeramos os votos e os assentos e definimos como “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);
        }
        // tornamos esses dados persistentes por meio da camada [dao]
        electionsDao.setListesElectorales(listes);
    }

    @Test
    public void testElections01() {
        ...
    }
}
  • linha 17: a anotação [RunWith], que é uma anotação [JUnit] (linha 6), garante a integração com o Spring por meio da classe [SpringJUnit4ClassRunner];
  • linha 16: a anotação [SpringApplicationConfiguration] é uma anotação [Spring] (linha 8) que permite designar as classes de configuração do teste JUnit. Aqui, designa-se a classe [AppConfig] utilizada para configurar o projeto. Assim, dispõe-se de todos os objetos Spring definidos por essa classe de configuração. É dessa forma que se pode injetar, nas linhas 21-22, uma referência na camada [DAO] que será testada;
  • linha 25: a anotação [Before] indica um método que deve ser executado antes de cada teste;
  • linhas 26-41: o método [init] zera os votos e assentos das listas da tabela [LISTES] e define o booleano [elimine] como [false];

O único método de teste é o seguinte:


@Test
    public void testElections01() {
        System.out.println("testElections01-------------------------------------");
        // recuperação da configuração das eleições
        ElectionsConfig electionsConfig = electionsDao.getElectionsConfig();
        int nbSiegesAPourvoir = electionsConfig.getNbSiegesAPourvoir();
        double seuilElectoral = electionsConfig.getSeuilElectoral();
        Assert.assertEquals(6, nbSiegesAPourvoir);
        Assert.assertEquals(0.05, seuilElectoral, 1E-6);

        // listas em disputa
        ListeElectorale[] listes = electionsDao.getListesElectorales();
        // exibição dos valores lidos
        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]);
        }

        // atribuição de votos e cadeiras às listas
        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;
        }

        // esses dados são tornados persistentes por meio da camada [dao]
        electionsDao.setListesElectorales(listes);

        // releitura dos dados
        ListeElectorale[] listesElectorales2 = electionsDao.getListesElectorales();
        // verifica-se os dados lidos
        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]);
        }
    }
  • linhas 5-9: verifica-se se é possível recuperar o conteúdo da tabela [CONF];
  • linhas 11-19: exibe-se o conteúdo da tabela [LISTES]. Não há testes aqui, apenas uma verificação visual;
  • linhas 21-35: modificamos no banco de dados a tabela [LISTES], atribuindo às listas os valores para seus campos [voix, sieges, elimine];
  • linhas 37-51: relê-se a tabela [LISTES] e verifica-se se o resultado obtido é realmente igual ao que foi inserido;
  • linhas 52-55: verificação visual;

Os resultados na console obtidos com uma camada [DAO] implementada são os seguintes:

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}
  • linhas 1-23: logs do Spring Test;
  • linhas 25-26: o conteúdo da tabela [CONF];
  • linhas 27-34: o conteúdo inicial da tabela [LISTES];
  • linhas 35-42: o conteúdo da tabela [LISTES] após a atribuição de valores aos campos [voix, sieges, elimine];

Além disso, o teste JUnit foi bem-sucedido:

 

7.11. Criação do arquivo [with-dependencies] a partir da camada [dao]

O projeto final possui a seguinte arquitetura:

Vale lembrar a configuração do Maven do projeto:


<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>
        <!-- biblioteca 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>
        <!-- Teste do Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Logging do Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>
    </dependencies>

    <!-- plug-ins -->
    <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>
            <!-- para a instalação do artefato do projeto no repositório local do Maven -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.18.1</version>
            </plugin>
        </plugins>
    </build>
</project>
  

Vamos gerar um único arquivo JAR que conterá as classes de todos os JARs acima, além das do projeto da camada [DAO]. Isso é feito por meio de uma modificação no arquivo [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>

    <!-- plug-ins -->
    <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>
  • linhas 25-37: configuram um plugin do Maven para gerar o JAR;
  • linha 15: indica a versão do Java a ser usada para a compilação;

Depois de fazer essa alteração, é possível gerar o arquivo jar da seguinte maneira [1-10]:

  • em [5], selecione a pasta do projeto usando o botão [6];
  • em [7], atribuir um nome à configuração de execução;
  • em [8], insira a lista de tarefas do Maven a serem executadas:
    • [clean]: esvazia a pasta [target] do projeto, na qual será colocado o arquivo JAR gerado;
    • [compile]: compila o projeto;
    • [assembly:single]: gera um único arquivo JAR contendo todas as classes do projeto e de suas dependências;
  • no [9-10], verifique se você possui um JDK (Java Development Kit) e não um JRE (Java Runtime Environment). A diferença é que o JDK possui um compilador, ao passo que o JRE não possui. Se você não tiver o JDK, será necessário adicioná-lo ao [10] junto com o [11]. Para isso, siga o procedimento descrito no parágrafo 3.1;
  • no [17], execute a configuração de execução;
  • em [13], o arquivo compactado gerado;

É possível abrir esse arquivo compactado com um programa de descompactação:

 

7.12. Teste do arquivo da camada [DAO]

Vamos criar um projeto Eclipse padrão (não Maven) [1]:

Vamos copiar e colar o pacote [dao.console] do projeto [elections-dao-jdbc-01] no projeto [elections-dao-jdbc-02] [2]. Aparecem vários erros porque a classe [Main] faz referência a classes que não estão em seu [Classpath]. Vamos modificar este último.

Primeiro, criamos a pasta [lib] no novo projeto:

No [9], colocamos o arquivo criado na etapa anterior na pasta [lib] e, em seguida, alteramos o Build Path do projeto:

  • para [18]; importamos o arquivo da camada [DAO] criado anteriormente;
  • no [19], o projeto não apresenta mais erros;

É possível, então, executar a classe [Main]. Obtêm-se os mesmos resultados que anteriormente.