terça-feira, 14 de agosto de 2012

Desenvolvendo uma aplicação Java EE 6 com Eclipse (parte 1)


[linu.com.br] - [parte 1] - [parte 2] - [parte 3] - [parte 4] - lgapontes@gmail.com

Introdução
Caros amigos leitores, nós estamos aqui, diante de mais uma jornada. Jornada essa interessante para ambos os lados: do escritor e do leitor.
Recentemente a nova versão EE do Java foi disponibilizada. Como de costume, alguns sites, revistas e papers vinham antecedendo os itens que seriam acrescidos à Enterprise Edition do Java, e assim como você, eu vinha acompanhando essas profecias e aguardava seu lançamento para estudar com a devida dedicação. Agora, portanto, que temos em mãos uma gama de novos recursos disponíveis, uma nova fronteira a ser alcançada, faremos um estudo dirigido da forma que considero mais consistente: um estudo prático.
Faremos aqui uma aplicação simples, com o objeto de tratar algumas dessas novidades com conceitos e aplicação. No final teremos uma ideia do que esse novo alicate suíço tem para nos oferecer.
 
 
Ferramentas necessárias
Antes de iniciarmos o debate prático do Java, vamos indicar que itens serão necessários para nosso picknick.
 
 
Esses são nossas ferramentas. Não entraremos em detalhes específicos para a instalação individual de cada elemento.
Caso queira maiores informações, veja o tutorial http://linubr.blogspot.com.br/2012/08/configurando-eclipse-para-java-ee-6.html.
Nós trabalharemos com o Eclipse, mas caso você esteja acostumado a trabalhar com outras IDEs (como o Netbeans, por exemplo), fique à vontade. Nenhum tópico aqui mostrado é de exclusividade do Eclipse. Na verdade, mantenho minha opinião sobre isso: o cara é o Java, a IDE é apenas para facilitar o trabalho. Se você for louco e tiver tempo pra isso, pode até construir aplicações EE na unha. O resultado será o mesmo.
 
 
Sistema-Exemplo
Trataremos agora dos requisitos do sistema-exemplo. Este será um simples sistema de fórum, onde usuários podem enviar comentários sobre determinado tópico. Certamente não apresentaremos aqui a melhor forma de se construir um fórum em Java - como disse, é um sistema de exemplo. Vou tentar usar o maior número de recursos da nova versão Java EE 6 para construir o sistema.
Atenção: Não tenho em mente o que será necessário para construir esse sistema. Faremos a análise e o desenvolvimento em tempo real - enquanto estou escrevendo o artigo. Acho que assim fica mais emocionante :)
Bom, nosso sistema de fórum será conhecido como SuperFórum. Abaixo temos o logo do sistema.
 
 
Caso queira construir uma réplica desse sistema, baixe a figura.
 
 
Requisitos funcionais
Os requisitos funcionais abaixo deverão ser atendidos:
 
  1. Cada tópico será composto por um título, por um texto (tópico em si), pelo nome do autor e data de criação;
  2. Depois de postado, o tópico poderá receber comentários de outros usuários;
  3. Os comentários serão formados por texto, data de criação e nome do usuário;
  4. Usuários terão a palavra "Visitante" associado ao tópico e/ou comentário caso não informe seu nome;
  5. A primeira página do sistema deverá mostrar uma relação dos tópicos cadastrados, ordenados em relação à data de postagem, do mais recente para o menos recente; Além disso a listagem de tópicos deve exibir a quantidade de comentários associados;
  6. O título de um Tópico deve ser único no sistema;
  7. Não poderão ser enviados comentários do usuário criador do tópico (ou seja, quem criou o tópico não pode comentá-lo).
  8. Deve existir uma ferramenta de busca que ordenará os tópicos pela quantidade de ocorrências de uma palavra. Neste caso, para facilitar, apenas uma palavra poderá ser pesquisada por vez.
 
Bom, acho que de requisitos funcionais, já temos o suficiente. Eventualmente faremos modificações nessa análise - se necessário. Conforme havíamos previsto, esse sistema não tenta, de forma alguma, ser a fórmula-secreta de fóruns para web. Apenas ilustramos casos típicos em sistemas desse porte para abordarmos os elementos do Java EE 6.
 
 
Modelagem
Abaixo temos o diagrama de classes que representa o modelo do sistema.
 
 
Trata-se de um modelo simples, mas que atende aos requisitos levantados. Não faremos outros diagramas para representar o modelo.
Vamos fazer uma breve descrição: os tópicos indicados nos requisitos são representados (é lógico) pela classe Topic. Cada Topic poderá estar associado com Comment (comentários), porém Topic não enxerga essa associação. A classe Metadata foi criada para possibilitar o mecanismo de pesquisa. Fica claro que num caso real de sistema de fórum, existiria uma entidade para cadastrar os usuários. Aqui, para facilitar, não será necessário prévio cadastro dos usuários - por isso a necessidade do atributo (de fato, mal modelado) userName. Veja que optei em colocar uma associação unidirecional entre Topic e Comment. Essa também poderia ter sido considerada bidirecional, caso a equipe de modelagem ache necessário.
 
 
Criando o projeto no Eclipse
Os projetos EE geralmente são criados em pacotes diferentes, de acordo com suas responsabilidades. Porém, uma das inovações do EE 6 é que projetos Web podem conter características de projeto EJB - além dos recursos web. Apesar deste novo recurso, aqui criaremos três projetos - como sugere a especificação.
Neste item mostraremos os passos para criar/configurar esses projetos. Primeiro, vamos criar o projeto EAR.
 
No Eclipse, clique em File, New, Other...
 
 
Na janela de criação de projetos, dentro da pasta Java EE, selecione Enterprise Application Project. Clique em Next
 
 
Em Project Name, defina o valor SuperForum. Atente-se em configurar os outros itens conforme os dados apresentados na figura acima. Clique em Finish.
Nosso projeto EAR já está pronto. Vamos criar agora o projeto EJB.
 
Clique em File, New, Other... 
 
 
Selecione a opção EJB Project dentro da pasta EJB. Clique em Next 
 
 
Preencha o item Project Name com o valor SuperForumEJB. Atente-se em configurar o projeto conforme os dados acima. Adicione o projeto ao EAR criado anteriormente marcando a opção Add project to an EAR e selecionando a opção SuperForum em EAR project name. Clique em Next
 
 
Nessa janela, apenas clique em Next
 
 
Nessa janela, desmarque a opção Create an EJB Client JAR module to hold the client interfaces and classes e marque a opção Generate ejb-jar.xml deployment descriptor. Por fim clique em Finish
Vamos configurar o projeto SuperForumEJB para trabalhar com os recursos de JPA.
 
Clique com o botão direito sobre o projeto SuperForumEJB (no Project Explorer do Eclipse) e selecione a opção Properties
 
 
Na janela das propriedades do projeto, selecione a opção Project Facets, marque o item Java Persistence e clique em Apply. Em seguida, clique em OK
Por fim, vamos criar o projeto Web.
 
Clique em File, New, Other... 
 
 
Selecione a opção Web e o tipo de projeto Dynamic Web Project. Clique em Next
 
 
Defina o valor SuperForumWeb no campo Project Name. Observe que esse projeto também deverá ser adicionado ao EAR, portanto preencha corretamente a definição de EAR membership conforme mostrado acima. Clique em Finish
Vamos agora configurar esse projeto para utilizar o SuperForumEJB.
 
Clique com o botão direito sobre o projeto SuperForumWeb, selecione o menu Build Path, Configure Build Path...
 
 
Selecione a aba Project, clique em Add...
 
 
Marque o projeto SuperForumEJB e clique em OK
 
 
Por fim, clique em OK 
Agora nossa estrutura de projetos no Eclipse está pronta para ser utilizada. Vamos à programação.
 
 
Desenvolvendo as classes do Modelo
Vamos agora programar as classes. O modelo deste projeto será construído através do recurso de mapeamento objeto-relacional já presente no EJB 3 do Java EE 5. Para maiores detalhes, veja http://linubr.blogspot.com.br/2012/08/entendendo-persistencia-de-dados-e.html. Outros recursos novos serão acrescidos e devidamente comentados, quando necessário.
Todas as classes de modelo serão criadas no projeto SuperForumEJB. Vamos criar agora o pacote model.
 
Clique com o botão direito no projeto SuperForumEJB, opção New, opção Package
 
 
No campo Name, defina o valor br.com.linu.forum.model e clique em Finish
Vamos agora criar as entidades. Não entrarei em detalhes sobre o funcionamento das anotações já presentes no Java EE 5. Caso você não tenha conhecimento sobre @Entity, @NamedQueries, @NamedQuery, @Id, @GeneratedValue, @Column e @ManyToOne, recomendo a leitura dos artigos http://linubr.blogspot.com.br/2012/08/desenvolvendo-um-crud-para-web-com-java.html e http://linubr.blogspot.com.br/2012/08/entendendo-persistencia-de-dados-e.html 
 
Clique com o botão direito sobre o pacote br.com.linu.forum.model, selecione a opção New e a opção Class (o Eclipse oferece recursos para a criação das entidades, porém optei em criar o código manualmente). Defina o nome da classe como Topic.
 
package br.com.linu.forum.model;
 
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
 
@Entity
@NamedQueries( {
            @NamedQuery(name = "topics", query = "select t from Topic t order by t.creationDate desc"),
            @NamedQuery(name = "topicByName", query = "select t from Topic t where t.title = :param") })
public class Topic implements Serializable {
 
      private static final long serialVersionUID = 1L;
 
      @Id
      @GeneratedValue
      private long id;
      @Column(unique = true, length = 100, nullable = false)
      @NotNull(message = "Título do tópico inválido.")
      @Size(min=3, max = 100, message = "O título do tópico deve conter de {min} a {max} caracteres.")
      private String title;
      @Column(length = 500, nullable = false)
      @NotNull(message = "Texto do tópico inválido.")
      @Size(min=1, max = 500, message = "O texto do tópico deve conter de {min} a {max} caracteres.")
      private String text;
      @Column(nullable = false)
      @Temporal(TemporalType.TIMESTAMP)
      private Date creationDate;
      @Column(length = 30, nullable = false)
      @Size(max = 30, message = "O nome do usuário não pode conter mais do que {max} caracteres.")
      private String userName;
     
      public String getTitle() {
            return title;
      }
 
      public void setTitle(String title) {
            this.title = title;
      }
 
      public String getText() {
            return text;
      }
 
      public void setText(String text) {
            this.text = text;
      }
 
      public Date getCreationDate() {         
            return creationDate;
      }
 
      public void setCreationDate(Date creationDate) {
            this.creationDate = creationDate;
      }
 
      public String getUserName() {
            return userName;
      }
 
      public void setUserName(String userName) {
            this.userName = userName;
      }
 
      public long getId() {
            return id;
      }
 
}
  
Após copiar o código para o arquivo criado, lembre-se de salvar (Ctrl+S).
Observe que após criar essa entidade, o Eclipse apresentará um erro. Esse erro indica que existem entidades no projeto JPA que não estão mapeadas no persistence.xml. Para resolver isso, clique com o botão direito sobre o arquivo persistence.xml (dentro da opção JPA Content) e selecione a opção Synchronize Class List.

Detalhe: eu sempre trabalho com o recurso de Build automatically desabilitado. Por isso, vez ou outra, farei referência ao uso da opção Build Project. Caso você deixe o recurso Build automatically habilitado (no menu Project), essa opção (Build Project) não estará acessível.

Clique então com o botão direito no projeto SuperForumEJB e selecione a opção Build Project. Isso fará com que o projeto seja publicado - retirando as mensagens de erro. É claro que se ainda existirem erros, as mensagens vão permanecer.
 
O que há de novo nessa classe?
Note que, além dos já conhecidos @Column, @Entity, @Id e @GeneratedValue, nós incluímos as anotações @NotNull e @Size. Essas são novas anotações da JSF 2.0.
O que!?!? Novos recursos da JavaServer Faces?
Sim. O Java EE 6 oferece novos recursos JSF que são capazes de validar os campos do modelo do sistema no momento em que as páginas XHTML se comunicação com seus Beans. Como você pode perceber, caso alguma delas não atenda exatamente as definições, a mensagem incluída no campo message serão passadas (como mágica) para os devidos componentes de mensagem da view (<h:messages />, por exemplo). Bom, com sabemos, muitos outros frameworks anteriores à especificação Java EE 6 já faziam esse tipo de validação. Eu mesmo já precisei implementar anotações (que na época, chamei de @IkColumn) para verificação de integridade nos dados do modelo. O Hibernate também fazia isso.
Acho muito intuitivo o funcionamento dessas anotações, portanto vou evitar explicações como "o @NotNull serve para indicar que o campo não pode ser nulo". Por enquanto, deixarei que seus neurônios trabalhem para descobrir o significado da anotação @Size e seus atributos min max. Veja que em message podemos definir a mensagem que será enviada e que, além disso, pode-se optar em apontar para os outros parâmetros internos da anotação - usando, por exemplo, a sintaxe {min} para indicar o valor definido no parâmetro min.

Pensando nos requisitos
Observe que a @NamedQuery de nome topics já implementa a consulta em conformidade com o 5º requisito funcional - todos os tópicos ordenados em ordem decrescente pela data de criação. Veja ainda que, segundo o requisito 4, a palavra Visitante deverá ser associada ao userName. Por esse motivo, não anotamos esse campo com @NotNull - lembre-se que a verificação de validação dessa entidade ocorrerá na camada View. Dessa forma, o próprio JSF impediria que o usuário deixasse seu nome em branco.
Uma observação interessante é que, quando a página JSF se comunica com um Bean, os campos de entrada (<h:inputText />, por exemplo), mesmo que vazios, não enviam uma String null. Isso significa que mesmo que o usuário não preencha os campos, eles não serão afetados pela regra @NotNull. Portanto deve-se ter o cuidado de tratar Strings com length insuficiente através do parâmetro min da anotação @Size.

Conceitos de Orientação a Objetos
Bom, vou aproveitar a situação para gerar um tema de discussão: vocês acham que seria correto tratar de elementos de tela (da camada View) nos objetos do modelo? Isso não faria com que as preocupações da camada View poluíssem a camada Model? Digo isso pelo fato de definirmos através do message a mensagem que será disparada para a camada View diretamente no modelo do sistema. Do ponto de vista da praticidade, é bem interessante esse recurso - poupa-nos tempo. Do ponto de vista de patterns EE, talvez isso ignore o fato de que definições da camada View no modelo não sejam recomendadas. Imaginem por exemplo se essa aplicação fosse criada para um celular. Será que a mensagem "gigante" de uma aplicação Web seria apropriada pela camada View do celular? Talvez não. Isso significa que nossa mensagem pode nem sempre ser viável. De qualquer forma, caso você seja chato (como eu), pode implementar isso em outras camadas. E pode inclusive fazer esse tipo de verificação (que ocorre naturalmente na comunicação do JSF para o Bean) chamando uma instância do Validator em qualquer camada.
Só um caso a se pensar, ok?
 
Vamos implementar agora a classe Comment.
 
package br.com.linu.forum.model;
 
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
 
@Entity
@NamedQueries( { @NamedQuery(name = "comments", query = "select c from Comment c where c.topic.id = :param") })
public class Comment implements Serializable {
 
      private static final long serialVersionUID = 1L;
      @Id
      @GeneratedValue
      private long id;
      @Column(length = 500, nullable = false)
      @NotNull(message = "Texto do comentário inválido.")
      @Size(min=1, max = 500, message = "O texto do comentário deve conter de {min} a {max} caracteres.")
      private String text;
      @Column(nullable = false)
      @Temporal(TemporalType.TIMESTAMP)
      private Date creationDate;
      @Column(length = 30, nullable = false)
      @Size(max = 30, message = "O nome do usuário não pode conter mais do que {max} caracteres.")
      private String userName;
      @ManyToOne(optional = false)
      private Topic topic;
 
      public String getText() {
            return text;
      }
 
      public void setText(String text) {
            this.text = text;
      }
 
      public Date getCreationDate() {
            return creationDate;
      }
 
      public void setCreationDate(Date creationDate) {
            this.creationDate = creationDate;
      }
 
      public String getUserName() {
            return userName;
      }
 
      public void setUserName(String userName) {
            this.userName = userName;
      }
 
      public Topic getTopic() {
            return topic;
      }
 
      public void setTopic(Topic topic) {
            this.topic = topic;
      }
 
      public long getId() {
            return id;
      }
 
}
 
Novamente as anotações @NotNull e @Size são aqui utilizadas para validação de consistência dos dados do modelo. Assim como em Topic, a @Size foi utilizada para delimitar o tamanho mínimo e máximo dos campos de texto definidos - veja, por exemplo, que text deve ter no mínimo 1 e no máximo 500 caracteres. Esta classe possui uma associação @ManyToOne com Topic. Observe que a @NamedQuery criada será utilizada para buscar comentários associados a determinado id de Topic.
Por fim, abaixo temos a classe Metadata.
 
package br.com.linu.forum.model;
 
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
 
@Entity
@NamedQueries( {
            @NamedQuery(name = "metadatas", query = "select m from Metadata m where m.word like :param order by m.occurrences desc"),
            @NamedQuery(name = "metadatasByTopic", query = "select m from Metadata m where m.topic.id = :param") })
public class Metadata implements Serializable {
 
      private static final long serialVersionUID = 1L;
      @Id
      @GeneratedValue
      private long id;
      @Column(length = 255, nullable = false)
      private String word;
      @Column(nullable = false)
      private long occurrences;
      @ManyToOne(optional = false)
      private Topic topic;
 
      public String getWord() {
            return word;
      }
 
      public void setWord(String word) {
            this.word = word;
      }
 
      public long getOccurrences() {
            return occurrences;
      }
 
      public void setOccurrences(long occurrences) {
            this.occurrences = occurrences;
      }
 
      public Topic getTopic() {
            return topic;
      }
 
      public void setTopic(Topic topic) {
            this.topic = topic;
      }
 
      public long getId() {
            return id;
      }
 
}

Nada de novo foi acrescido aqui. Atente-se no detalhe que a @NamedQuery metadatas foi elaborada de acordo com o 8º requisito funcional descrito. Já a metadatasByTopic será utilizada para o processo de atualização das entidades Metadata - conforme veremos mais adiante. Observe que não foram acrescentadas as anotações @NotNull e @Size, pois como não haverá criação de objetos Metadata a partir de telas, o controle não poderá ser realizado via JSF. Faremos as devidas verificações na camada de negócios.
Para sincronizar as novas classes, clique com o botão direito sobre o arquivo persistence.xml e selecione Synchronize Class List. Nosso modelo está pronto.

 
Configurando o GlassFish para trabalhar com o Banco de Dados
Vou utilizar o banco PostgreSQL 8.4, porém você pode optar em utilizar outro banco. Independente do SGBD, crie um banco chamado SuperForum, que contenha o schema padrão public - se seu SGBD trabalhar com schemas. Vou realizar o acesso ao banco através do usuário postgres com senha 123eja
Para o banco de dados PostgreSQL, foi necessário realizar o download do JDBC correspondente. Entre no endereço http://jdbc.postgresql.org/download.html e faça o download do driver "8.4-701 JDBC 4" (ou outra versão que julgue mais apropriada). Vou chamar o driver JDBC, neste artigo, pelo próprio nome do JAR baixado (no meu caso): postgresql-8.4-701.jdbc4.jar
 
Com o GlassFish parado, copie o arquivo postgresql-8.4-701.jdbc4.jar para os diretórios sges-v3/glassfish/domains/domain1/lib/databases e sges-v3/glassfish/lib, onde sges-v3 é o diretório de instalação do GlassFish. Isso fará com que o servidor de aplicação e o domínio 1 do GlassFish tenha acesso ao driver JDBC.
Na aba Server, clique com o botão direito sobre o servidor GlassFish e selecione Start. Caso o Eclipse abra uma tela de login, entre com o usuário e senha de administração do GlassFish.
 
Atenção: Caso seu usuário e senha (cadastrados na instalação do Glassfish) não sejam aceitos pelo login interno do Eclipse, entre com usuário admin e senha em branco (será um BUG? - não sei). A tela do login interno pode ser visualizada abaixo:
 
 
Veja que no meu caso, por exemplo, o usuário cadastrado no Glassfish não funcionou. Por isso precisei realizar o login como informado acima. Certamente esqueci-me de fazer alguma configuração. Mas não se preocupe! Isso não vai atrapalhar.
 
Abra em seu navegador a URL http://localhost:4848/ e realize o login.
 
 
Na tela de administração do GlassFish, clique na opção Recursos, JDBC, Grupos de conexões. Em seguida, na página da direita, clique na opção Novo
  

Na primeira tela de configuração, entre com os valores:
Nome: PostgresPool
Tipo de recurso: javax.sql.DataSource
Fornecedor do banco de dados: Postgresql
Em seguida, clique me Avançar
 
 
Mantenha os dados da segunda tela de configuração com seus valores padrões, exceto na tabela de Outras propriedades. Configure aqui os valores:
databaseName: SuperForum
password: 123eja (ou o password que você tenha definido)
portNumber: 5432
user: postgres
Por fim, clique em Concluir
 
Antes de configurar essa conexão, vamos fazer um teste de acesso ao banco.
 
 
Clique em Recursos, JDBC, abra a opção Grupo de conexões e clique em PostgresPool. Nesta janela teremos as configurações de conexão realizadas. Para testá-la, clique em Ping
 
 
Caso você tenha sucesso na conexão com o banco, a mensagem Ping executado com êxito será exibida. Se ocorrer falha, a própria mensagem de falha mostrará o problema ocorrido - portanto você pode usar essas informações para resolver o problema. 
Vamos configurar agora o acesso à conexão criada anteriormente.
 
 
Clique, à esquerda da tela, na opção Recursos JDBC - dentro de Recursos, JDBC. Clique em Novo
 
 
Em Nome JNDI defina o valor jdbc/forum. Em Nome do grupo selecione PostgresPool. Clique em OK
O GlassFish já está pronto para nos fornecer, através do JNDI, o pool de conexões com o banco de dados Postgres. Vamos agora criar a classe de acesso ao banco.  
 
 
Desenvolvendo a camada de acesso aos dados
A ideia de separar a codificação de acesso ao banco de dados em classes separadas permanece. Vamos primeiro criar o banco de dados e seu acesso através de diretivas no persistence.xml. Em seguida vamos elaborar a classe geral de acesso aos dados.
 
O primeiro passo é definir o arquivo persistence.xml para utilizar, via JNDI, o acesso ao banco de dados que configuramos no tópico anterior. Seu arquivo persistence.xml deverá ficar exatamente igual ao mostrado abaixo:
 
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
      <persistence-unit name="SuperForumEJB">
            <jta-data-source>jdbc/forum</jta-data-source>
            <class>br.com.linu.forum.model.Topic</class>
            <class>br.com.linu.forum.model.Metadata</class>
            <class>br.com.linu.forum.model.Comment</class>
            <properties>
               <property name="eclipselink.ddl-generation" value="create-tables"/>
            </properties>          
      </persistence-unit>
</persistence>
 
Observe que as definições das entidades (via tag <class>) já estavam definidas. Isso foi possível através do recurso Synchronize Class List (como vimos). Devemos atentar-se nas configurações do <jta-data-source> e <properties>. Observe que o <jta-data-source> aponta exatamente para a conexão jdbc/forum que criamos no GlassFish. Já a <property> incluída faz com que, sempre que publicarmos a aplicação, as tabelas serão criadas (mapeadas através das entidades), se não existirem. Certamente, em produção, esse recurso não precisará mais estar definido - porém, por enquanto, vamos mantê-lo. Após modificar seu persistence.xml, salve-o e vamos para o próximo passo.
 
Vamos agora cria o pacote que manterá nossas classes de acesso ao banco. Clique com o botão direito sobre o projeto SuperForumEJB, selecione New, Package. Define o nome do novo pacote como br.com.linu.forum.dao
Todos os recursos de acesso aos dados ficarão abaixo desse pacote. Como boa prática de sistemas enterprise, é interessante criar diferentes exceções para as camadas do sistema. Na camada DAO podem ocorrer vários problemas. Esses problemas deverão ser tratados e disparados em um único formato de Exception para a camada acima. Portanto, vamos criar uma classe DaoException. Clique com o botão direito sobre o pacote br.com.linu.forum.dao (dentro do projeto SuperForumEJB), selecione New, Class. Defina o nome da classe como DaoException e entre com o código abaixo:
 
package br.com.linu.forum.dao;
 
public class DaoException extends Exception {
 
      private static final long serialVersionUID = 1L;
 
      public DaoException(String message) {
            super( message );
      }
     
}
 
Como você pode observar, essa classe apenas herda a Exception padrão do Java. De posse de nossa exceção exclusiva para a camada de acesso aos dados, poderemos então criar uma interface padrão que define os métodos comuns para nosso sistema. Clique com o botão direito sobre o pacote br.com.linu.forum.dao, selecione a opção New, Interface. Defina seu nome como Dao. Abaixo temos o código dessa interface.
 
package br.com.linu.forum.dao;
 
import java.io.Serializable;
import java.util.List;
import javax.ejb.Local;
 
@Local
public interface Dao extends Serializable {
 
      public void create(Object entity) throws DaoException;
      public void update(Object entity) throws DaoException;
      public Object read(Class<?> entityClass, long id) throws DaoException;
      public List<?> read(String namedQuery) throws DaoException;
      public List<?> read(String namedQuery, Object parameter) throws DaoException;
     
}
 
Observe que nada de novo foi acrescentado nessa interface (a anotação @Local já existia na Java EE 5 - utilizada para distribuir, localmente, os Beans do sistema). Note ainda que cada método disponível em nossa interface vá lançar DaoException quando erros ocorrerem. Os requisitos, em nenhum momento, informam necessidade de exclusão de registros no banco de dados. Por isso, opcionalmente, não foi criado o método delete - geralmente comum para essa camada. Os três métodos polimórficos read serão necessários. Deixo a cargo do leitor descobrir um ponto na aplicação onde cada caso de read seria necessário - veremos isso na prática, mais adiante.
 
Por fim, criaremos agora a classe BasicDao que fará toda a implementação do acesso aos dados. Crie uma nova classe no pacote br.com.linu.forum.dao com o nome BasicDao e defina seu código como mostrado abaixo:
 
package br.com.linu.forum.dao;
 
import java.io.Serializable;
import java.util.List;
import javax.ejb.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
 
@Singleton(name="ejb/dao")
public class BasicDao implements Dao, Serializable {
     
      private static final long serialVersionUID = 1L;
     
      @PersistenceContext(unitName="SuperForumEJB")
      private EntityManager entityManager;
 
      public BasicDao() {         
            System.out.println( "A classe BasicDao foi instanciada!" );
      }
 
      @Override
      public void create(Object entity) throws DaoException {
            try {            
                  entityManager.persist(entity);
            } catch (Exception e) {
                  throw new DaoException( e.getMessage() );
            }
      }
     
      @Override
      public void update(Object entity) throws DaoException {
            try {                             
                  entityManager.merge(entity);            
            } catch (Exception e) {
                  throw new DaoException( e.getMessage() );
            }
      }
 
      @Override
      public Object read(Class<?> entityClass, long id) throws DaoException {
            try {
                  return entityManager.find(entityClass, id);
            } catch (Exception e) {
                  throw new DaoException( e.getMessage() );
            }
      }
     
      @Override
      public List<?> read(String namedQuery) throws DaoException {
            try {
                  return entityManager.createNamedQuery(namedQuery).getResultList();
            } catch (Exception e) {
                  throw new DaoException( e.getMessage() );
            }
      }
     
      @Override
      public List<?> read(String namedQuery, Object parameter)
                  throws DaoException {
            try {
                  Query query = entityManager.createNamedQuery(namedQuery);
                  query.setParameter("param", parameter);
                  return query.getResultList();
            } catch (Exception e) {
                  throw new DaoException( e.getMessage() );
            }
      }
     
}
 
Aqui nós temos novidades! O comando @PersistenceContext(unitName="SuperForumEJB") incluído logo no início do arquivo (como já fazíamos no EE 5) serve para buscar a Unidade de Persistência criada no persistence.xml (observe que o nome SuperForumEJB precisa ser o mesmo do campo <persistence-unit name="SuperForumEJB"> do persistence.xml). Todos os métodos da interface Dao foram implementados com seus devidos recursos (também já conhecidos). A grande novidade gira em torno da notação @Singleton(name="ejb/dao"). O design pattern Singleton já é comumente usado para construção de DAOs em sistemas EE (veja http://linubr.blogspot.com.br/2012/08/criar-dao-com-singleton-ou-nao-eis.html). Aqui, toda a lógica desse padrão foi automaticamente implementada com a anotação @Singleton. Muito boa essa novidade né?! Agora existirá uma única instância do BasicDao em toda a aplicação SuperForum - e para provarmos isso, coloquei o comando System.out.println( "A classe BasicDao foi instanciada!" ); no constructor do objeto BasicDao. Sempre que o container criar uma nova instância desse objeto, essa mensagem será mostrada no console (quando você rodar a aplicação verá que essa mensagem só será exibida uma vez!).
Existe ainda uma novidade que gira em torno das interfaces @Local e @Remote. Agora, para a publicação de beans, a criação de interfaces não será mais necessária. No entanto, de qualquer forma, é uma boa prática criar interface que define o comportamento padrão de objetos em sistemas EE. Por isso optei em mantê-la.
 
Conceitos de Orientação a Objetos
Achei interessante ressaltar que através da anotação @Singleton a criação de um típico DAO ficou muito mais fácil. Lembre-se que cada objeto deve, de forma única, realizar determinadas ações - ações essas que devem ter algo em comum. O pattern Singleton (criado manualmente em Java SE), porém, demanda código que de certa forma polui os métodos do objeto em si (neste caso, o DAO). Com a anotação @Singleton essa sujeira será evitada. Muito bom! Por acaso, eu sabia que o Java EE 6 viria com esse recurso (JSR-330). Adorei usá-lo pela primeira vez.
Outro detalhe a ser observado: todas as classes e interfaces foram implementadas com a tag interface Serializable. Isso se faz necessário para que o servidor EE possa trabalhar com essas classes corretamente (ativação/desativação de SessionBeans, tráfego JNDI e etc).
 
Pode-se dizer que estamos em 1/4 do caminho. Já temos Model e DAO. Vamos agora para a camada de business.

Continuação
Bom, o documento ficou maior do que eu havia esperado, portanto optei em separá-lo em quatro partes.

"Se tão poderoso você é... fugir quer por que?" - Mestre Yoda
Att, Guilherme Pontes

[linu.com.br] - [parte 1] - [parte 2] - [parte 3] - [parte 4] - lgapontes@gmail.com

Nenhum comentário:

Postar um comentário