Por que eu devo ler este artigo:Apresentaremos neste artigo como construir um sistema com alta qualidade, produtividade e escalabilidade utilizando como ponto de referência o padrão arquitetural MVC.

Além disso, buscaremos também uma melhor extensibilidade para o sistema, o que pode facilitar tarefas de manutenção e adição de novos recursos, o que será auxiliado pela adoção do DAO. Com estas boas práticas o leitor terá ciência de novas opções que podem ser adotadas em praticamente qualquer aplicação web.

Com a chegada da era da informação a velocidade com que o mercado muda o rumo dos negócios cresce rapidamente, e para atender essa demanda, o comportamento empresarial também teve que se adaptar.

Diante disso, as empresas passaram a investir cada vez mais em tecnologias que potencializassem os seus negócios e os desenvolvedores, como consequência, começaram a adotar novos frameworks que possibilitassem a construção de sistemas web capazes de lidar com conteúdo mais dinâmicos, oferecer interfaces mais atraentes, maneiras mais amigáveis de navegar por suas páginas e que fossem capazes de se adaptar mais facilmente às mudanças.

Neste momento, os sistemas também passaram a lidar com quantidades cada vez maiores de informações, de forma que em situações extremas, tornou-se um importante requisito a gerência do uso da memória.

Pensando nisso, apresentamos na primeira parte deste artigo as técnicas de virtualização e paginação para geração de relatórios em sistemas Java, o que foi feito com o uso do JasperReports e do Hibernate.

Neste artigo, evoluiremos a estrutura do simulador quiz já desenvolvida, de modo que ao término da implementação ela se torne uma arquitetura de alta escalabilidade. Para isso, utilizaremos o padrão arquitetural MVC, onde organizaremos os principais componentes da aplicação em camadas (Modelo Visão Controlador).

Veremos também como implementar o padrão de projetos DAO, que isolará toda a camada de dados da aplicação, viabilizando assim uma melhora da extensibilidade do sistema.

Isso significa que o software terá uma maior capacidade de incorporar mudanças sem comprometer drasticamente outros elementos da arquitetura.

Finalmente, para que não precisemos administrar as transações em diversos pontos do sistema, centralizaremos o seu gerenciamento em um único lugar e deixaremos que o Tomcat se encarregue por este controle.

Criando o servlet de carga de dados

O leitor mais atento deve ter encontrado algumas ausências de imports em três listagens do artigo anterior. Vamos a elas:

  • Na Listagem 8, atualize o import java.util.List para java.util.*;
  • Na Listagem 12, adicione import java.util.Date;
  • Na Listagem 17, adicione import br.com.javamagazine.simulador.domain.Pergunta.

Dando continuidade ao desenvolvimento do simulador quiz, implementaremos agora o servlet responsável por efetuar a carga automática dos dados da aplicação, que é um procedimento independente e necessário para o correto funcionamento do sistema.

Normalmente os dados seriam carregados dinamicamente via SGBD, mas para simplificar o exemplo, o faremos com recursos do Hibernate.

Para isso, utilizaremos o EntityManager, que possibilita executar operações com o banco de dados da mesma forma que o faríamos diretamente através de scripts SQL.

Sendo assim, crie um pacote de nome br.com.javamagazine.simulador.persistence e depois um servlet chamado InitServlet. Feito isso, substitua o código gerado pelo Eclipse para ficar semelhante ao da Listagem 1.


package br.com.javamagazine.simulador.persistence;
//imports omitidos...

@WebServlet(urlPatterns={"/pagina_inicial.xhtml"},loadOnStartup=1)
public class InitServlet extends HttpServlet {
private EntityManager em;
private EntityTransaction tx;
private Integer qtdeCargaBaseDados;
private Double porcentualAcerto;     

public void init(ServletConfig config) throws ServletException {              
  this.em = HibernateUtil.getEntityManager();
  this.tx = this.em.getTransaction();
  this.qtdeCargaBaseDados = Integer.parseInt(config 
  .getServletContext().getInitParameter("qtde_dados_carga_base"));
  this.porcentualAcerto = Integer.parseInt(config 
  .getServletContext().getInitParameter("porcentual_acerto_avaliacao"));
}

protected void doGet(HttpServletRequest request, 
HttpServletResponse response) throws ServletException, IOException {
this.inicializarTemas();
this.inicializarPerguntas();
this.inicializarRespostas();
this.inicializarAvaliacoes();
RequestDispatcher rd = request.getRequestDispatcher("/avaliacao/simulador.xhtml");
rd.forward(request, response);
}

private int sortear(int limite) {
   return (int) ((Math.random() * limite - 1) + 1);
}

private void inicializarTemas() {
   tx.begin();
   em.persist(new Tema("Tema 1"));
   em.persist(new Tema("Tema 2"));
   tx.commit();
}

private void inicializarPerguntas() {
    List<Tema> temas = (this.em.createNamedQuery(Tema.LISTAR_TEMAS, 
    Tema.class).getResultList());
    List<NivelEnum> niveis = Arrays.asList(NivelEnum.values());
    List<TipoQuestaoEnum> tipos = Arrays.asList(TipoQuestaoEnum.values());
    for (Tema tema : temas) {
     for (int i=1; i<=this.qtdeCargaBaseDados; i++) {
       NivelEnum nivel = niveis.get(this.sortear(niveis.size()));
       TipoQuestaoEnum tipoQuestao = tipos.get(this.sortear(tipos.size()));
           tx.begin();
           em.persist(new Pergunta("Pergunta " + i, nivel, tipoQuestao, tema));
       tx.commit();
     }
    }
}

private void inicializarRespostas() {
  List<Tema> temas = (this.em.createNamedQuery(Tema.LISTAR_TEMAS, 
  Tema.class).getResultList());
    for (Tema tema : temas) {
        List<Pergunta> perguntas = (this.em.createNamedQuery(
        Pergunta.LISTAR_TEMA_PERGUNTAS, Pergunta.class).setParameter("tema", 
        tema).getResultList());
        for (int i=0; i<perguntas.size(); i++) {
         for (int j=0; j<this.qtdeCargaBaseDados; j++) {
             tx.begin();
             em.persist(new Resposta("Reposta " + j, perguntas.get(j), 
             this.isCorreta()));
                 tx.commit();
        }
     }
  }
}

private void inicializarAvaliacoes() {
  List<Tema> temas = (this.em.createNamedQuery(Tema.LISTAR_TEMAS, 
  Tema.class).getResultList());
  for (int i=0; i<temas.size(); i++) {
   List<Pergunta> perguntas = (this.em.createNamedQuery(Pergunta.
   LISTAR_TEMA_PERGUNTAS, Pergunta.class).setParameter("tema", temas.get(i))).getResultList();
   tx.begin();
   this.em.persist(new Avaliacao("Avaliacao " + i, perguntas, perguntas.size(), 
   Cronometro.configurarTempoAvaliacao(0, 1, 30), temas.get(i)));
   tx.commit();
  }
}

public void destroy() {
   HibernateUtil.closeEntityManagerFactory();
}
}
Listagem 1. Código da classe InitServlet

Ao compilar esse código podemos verificar que temos um erro de compilação. No entanto, deixaremos assim até implementar a classe HibernateUtil, mais adiante.

Os principais trechos deste código são explicados a seguir:

  • Linha 04: A anotação @WebServlet faz com que o container execute este servlet assim que o usuário acessar a página inicial da aplicação, mapeada no atributo urlPatterns;
  • Linha 05: Estendemos a classe abstrata HttpServlet. Desta forma, devemos codificar pelo menos um de seus métodos de serviço, neste exemplo codificamos o doGet();
  • Linhas 12 e 13: Inicializamos as propriedades em e tx através da classe utilitária HibernateUtil, que será implementada mais adiante;
  • Linhas 14 e 15: Inicializamos as variáveis qtdeCargaBaseDados e porcentualAcerto com valores obtidos de parâmetros provenientes do arquivo web.xml. Fizemos desta forma para incentivar o uso desta prática, que torna o sistema mais flexível a mudanças.

Assim podemos modificar o valor dos parâmetros sem ter a necessidade de alterar o código fonte da aplicação. O único procedimento necessário para que as alterações surtam efeito é a reinicialização do servidor;

  • Linha 18: Chama o método de serviço doGet(), utilizado para realizar a carga automática dos dados na seguinte ordem de prioridade: temas, perguntas, respostas e avaliações. Ao final do procedimento de carga, fazemos, através do método forward(), com que a solicitação seja encaminhada para a página onde ocorrerá a avaliação;
  • Linhas 27, 28 e 29: Como a carga de dados é feita automaticamente, optamos por selecionar as informações de forma aleatória para montar os objetos que serão cadastrados no banco de dados.

Por exemplo: uma pergunta deverá ter um nível de dificuldade. Para selecionar qual nível essa pergunta terá, enviamos para o método sortear() a quantidade de níveis disponíveis e ele se encarregará de retornar um número aleatório entre zero e o valor recebido como parâmetro.

O retorno deste método será utilizado como índice para selecionar um nível em uma lista. Também adotamos esta lógica para selecionar objetos que representam os tipos de questões (simples, múltipla escolha ou arrastar e soltar);

  • Linha 31 a 36: Realizamos o cadastro dos temas;
  • Linha 38 a 51: Realizamos o cadastro das perguntas;
  • Linha 53 a 65: Realizamos o cadastro das respostas;
  • Linha 67 a 75: Realizamos o cadastro das avaliações do simulador;
  • Linha 78: De acordo com o ciclo de vida de um servlet, o último método a ser executado é o destroy(). Desta forma, liberamos aqui todos os recursos utilizados pelo Hibernate durante a carga automática dos dados.

Criando o controlador de transações e a fábrica de DAOs

Nosso próximo passo é criar as classes que darão suporte à aplicação nas operações com o banco de dados. Nestas classes implementaremos toda a lógica de acesso ao MySQL e então delegaremos o controle das transações para o container.

Nossa intenção ao adotar um controlador de transações é centralizar todo esse controle em uma só classe.

Para fazer com que o Tomcat seja o gerente destas transações, implementaremos nesta classe a interface Filter, como veremos mais adiante.

Para isso, crie um pacote, de nome br.com.javamagazine.simulador.persistence, e depois uma classe, chamada HibernateUtil. Da mesma forma que fizemos anteriormente, modifique o código gerado pelo Eclipse para que fique semelhante ao da Listagem 2.


package br.com.javamagazine.simulador.persistence;

import javax.persistence.*;
import org.hibernate.Session;

public class HibernateUtil {

            private static final String PERSISTENCE_UNIT_NAME = "simuladorPU";
            private static ThreadLocal<EntityManager> manager = 
            new ThreadLocal<EntityManager>();
      private static EntityManagerFactory factory;

      public static boolean isEntityManagerOpen() {
           return HibernateUtil.manager.get() != null && 
           HibernateUtil.manager.get().isOpen();
      }

      public static void closeEntityManagerFactory() {
           closeEntityManager();
           HibernateUtil.factory.close();
      }

      public static void closeEntityManager() {
           EntityManager em = HibernateUtil.manager.get();
           if (em != null) {
             EntityTransaction tx = em.getTransaction();
             if (tx.isActive()) {
                         tx.commit();
             }
             em.close();
             HibernateUtil.manager.set(null);
             HibernateUtil.manager.remove();
           }
      }

      public static EntityManager getEntityManager() {
        if (HibernateUtil.factory == null) {
           HibernateUtil.factory = Persistence.createEntityManagerFactory(
           PERSISTENCE_UNIT_NAME);
        }
        EntityManager em = HibernateUtil.manager.get();
        if (em == null || !em.isOpen()) {
           em = HibernateUtil.factory.createEntityManager();
           HibernateUtil.manager.set(em);
        }
        return em;
      }
}
Listagem 2. Código da classe HibernateUtil

Para ter acesso à unidade de persistência, declaramos na linha 8 uma variável estática do tipo String e a inicializamos com o mesmo valor da tag persistence-unit, definida no arquivo persistence.xml. Na linha 9 instanciamos a variável de classe manager, que será adicionada no escopo ThreadLocal dentro do método getEntityManager().

Desta forma, teremos uma única instância de EntityManager disponível para uso durante toda a execução de uma solicitação e será possível compartilhá-la com outros componentes da aplicação envolvidos no tratamento desta requisição. Por exemplo, ao selecionar uma avaliação na página simulador.xhtml, consultamos todas as suas perguntas e suas respectivas respostas utilizando apenas uma instância de EntityManager.

Na linha 10 declaramos a variável factory, do tipo EntityManagerFactory, pois somente com ela conseguimos recuperar uma instância de EntityManager. Já na linha 13 codificamos o método isEntityManagerOpen(), onde checamos se a conexão com a base de dados está aberta. Entre as linhas 16 e 32, implementamos os métodos closeEntityManagerFactory() e closeEntityManager(), que se encarregarão de fechar a conexão com o banco de dados e liberar os recursos utilizados pelo Hibernate.

Já no método getEntityManager(), mais precisamente na linha 36, obtemos de fato o acesso à unidade de persistência da aplicação. Nas linhas 40 e 41 criamos uma instância de EntityManager e a adicionamos de fato no escopo ThreadLocal para que tenhamos apenas uma ocorrência da mesma por solicitação no sistema. E para finalizar, na linha 43 retornamos o EntityManager preparado para efetuar operações no MySQL.

Dando continuidade ao desenvolvimento do simulador, criaremos agora a classe TransactionFilter, que terá a função de delegar o controle das transações para o Tomcat.

Por ...

Quer ler esse conteúdo completo? Tenha acesso completo