Struts 2 + AJAX + JPA + Spring – Parte IV

 

Persistência com JPA

Estamos utilizando a implementação do Hibernate da JPA, que será nosso Persistence provider, que executará o acesso ao banco de dados, como estaremos utilizando a JPA, então podemos a qualquer hora mudar sua implementação para utilizar o TopLink da Oracle por exemplo, desde que utilizemos suas anotações definidas no pacote javax.persistence.

 

Não se preocupe em criar as tabelas, a partir da primeira execução da aplicação exemplo, a JPA irá criar todas as tabelas automaticamente.

 

Uma das vantagens quando usamos as anotações da JPA é que por se tratar de um padrão definido para frameworks de mapeamento objeto/relacional podemos mudar sua implementação sem alterar nossas entidades.

 

Aqui na JPA temos o conceito de Entidades, uma entidade é um simples POJO que possui um construtor default e os métodos getters e setters dos atributos que não podem ser final e representa uma tabela no banco de dados.

 

A JPA utilizamos um  EntityManagerFactory para podermos criarmos nossa  EntityManager que nos dara acesso a funções de insert,select,update,etc. É semelhante ao SessionFactory do hibernate.

 

A entidade representa a tabela e os atributos dessa entidade representam as colunas da tabela, para isso temos a anotação @Entity que informa a JPA que este pojo é uma entidade.

                  

Quando utilizando frameworks de persistência, nos temos os seguintes tipos de relacionamentos possíveis entre as entidades. OneToOne, ManyToOne, OneToMany, ManyToMany

 

Estes relacionamentos possuem dois parâmetros importantes o cascade e o fetch que serão explicados mais adiante.

 

Como temos que importar as libs do hibernate para que possamos utilizar a JPA devemos nos atentar para que todas as anotações que importemos para nossas classes sejam do pacote javax.persistence, assim garantimos que é uma anotação da especificação EJB 3.0.

 

Para utilizar a JPA crie dentro do seu diretório src a pasta META-INF, e dentro desta pasta crie o arquivo persitence.xml, podem ver um exemplo na listagem 13, mas o nosso persistence.xml não terá nada já que estaremos utilizando o Spring.

 

Vamos também utilizar o arquivo log4j.properties que deve ser copiado para o diretório src da aplicação, utilizamos este arquivo para podermos ativar a saída da console de informações ou debug do hibernate, esse arquivo tem mais utilidades mas para nos neste artigo utilizaremos apenas para poder exibir as informações do hibernate.

 

Este arquivo pode ser encontrado dentro do diretório hibernate-3.2.3.ga\hibernate-3.2\etc.

 

Criando nossas Entidades

Começaremos analisando a classe Usuario, que utilizamos na nossa action de login. Conforme a listagem 10.

 

package br.jm.entidade;

 

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.Table;

 

@Entity

@Table(name="CT_USUARIOWEB")

public class Usuario {

     

      @Id

      @GeneratedValue(strategy=GenerationType.IDENTITY)

      private Long id;

      @Column(length=10)

      private String login;

      @Column(length=10)

      private String senha;

           

      public Usuario() {

 

      }

     

      public Usuario(String login, String senha) {

            this.login = login;

            this.senha = senha;

      }

 

      //métodos getters e setters

     

}    

Listagem 10 – Usuario.java

 

Aqui também não podemos esquecer de um construtor default e os métodos getters and setters.

 

Antes de mais nada deve-se utilizar a anotação @Entity, somente isto já basta para persistirmos um objeto desta classe, a JPA por default associa o nome da classe a tabela e o nome dos atributos como nome das colunas, aqui estamos utilizando a anotação @Table passando para o parâmetro name o nome da tabela, que neste caso quer dizer que a classe Usuario representa a tabela CT_USUARIOWEB.

 

Todo objeto por regra tem que possuir um atributo que possa ser usado para identificá-lo no banco de dados, com a anotação @Id, também utilizamos a anotação @GeneratedValue para informar que deve ser gerado um valor para este atributo, e o parâmetro strategy define a estratégia com a qual será utilizada para gerar o valor deste campo identificador. Neste caso utilizamos a enumeração da JPA.

           

@GeneratedValue(strategy=GenerationType.IDENTITY)

 

Similar ao identity do banco de dados. Existem outros tipos de estratégias que não serão abordadas neste artigo, mas no final do artigo existem links onde você poderá saber mais a respeito.

 

A propriedade id é um Long e sera utilizado como identificador deste objeto, nas propriedades login e senha apenas utilizamos a anotação @Collum para definir o tamanho da coluna, o default é para campos string criar o tamanho máximo, também existe a possibilidade de configurar o nome que a aquela propriedade representa utilizando essa mesma anotação.

 

Na listagem 11 temos a classe Telefone que representa o telefone do contato que será adicionado.

 

package br.jm.entidade;

 

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.Table;

 

@Entity

@Table(name="CT_TELEFONECONTATO")

public class Telefone {

 

      @Id

      @GeneratedValue(strategy=GenerationType.IDENTITY)

      private Long id;

      private String ddd;

      @Column(length=20)

      private String numero;

     

      public Telefone() {

     

      }

      //métodos getters e setters

Listagem 11 – Telefone.java

 

Também definimos as mesmas anotações para esta classe e relacionamos esta classe com a tabela CT_TELEFONECONTATO, e definimos o tamanho do campo dos atributos, temos um exemplo de relacionamento na listagem 12.

 

package br.jm.entidades;

 

import javax.persistence.CascadeType;

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.FetchType;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.JoinColumn;

import javax.persistence.JoinColumns;

import javax.persistence.OneToOne;

import javax.persistence.Table;

 

@Entity

@Table(name="CT_CONTATO")

public class Contato {

 

      @Id

      @GeneratedValue(strategy=GenerationType.IDENTITY)

      private Long id;

      @OneToOne(cascade=CascadeType.ALL, fetch=FetchType.LAZY)

      @JoinColumn(name="fk_pessoa")

      private Pessoa pessoa;

      @Column(length=100)

      private String email;

      @OneToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)

      @JoinColumn(name="fk_telefone")

      private Telefone telefone;

     

      public Contato() {

           

      }

      //métodos getters e setters

Listagem 12 – Contato.java

 

Esta classe, além de ter as anotações já conhecidas, possui um relacionamento do tipo um para um com a classe Pessoa e a Classe Telefone, na JPA este tipo de relacionamento deve ser configurado utilizando a anotação @OneToOne, são disponíveis os tipos de relacionamento @OnetoMany, @ManyToMany, @ManyToOne e @OneToOne mas neste artigo só utilizaremos relacionamento um-para-um, mas os elementos principais e comuns a esses relacionamentos são explicados abaixo.

        

Na anotação @OneToOne igual as outras anotações de relacionamentos possui o parâmetro cascade onde definimos que tipo de cascade deve ser realizado caso haja alguma operação na entidade, utilizamos a enumeração CascadeType para escolher entre: MERGE, PERSIST, REMOVE, REFRESH e ALL.

 

Por exemplo, no nosso caso se for removido o contato, também será removido a pessoa, pois colocamos o tipo como ALL, logo qualquer operação que for realizada na entidade contato será realizada também nos seus relacionamentos.

 

Em sistemas de produção não é aconselhável a utilização do parâmetro cascade configurado para todos, como o nosso exemplo, pois é interessante ter controle sobre suas entidades.

 

No parâmetro fecth configuramos o modo que será obtido o objeto que faz o relacionamento do banco de dados.

 

Contato = entityManager.find(Contato.class, id);

 

Configurando o parâmetro fetch para LAZY, os atributo email da classe contato é recuperado do banco de dados após a execução deste método find(), mas não o atributo pessoa, será sim criado uma referencia e não será armazenado o conteúdo do banco de dados na memória dentro do objeto pessoa, somente quando for realizado a chamada para o seu método get.

 

Pessoa pessoa = contato.getPessoa();

 

Depois dessa chamada sim, será então populado o objeto pessoa com as informações do banco de dados. Isso é muito útil quando necessário ter objetos de tabelas com grande quantidade de dados e não for necessário utilizar esses dados a toda hora.

 

Já na propriedade telefone, configuramos para EAGER o parâmetro fetch, logo quando for realizado um load/find será obtido na hora o valor do banco de dados e populado na memória o objeto telefone.

 

Isso pode ser melhor visualizado em modo debug, coloque um breakpoint no método acharContato da classe ContatoDao que você poderá ver que não é carregado o objeto pessoa, mas que o objeto telefone tem todo seus atributos devidamente carregados em memória.

 

Agora a classe Pessoa que esta na listagem 13.

 

package br.jm.entidade;

 

import java.util.Date;

 

import javax.persistence.Column;

import javax.persistence.Entity;

import javax.persistence.GeneratedValue;

import javax.persistence.GenerationType;

import javax.persistence.Id;

import javax.persistence.Inheritance;

import javax.persistence.InheritanceType;

import javax.persistence.Table;

import javax.persistence.Temporal;

import javax.persistence.TemporalType;

 

@Entity

@Table(name="CT_PESSOA")

public class Pessoa {

 

      @Id

      @GeneratedValue(strategy=GenerationType.IDENTITY)

      private Long id;

     

      @Column(length=150)

      private String nome;

      @Column(length=150)

      private String sobreNome;

      @Temporal(TemporalType.DATE)

      private Date dataDeNascimento;

 

      public Pessoa() {

     

      }

 

      //métodos getters e setters

 

}

Listagem 13 – Pessoa.java

 

Nossa classe pessoa esta mapeada para a tabela CT_PESSOA e diferente de nossas outras entidades possui um atributo do tipo date que é a data de nascimento do contato.

 

Neste caso utilizamos a anotação @Temporal para indicar que este atributo vai ser uma coluna do tipo DATE, a enumeração TemporalType possui vários tipos como hora, data, etc. Aqui usaremos o tipo DATE, por se tratar de uma data de aniversário.

 

Pronto nossas entidades estão prontas para utilizarmos o banco de dados em nossa aplicação de exemplo.

 

Poderíamos criar um dao genérico que atendesse as nossas necessidades, mas neste artigo criaremos dois daos, um para o contato e outro para o login.

 

Abaixo na listagem 14 o LoginDao.

 

package br.jm.persistencia;

 

import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory;

import javax.persistence.NoResultException;

import javax.persistence.Persistence;

import javax.persistence.Query;

 

import br.jm.entidade.Usuario;

 

public class LoginDAO {

                 

      private EntityManager entityManager;

      private EntityManagerFactory entityManagerFactory;

     

      public LoginDAO() {

 

      }

     

      public LoginDAO(EntityManagerFactory entityManagerFactory) {

            this.entityManagerFactory = entityManagerFactory;

            this.entityManager = this.entityManagerFactory.createEntityManager();

      }

     

      public Boolean validaLogin(String login, String senha) {

            Query query = this.entityManager.createQuery("SELECT u FROM Usuario u WHERE u.login = :login AND u.senha = :senha");

            query.setParameter("login", login);

            query.setParameter("senha", senha);

           

            Usuario user = null;

            try {

                  user = (Usuario) query.getSingleResult();     

            } catch (NoResultException e) {

                  e.printStackTrace();

            }

           

            return (user != null);

      }

      //métodos getters e setters

Listagem 14 – LoginDAO.java

        

 

Nossa  EntityManagerFactory será injetada no nosso dao pelo Spring, mais adiante veremos como isso é feito.

 

Criamos nossa  EntityManager a partir do método createEntityManager() da  EntityManagerFactory, e somente com isso já podemos obter acesso ao banco.

 

No método validaLogin é recebido um login e uma senha, e aqui utilizamos um objeto do tipo Query para criarmos nossa JPAQL, que é uma linguagem semelhante a HQL do Hibernate onde temos como objetivo realizar consultas SQL pensando em objetos .

 

Neste exemplo realizamos a seguinte JPAQL.

 

Query query = this.entityManager.createQuery("SELECT u FROM Usuario u             WHERE u.login = :login AND u.senha = :senha");

 

        

Quer dizer que vamos realizar um select do objeto usuário onde o login e a senha forem como as passadas como parâmetro. É usado a sintaxe :nomeParametro para informar que este será substituído por uma string utilizando o método setParameter() da classe Query.

 

query.setParameter("login", login);

query.setParameter("senha", senha);

 

Assim criamos nossa consulta para validar o usuário.

 

Caso não seja encontrado nenhum registro será lançada a exceção NoResultException que no nosso caso retornara false, lembrando que isso é apenas um exemplo um login para um sistema é mais complexo e possui direcionamentos variados.

 

Ate agora bem simples de se realizar uma consulta, mas existem maneiras mais simples de se fazer isso, vamos mostrar abaixo agora outras operações.

 

package br.jm.persistencia;

 

import java.util.List;

 

import javax.persistence.EntityManager;

import javax.persistence.EntityManagerFactory;

import javax.persistence.EntityTransaction;

import javax.persistence.Query;

 

import br.jm.entidade.Contato;

 

public class ContatoDAO {

 

      private EntityManagerFactory entityManagerFactory;

      private EntityManager entityManager;

      private EntityTransaction transaction;

           

      public ContatoDAO() {

      }

 

      public ContatoDAO(EntityManagerFactory entityManagerFactory) {

            this.entityManagerFactory = entityManagerFactory;

            this.entityManager = this.entityManagerFactory.createEntityManager();

            this.transaction = this.entityManager.getTransaction();

      }

 

      public void adiciona(Contato contato) {

            if(this.transaction.isActive()) {

                  this.entityManager.persist(contato);

                  this.transaction.commit();

            }else {

                  this.transaction.begin();

                  this.entityManager.persist(contato);

                  this.transaction.commit();

            }

           

      }

 

      public List<Contato> obtemListaContato() {

            Query query = this.entityManager.createQuery("SELECT c FROM Contato c");

 

            return query.getResultList();

      }

 

      public void removerContato(Long id) {

            Contato contato = acharContato(id);

            if(this.transaction.isActive()) {

                  this.entityManager.remove(contato);

                  this.transaction.commit();

            }else {

                  this.transaction.begin();

                  this.entityManager.remove(contato);

                  this.transaction.commit();

            }

      }

 

      public Contato acharContato(Long id) {

            Contato contato = null;

            contato = this.entityManager.find(Contato.class, id);

           

            return contato;

      }

 

      public void removeContatos() {

            Query query = null;

           

            if(this.transaction.isActive()) {

                  query = this.entityManager.createQuery("DELETE Contato");

                  query.executeUpdate();

                  this.transaction.commit();

            }else {

                  this.transaction.begin();

                  query = this.entityManager.createQuery("DELETE Contato");

                  query.executeUpdate();

                  this.transaction.commit();

            }

      }

      //métodos getters e setters

Listagem 15 – ContatoDAO.java

 

Neste nosso ContatoDAO que pode ser visto na listagem 15 o EntityManagerFactory também será injetado pelo Spring, veremos então como funciona suas funcionalidades utilizadas aqui.

 

public void adiciona(Contato contato) {

      if(this.transaction.isActive()) {

            entityManager.persist(contato);

            transaction.commit();

      }else {

            transaction.begin();

            entityManager.persist(contato);

            transaction.commit();

      }

     

}

 

O método adiciona() recebe um objeto do tipo contato e com um bloco if..else ele valida se existe uma transação ativa, para algumas operações como insert é necessário ter uma transação ativa para realizar o insert utilizando o método persist() da JPA, caso você não inicie uma transação e não realize o commit da mesma, nenhum erro será exibido, mas nenhuma alteração no banco de dados será realizada.

 

Fazemos uma verificação com método isActive() da classe EntityTransaction e se estiver ativa então apenas chamamos o método persist() do nosso EntityManager passando o objeto que deve ser inserido no banco de dados. Para realizar um update utiliza o método merge() da JPA que realiza o update, passa-se um objeto também para este método. Após a execução do método persist() é necessário realizar o commit da transação para que seja realmente realizado o insert no banco de dados.

 

Se nossa transação não estiver aberta ela então chamará o método begin()  do objeto EntityTransaction para poder iniciar a inserção do objeto contato.

 

public List<Contato> obtemListaContato() {

      Query query = this.entityManager.createQuery("SELECT c FROM Contato c");

            return query.getResultList();

}

 

No método obtemListaCOntato() utilizamos novamente a JPAQL para junto com o objeto do tipo Query nos fornecer a lista de todos os registros no banco de dados do objeto Contato. Repare que com a JPAQL realmente pensamos em consultas de objetos e existem várias maneiras de se realizarem consultas complexas, no final do artigo veja os links a respeito.

 

O tipo de retorno é a saída do método getResultList(), também possui o método getSingleResult() que obtem apenas um resultado o contrário do que estamos utilizando.

 

public Contato acharContato(Long id) {

      Contato contato = null;

      contato = this.entityManager.find(Contato.class, id);

     

      return contato;

}

 

Neste método recebemos um id que é o identificador do objeto. Apenas retornamos o objeto contato recebido através do método find similar ao load do Hibernate, mas como aqui é utilizado genéricos do Java 5 então não há a necessidade de fazermos um cast para um objeto, diferente do load do hibernate que utiliza um Object, e caso não for encontrado o objeto então o retorno será null.

        

Os parâmetros para o método find() são apenas o tipo da classe, ou seja, seu .class e seu identificador. Isso já basta para você obter um objeto do banco de dados, lembrando que nesse momento  o relacionamento do objeto pessoa no objeto contato não será carregado na memória pois seu tipo de fetch esta configurado para LAZY, então somente após realizado um get na propriedade pessoa será realizado um load nesse objeto.

 

public void removerContato(Long id) {

      Contato contato = acharContato(id);

      if(this.transaction.isActive()) {

            this.entityManager.remove(contato);

            this.transaction.commit();

      }else {

            this.transaction.begin();

            this.entityManager.remove(contato);

            this.transaction.commit();

      }

}

 

Para remover o contato receberemos o id desse contato e obtemos o objeto a partir de uma chamada para o método acharContato(), lembram-se que criamos uma url para nossa action remover contato que recebia como paramétro um id?

 

Então é este método que a action ira executar chamando o servico.

 

Aqui também precisamos de uma transação para realizar essa operação, fazemos a mesma verificação do método adicionar() apenas chamamos o método remove() do nosso EntityManager e esse objeto será removido, então realizamos um commit na operação para que essa seja concretizada, nosso último método deste dao é o:

 

public void removeContatos() {

      Query query = null;

     

      if(this.transaction.isActive()) {

            query = this.entityManager.createQuery("DELETE Contato");

            query.executeUpdate();

            this.transaction.commit();

      }else {

            this.transaction.begin();

            query = this.entityManager.createQuery("DELETE Contato");

            query.executeUpdate();

            this.transaction.commit();

      }

}

 

Este método remove todos os contatos do banco de dados, utilizando a JPAQL para fazer o delete.

Para isso criamos um objeto do tipo Query e a partir do EntitManager criamos a query:

 

DELETE Contato

 

Também realizamos tanto a validação para verificar se nossa transação esta ativa e realizamos um commit nessa operação.

 

Estes daos poderiam estar organizados em um dao genérico que atendesse as necessidades de nossas actions, mas fiz desta maneira para não me prender muito a explicação de genéricos do Java 5, talvez para um próximo artigo.

 

Nosso LoginImpl e ContatoImpl são apenas objetos que tem suas operações definidas em interfaces que servem para fornceçer o acesso ao banco pelas nossas actions, e também para vermos a utilização do spring quando injetamos nossos daos em nossos serviços e nosso serviços em nossas actions.

Leia todos os artigos da série