Sumário

  1. Baixando o Hibernate
  2. Configurando o banco de dados
  3. Configurando o Hibernate
  4. A primeira Entidade
  5. Utilizando o Maven
  6. Entidades Managed, Transient e Detached no Hibernate e JPA
  7. Relacionando as tabelas
  8. Pesquisas orientadas a objeto
  9. Relacionamentos bidirecionais
  10. Comportamento LAZY dos relacionamentos para-muitos
  11. Melhorando a busca TypedQuery
  12. NamedQueries

Baixando o Hibernate

  1. Acesse http://hibernate.org/orm/
  2. baixe o projeto e descompacte
  3. copie os seguintes JAR's:
antlr-2.7.7.jar
classmate-1.3.0.jar
dom4j-1.6.1.jar
hibernate-commons-annotations-5.0.1.Final.jar
hibernate-core-5.2.11.Final.jar
hibernate-jpa-2.1-api-1.0.0.Final.jar
javassist-3.20.0-GA.jar
jboss-logging-3.3.0.Final.jar
jboss-transaction-api_1.2_spec-1.0.1.Final.jar
  1. crie uma pasta scr/lib e cole os JAR's lá.
  2. selecione todos os JAR's e clique com o botão direito > Build Path > add Build path

Configurando o banco de dados

  1. Acesse https://dev.mysql.com/downloads/connector/j/
  2. baixe o conector J e descompacte o arquivo
  3. cole o mysql-connector-java-5.1.44-bin.jar na pasta scr/lib
  4. selecione todos os JAR's e clique com o botão direito > Build Path > add Build path
  5. crie o banco de dados
mysql -u root -p
create database financas;

Configurando o Hibernate

  1. Crie o arquivo persistence.xml dentro da pasta scr/META-INF
  2. cole o conteúdo a seguir:
<persistence 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_2_0.xsd"
    version="2.0">

    <!-- unidade de persistencia com o nome financas -->
    <persistence-unit name="financas">

        <!-- Implementação do JPA, no nosso caso Hibernate -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <!-- Aqui são listadas todas as entidades -->
        <class>io.github.danprates.jpa.modelo.Conta</class>

        <properties>

            <!-- Propriedades JDBC -->
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />

            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost/financas" />

            <property name="javax.persistence.jdbc.user" value="root" />
            <property name="javax.persistence.jdbc.password" value="root" />

            <!-- Configurações específicas do Hibernate -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />

            <property name="hibernate.hbm2ddl.auto" value="update" />
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.format_sql" value="true" />

        </properties>
    </persistence-unit>
</persistence>

A primeira Entidade

  1. Crie uma classe dentro do pacote model
  2. cole o conteúdo a seguir:
package io.github.danprates.jpa.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Conta {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;
    private String titular;
    private String banco;
    private String agencia;
    private String numero;

    // geters e setters 
}

Utilizando o Maven

  1. Cole o conteúdo a seguir, no aquivo pom.xml
  2. antes do fechamento da tag </dependecies>:
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>5.2.8.Final</version>
</dependency>

Entidades Managed, Transient e Detached no Hibernate e JPA

Distinguir entre os estados de uma entidade no JPA/Hibernate é difícil no início. Um objeto é dito transiente quando não tem representação no banco de dados e nem o EntityManager o conhece, como abaixo:

Cliente c = newCliente();

Aqui, qualquer mudança no objeto referido por c não gerará nenhum tipo de insert ou update no banco de dados. O oposto é quando o objeto existe no banco de dados e o EntityManager em questão possui uma referência para ele, essa entidade está managed, gerenciada pelo EntityManager. Considere em uma referência a um EntityManager no seguinte exemplo:

Cliente c = newCliente();// transiente
em.persist(c);// gerenciado

Ou ainda:

Cliente c = em.find(Cliente.class,1);// gerenciado

Quando uma entidade está managed, qualquer mudança em seu estado (como uma chamada de setter) resultará em uma atualização no banco de dados no momento do commit.

O último caso é quando a entidade representa algo que possivelmente está no banco de dados, mas o EntityManager o desconhece: a entidade está fora do contexto, detached. Exemplo:

Cliente c = newCliente();
c.setId(1);

Uma entidade também está detached quando o EntityManager de onde tiramos esse Cliente (por exemplo, quando fizemos um find ou vindo de uma Query) já não está mais aberta. Qualquer mudança nessa referência obviamente não surtirá efeito no banco de dados.

Para que essa mudança faça efeito, isto é, para reattach o entidade, antes precisamos amarrá-la ao contexto de persistência. Repare que no EntityManager já pode existir uma entidade Cliente com esse mesmo id, imagine então o que aconteceria se tivéssemos um método que se chamasse reattach ou update?

Por isso o método é o merge. Ele junta a possível entidade com mesmo id que se encontra no EntityManager com a passada como argumento, e devolve a que está managed. O método merge não faz reattach. Então:

Cliente c = newCliente();
c.setId(1);
em.merge(c);
c.setNome("Cliente com nome alterado");

Não surtirá efeito! Aqui você precisava antes ter pego o que o merge devolveu. Repare na pequena alteração:

Cliente c = newCliente();
c.setId(1);
c = em.merge(c);
c.setNome("Cliente com nome alterado");

Pronto. Uma pequena introdução sobre o ciclo de vida de uma entidade em relação a um EntityManager:

transient (a especificação chama de new), managed e detached!

Por último falta entender como remover uma entidade. Para tal, o JPA possui um método remove() que recebe a entidade a ser removida.

em.getTransaction().begin();

c = em.find(clientes.class, id); // seleciona o cliente
em.remove(conta); // cliente removido do banco 

em.getTransaction().commit();
em.close();

Logo após a chamada de remove(), o objeto não tem mais representação no banco, já que foi removido. Porém, o objeto continua existindo na memória, em um estado conhecido como Removed.


Relacionando as tabelas

  1. Crie uma classe no pacote models, chamada Movimentação.

Devemos então primeiro anotar o atributo com @Temporal, depois definir o parâmetro de precisão desejado (TemporalType). Aqui temos 3 opções:

  • DATE: somente a data, sem a hora;
  • TIME: somente a hora, sem data;
  • TIMESTAMP: tanto data quanto hora.

O que queremos mapear é um relacionamento entre as entidades, que no banco deverá ser refletido por uma chave estrangeira (Foreign Key).

@Entity
public class Movimentacao {

   @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Integer id;
   private BigDecimal valor;

   @Enumerated(EnumType.STRING)
   private TipoMovimentacao tipoMovimentacao;

   @Temporal(TemporalType.DATE)
   private Calendar data;

   @ManyToOne
   private Conta conta;

   @ManyToMany
   public List<Categoria> categorias;

   // getters e setters omitidos

}
  1. Crie uma classe no pacote models, chamada Clientes.
@Entity
public class Cliente {


    @OneToOne
    public Conta conta;

    // getters e setters omitidos
}

ATENÇÃO:

Não podemos esquecer de registrar a nova entidade Movimentacao no arquivo persistence.xml. Vamos editar o arquivo

persistence.xml e acrescentar na _persistence-unit _a nova entidade:

Criando o ENUM

  1. Crie um enum no pacote models com o nome TipoMovimentacao.
public enum TipoMovimentacao {

   ENTRADA, SAIDA;
}

Pesquisas orientadas a objeto

Com JPA podemos tornar isso mais simples usando apenas o Java Persistence Query Language. O JPQL é uma linguagem de consulta, assim como o SQL, porém orientada a objetos. Isso significa que quando estivermos pesquisando dados, não consideramos nomes de tabelas ou colunas, e sim entidades e seus atributos. Através dessa linguagem temos acesso a recursos que o SQL não nos oferece, como polimorfismo e até mesmo maneiras mais simples de buscarmos informações através de relacionamentos.

public class TestaConsulta {

    public static void main(String[] args) {

        EntityManager manager = new JPAUtil().getEntityManager();

        Conta conta = new Conta();
        conta.setId(1);

        Query query = manager.createQuery(
                        "select m from Movimentacao m where m.conta = :pConta"
                        + " and m.tipoMovimentacao = :pTipo"
                        + " order by m.valor desc");

        query.setParameter("pConta", conta);
        query.setParameter("pTipo", TipoMovimentacao.SAIDA);

        List<Movimentacao> movimentacoes = query.getResultList();

        for (Movimentacao m : movimentacoes) {
            System.out.println("\nDescricao ..: " + m.getDescricao());
            System.out.println("Valor ......: R$ " + m.getValor());
        }
    }
}
public class TestaConsulta {

    public static void main(String[] args) {

        EntityManager manager = new JPAUtil().getEntityManager();

        Categoria categoria = new Categoria();
        categoria.setId(1);

        Query query = manager
                .createQuery("select m from Movimentacao m join m.categoria c where c = :pCategoria");

        query.setParameter("pCategoria", categoria);

        List<Movimentacao> movimentacoes = query.getResultList();

        for (Movimentacao m : movimentacoes) {
            System.out.println("\nDescricao ..: " + m.getDescricao());
            System.out.println("Valor ......: R$ " + m.getValor());
        }

    }
}

Relacionamentos bidirecionais

Queremos um único relacionamento com navegabilidade para ambos os lados (bidirecional) ao invés de dois relacionamentos independentes. Teremos então que configurar o JPA para que ela entenda que esse novo relacionamento representa o mesmo que já temos configurado com a anotação @ManyToOnena classe Movimentacao.

Para atingir esse objetivo, devemos escolher um dos lados para ser o dono do relacionamento e nesse caso somente a tabela do dono é que terá a chave estrangeira, sendo assim, o outro relacionamento existe apenas no mundo OO, não havendo a necessidade de criar uma nova tabela.

Usaremos um atributo da anotação @OneToMany, cujo nome é mappedBy, que indica que essa relação é a mesma representada pelo atributo conta na classe Movimentacao.

  • Na classe Conta coloque o seguinte atributo e seu getter e setter:
// lado forte do relacionamento
// chave estrangeira fica do outro lado
@OneToMany(mappedBy="conta")
private List<Movimentacao> movimentacoes;
  • Na classe Movimentação colocaremos o seguinte atributo:
// lado fraco do relacionamento
// chave estrangeira fica aqui
@ManyToOne
private Conta conta;

Comportamento LAZY dos relacionamentos para-muitos

Isso só acontece porque todos os relacionamento do tipo ToMany assumem um comportamento Lazy (preguiçoso), que por definição só recupera os dados dos relacionamentos quando eles realmente são demandados pela aplicação. Essa é geralmente uma boa estratégia, visto que na maioria das vezes não precisaremos de todos os dados dos relacionamentos entre as entidades, e assim economizamos no processamento das consultas pelo servidor.

Para resolver esse problema, a opção mais "certeira" seria tratar especificamente esse relacionamento como se tivesse um comportamento Eager (ansioso). Para esse fim temos o join fetch. Usando essa instrução fazemos com que um relacionamento que é fetch=LAZY comporte-se como fetch=EAGER para realizar a consulta. Nossa consulta então ficaria assim:

// o resultado irá trazer uma conta para cada movimentação relacionada à ela
Query query = manager.createQuery("select c from Conta c join fetch c.movimentacoes");

// Para não termos valores repetidos na lista dos resultados podemos usar o DISTINCT
Query query = manager.createQuery("select distinct c from Conta c join fetch c.movimentacoes");

// para trazermos até os dados das colunas que não foram preenchidas
Query query = manager.createQuery("select distinct c from Conta c left join fetch c.movimentacoes");

// somando os valores pela conta
String jpql = "select sum(m.valor) from Movimentacao m where m.conta=:pConta and m.tipoMovimentacao=:pTipo";

// criando a média dos valores pela conta
String jpql = "select sum(m.valor) from Movimentacao m where m.conta=:pConta and m.tipoMovimentacao=:pTipo";

// busca o maior gasto que a conta teve
Query query = manager.createQuery("select max(m.valor) from Movimentacao m where m.conta = :pConta");

// conta quantas movimentações teve 
Query query = manager.createQuery("select count(m) from Movimentacao m where m.conta = :pConta");

Mas atenção, o contrário não é possível. Não tem como definirmos um relacionamento como eager e fazer uma consulta lazy para carregar um simples combo, por exemplo. Por isso geralmente deixamos o relacionamento lazy e caso precisemos forçamos o carregamento somente em uma consulta.

Apenas por curiosidade, o FetchType.EAGER

Uma outra forma de fazer isso é transformando todo o relacionamento em eager, fazendo o parâmetro fetch do mapeamento @OneToMany da entidade Conta, receber o valor FetchType.EAGER. Mas isso na maioria dos casos seria uma má prática e afetaria outras áreas da aplicação.

@OneToMany(mappedBy = "conta", fetch = FetchType.EAGER)
private List<Movimentacao> movimentacoes;

Exception

LazyInitializationException ocorre quando não há conexão aberta com o banco de dados no momento da execução de um código lazy. As soluções seriam deixá-la aberta um pouco mais ou trazer os relacionamentos de forma Eager.


Melhorando a busca TypedQuery

Para deixar o nosso código um pouco mais elegante o JPA2 trouxe um novo tipo de objeto para realizarmos queriestype safety (que previne erros de tipos de dados), o TypedQuery. Com ele, conseguimos definir o tipo do objeto que vai ser retornado pela nossa query e, com isso, não precisamos ficar fazendo os casts. Para conseguirmos esse objeto, temos que usar a sobrecarga do método createQuery do EntityManager que recebe um segundo parâmetro indicando o tipo de retorno:

TypedQuery<BigDecimal> query = manager.createQuery(jpql, BigDecimal.class);

// trechos omitidos

BigDecimal resultado = query.getSingleResult();

System.out.println("Total movimentado ..: R$ " + resultado);

Repare que agora não precisamos mais fazer o cast, o próprio TypedQuery consegue inferir o tipo de retorno. Ao executar tudo continua funcionando.


NamedQueries

Há uma outra forma de organizar as queries, além do método DAO. Com o JPA, trabalhamos mais orientados à objetos, fazendo as buscas em cima do nosso modelo. Pensando assim, poderia fazer sentido manter as consultas junto às entidades (Entity).

Costumamos colocar a consulta em cima da entidade. Então, nesse caso, como nossa consulta é relacionada com as movimentações de uma determinada conta, colocaremos ela na entidade Movimentacao.

package br.com.caelum.financas.modelo;

@NamedQuery(name = "mediaDaContaPeloTipoMovimentacao",
             query = "select avg(m.valor) from Movimentacao m where m.conta=:pConta  and m.tipoMovimentacao = :pTipo")

@Entity
public class Movimentacao {

    // trecho omitido

}

Agora podemos ver como praticamente não muda nada com o uso das Named Queries. A diferença é que em vez de usarmos o método createQuery, usamos o createNamedQuery e este, em vez de receber o JPQL, recebe somente seu nome.

Uma vantagem é que as Named Queries são verificadas quando o JPA carrega a entidade. Então se fizermos mudanças no nosso modelo, mas esquecermos de alterar a query, receberemos uma exceção antecipadamente.

Uma outra característica imposta pelo uso das Named Queries é o uso dos Named Parameter. Não tem como concatenar a query na anotação, o que impõe logo de início a boa prática.

results matching ""

    No results matching ""