Você está na página 1de 279

.

Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com


Caelum Sumário

Sumário

1 Persistência com JPA2 e Hibernate 1


1.1 Livrar-se de SQL? 1
1.2 Leitura recomendada 2
1.3 Tirando dúvidas 2
1.4 Para onde ir depois? 2

2 Definição do projeto 4
2.1 Descrição do problema 4
2.2 Tecnologias escolhidas 4
2.3 Uma visão geral sobre o domínio do projeto 5
2.4 Exercícios: Criando o projeto no eclipse 5

3 Mapeamento Objeto Relacional 9


3.1 Trabalhando com o banco de dados 9
3.2 Trabalhando com sistemas orientado a objetos 10
3.3 Evitando o SQL dentro do código Java 10
3.4 Unindo os dois mundos através do Mapeamento Objeto Relacional 11
3.5 A Java Persistence API - JPA 11
3.6 Instalando o Hibernate e o driver do MySQL 12
3.7 Mapeando os modelos através de anotações 16
3.8 Estratégias da JPA para chaves auto geradas 19
3.9 Configurando a JPA 21
3.10 Criando o banco de dados e se conectando 23
3.11 Persistindo objetos dentro de uma transação 24
3.12 Exercícios: Mapeando os modelos e criando o banco de dados 24
3.13 Exercícios Opcionais: Tempo de execução, criação do banco de dados e scanning de entidades 28

4 Organização de persistência: DAO e outros patterns 30


4.1 Encapsulando a criação do EntityManager 30
4.2 Exercícios: Controlando a criação do EntityManagerFactory com a JPAUtil 31

Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com


Sumário Caelum

4.3 Encapsulando a JPA dentro dos DAOs 32


4.4 Queries: Selecionando todas linhas 34
4.5 Para saber mais: Repository 35
4.6 Exercícios: Encapsulando código através do DAO 36
4.7 Estados das entidades e ciclo de vida 38
4.8 O estado Managed 38
4.9 O estado Transient 40
4.10 O estado Detached 41
4.11 O estado Removed 42

5 Complementando o modelo com o uso de relacionamentos 44


5.1 Relacionando a Movimentação com uma Conta 44
5.2 Trabalhando com enumerações e datas 45
5.3 Definindo a cardinalidade do relacionamento 49
5.4 Exercícios: Criando o modelo de Movimentacao 50
5.5 Persistindo objetos envolvidos em relacionamentos 51
5.6 Exercícios: Tentando criar uma nova Movimentação relacionada com uma Conta 52
5.7 E quando falha? Entendendo a TransientPropertyValueException 53
5.8 Buscando objetos em relacionamentos 54
5.9 Atualizando objetos envolvidos em relacionamentos 55
5.10 Criando o DAO de Movimentação 55
5.11 Exercícios: Persistindo e pesquisando em relacionamentos 56
5.12 Para saber mais: Operações em cascata 57
5.13 Callbacks para Entidades 57
5.14 EntityListener 58
5.15 Exercício opcional - EntityListeners 59

6 Introdução a Java EE 60
6.1 Gerenciamento do JPA 60
6.2 Inversão de Controle 62
6.3 Java EE - Java Enterprise Edition 63
6.4 Servidor de aplicação 64
6.5 Para saber mais: JBoss AS se chama WildFly 66
6.6 Exercícios: Instalação do JBoss AS 10 - Wildfly 66
6.7 Diretórios do WildFly 66
6.8 Exercícios: Configurando o WildFly no WTP do Eclipse 67
6.9 Em casa 74

7 Cuidando melhor das conexões com Pool e DataSource 76

Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com


Caelum Sumário

7.1 Escalabilidade e conexões com o banco, qual a relação entre eles? 76


7.2 Connection Pool do Hibernate 77
7.3 Configuração do DataSource no WildFly 77
7.4 Datasource com MySQL 79
7.5 Em casa: Tarefa do Administrador - Configuração do DataSource no Wildfly 82
7.6 Usando o C3P0 como pool para aplicações sem DataSource 83
7.7 Exercícios opcionais: Configurando e gerenciando o pool com o C3P0 83

8 Gerenciamento da JPA com EJB lite dentro de uma aplicação Web 86


8.1 A ideia do projeto web 86
8.2 Exercício: colocando o projeto web no ar 87
8.3 Integrando JPA e o DataSource 92
8.4 Gerenciamento da JPA dentro do WildFly 92
8.5 Introdução aos Enterprise Java Beans 93
8.6 Injeção com @PersistenceContext 93
8.7 Tipos de EJBs 94
8.8 Gerenciamento de transação: modo declarativo 96
8.9 Integrando JSF com EJB através de CDI 97
8.10 Exercícios: Integrando os DAOs ao projeto Web 99
8.11 Exercícios: Alteração da Conta 102

9 EJB 3.1 - Ciclo de vida de Session Beans 103


9.1 Em que momento acontece a injeção? 103
9.2 Ciclo de vida de um Stateless Session Bean 104
9.3 Pool de objetos 105
9.4 Atributos de instância de um Stateless Session Bean 106
9.5 Configurando o pool de instâncias do WildFly 107
9.6 Um bean na aplicação: Singleton 108
9.7 Um bean por cliente: Stateful 108
9.8 Qual tipo de Session Bean devo usar? 109
9.9 Interfaces Locais e Remotas de Session Bean 109
9.10 Para saber mais: Lookup pelo JNDI 110
9.11 Exercícios: Utilizando Session Beans e Pool de Objetos 111

10 Agendamento de tarefas 114


10.1 Agendamentos precisos com TimerService 114
10.2 Agendamento exato 115
10.3 Expressões declarativas com @Schedule 116
10.4 Exercícios: Agendamento 117

Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com


Sumário Caelum

11 Transações e Exceções 119


11.1 Confiabilidade dos dados 119
11.2 Gerenciamento de transação: modo programático (BMT) 120
11.3 O modo declarativo (CMT) 121
11.4 Tratamento de exceções 123
11.5 System Exceptions 124
11.6 Application Exceptions 124
11.7 Exercícios: Manipulando a transação com EJB 3 125
11.8 Exercício opcional - @TransactionAttribute 127
11.9 Exercício opcional - BMT 127

12 Criando queries avançadas para os relatórios 129


12.1 Entendendo a JPQL 129
12.2 Utilizando JPQL com AND e OR no filtro 132
12.3 Exercícios: Buscando todas as movimentações de determinada conta 132
12.4 Exercícios: Buscando movimentações seguindo alguns critérios 133
12.5 Executando buscas com funções 134
12.6 Pesquisando com filtros em relacionamentos 135
12.7 Exercícios: Calculando o total movimentado em uma Conta 136
12.8 Exercícios: Pesquisando no relacionamento 137
12.9 Implementando pesquisas complexas com JPQL 137
12.10 Exercícios: Relatório avançado 141
12.11 Para saber mais: Having 142
12.12 Para saber mais: Named Queries 142

13 Relacionamento bidirecional e Lazyness 144


13.1 E quando eu preciso saber quais são as movimentações da conta? 144
13.2 Exercícios: Testando o novo mapeamento 145
13.3 Relacionamento bidirecional 146
13.4 Exercícios: Utilizando o mappedBy no relacionamento bidirecional 147
13.5 Detalhes do relacionamento bidirecional 148
13.6 Relacionamentos Many To Many 149
13.7 Exercício: Criando um relacionamento Muitos-Para-Muitos 150
13.8 Para saber mais: @JoinTable 153
13.9 Comportamento Lazy 154
13.10 Problema comum na web: LazyInitializationException 154
13.11 Exercício: Relacionamentos Lazy 155
13.12 Inicializando as entidades com Queries planejadas 156
13.13 Exercícios: Queries planejadas 157

Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com


Caelum Sumário

13.14 Para saber mais: Fetch Profile 157


13.15 Resolvendo o problema do Lazy-Loading 158
13.16 Open EntityManager In View com CDI 159
13.17 Exercícios: Implementando o pattern Open EntityManager In View 161
13.18 @BatchSize e Queries n+1 162

14 Recursos avançados: Cache, estatísticas e Locks 164


14.1 Cache de primeiro nível 164
14.2 Exercícios: Testando o cache de primeiro nível 165
14.3 Cache de segundo nível 166
14.4 Cache no WildFly: Infinispan 166
14.5 Habilitando o Infinispan em sua aplicação 167
14.6 Exercício: Configurando e testando o Infinispan 168
14.7 Cache de collections 169
14.8 Exercícios: Utilizando cache para as collections 170
14.9 Invalidadação programática do Cache - javax.persistence.Cache 171
14.10 Exercício opcional: Invalidar programaticamente 172
14.11 Cache de queries 173
14.12 Exercícios: Adicionando cache em consultas 174
14.13 Trabalhando com cache fora de um servidor de aplicação 175
14.14 Exercício opcional: Configurando e testando o EhCache 176
14.15 Para saber mais: Estratégias para invalidar o cache 177
14.16 Vendo nossa performance: Hibernate Statistics 178
14.17 Exercícios: Visualizando estatísticas do hibernate na aplicação web 179
14.18 Extended Persistence Context - Um EntityManager para várias requisições 180
14.19 Lock otimista e pessimista 183
14.20 Exercícios Opcionais: Configurando e testando o lock otimista 185
14.21 Para saber mais: Operações em lote 186
14.22 Exercícios Opcionais: Operações em lote 187
14.23 As novidades do JPA 2.1 no Java EE 7 188

15 Validação e integridade dos modelos 189


15.1 Definição de constraints no banco pelo JPA 189
15.2 Exercícios: Validação através de Constraints 190
15.3 Validando objetos 191
15.4 A forma tradicional de validação 191
15.5 A forma simples: Bean Validation e Hibernate Validator 192
15.6 Utilizando o Hibernate Validator 193
15.7 Exercícios: Adicionando o Hibernate Validator aos modelos 194

Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com


Sumário Caelum

15.8 Outros validadores 195


15.9 Alterando a mensagem de erro padrão das validações 198
15.10 Criando seu próprio validador 199
15.11 Exercícios: Criando um validador customizado 201
15.12 Para saber mais: Agrupando anotações de validação para evitar repetição 203
15.13 Validação em cascata 204
15.14 Para saber mais: Novidades do Bean Validation no Java EE 7 204
15.15 Integração com outras tecnologias 205

16 Mais mapeamentos 206


16.1 Relacionamentos um para um 206
16.2 Exercícios: Mapeando relacionamentos um para um 207
16.3 O pattern Value Object do Domain Driven Design 208
16.4 Implementando value objects com Embeddable 209
16.5 Exercícios: Utilizando o Embeddable em atributos 210
16.6 Para saber mais: Detalhes sobre o Embeddable 213
16.7 Para saber mais: Mapeando chaves compostas 214

17 Detalhes sobre os mapeamentos 216


17.1 Mapeando os objetos para um banco de dados legado 216
17.2 Mapeamento de Herança 217
17.3 Fazendo scripts de criação de tabelas com SchemaExport 220
17.4 Exercícios: Mapeamento de Herança com JPA 221

18 Apêndice - Criando consultas com Criteria 224


18.1 Evitando concatenação de String nas queries 224
18.2 As classes principais da Criteria 225
18.3 Exercício opcional: Conhecendo a Criteria 229
18.4 Exercício opcional: Pesquisa no relacionamento com MetaModel dinâmico 230
18.5 Montando uma consulta dinâmica 232
18.6 Criteria typesafe com Metamodel 233
18.7 Criteria typesafe com Static Metamodel 234
18.8 Exercícios: Consultas utilizando Strings na Criteria 236
18.9 Desafio: Join e Fetch nas Criterias 237
18.10 Exercício opcional: Configurando a geração do Static Metamodel pelo Eclipse 237
18.11 Discussão: Qual tipo de Criteria devo utilizar? 238
18.12 Desafio: Explore a API de Criteria 238

19 Apêndice - Hibernate Envers 239


19.1 Auditando as alterações: Listeners 239

Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com


Caelum Sumário

19.2 Utilizando o Hibernate Envers 240


19.3 Exercícios: Utilizando o Hibernate Envers 240
19.4 Para saber mais: Revision Entity 241

20 Apêndice - Googlando sua aplicação através do Hibernate Search 243


20.1 O problema de busca por texto: a descrição da movimentação 243
20.2 Apache Lucene 245
20.3 Além da especificação da JPA2: Recursos específicos do Hibernate 245
20.4 Encapsulando o Lucene com Hibernate Search 245
20.5 Configuração do Hibernate Search 248
20.6 Exercícios: Indexação das Movimentações 248
20.7 Buscando através do Hibernate Search 250
20.8 Exercícios: Utilizando o Hibernate Search para buscas textuais 251
20.9 Para saber mais: atualizando o índice 252
20.10 Para saber mais: MultiFieldQueryParser 252
20.11 Indexando mais de um campo 253
20.12 Para saber mais: Utilizando o Hibernate Search DSL para facilitar buscas avançadas 253
20.13 Para saber mais: Buscando termos usando AND e NOT 256

21 Apêndice - Hibernate Puro 258


21.1 Entendendo a Diferença JPA e Hibernate 258
21.2 Configuração do hibernate sem JPA 259
21.3 Startup e CRUD com Hibernate 259
21.4 Consultas com Criteria do Hibernate 261
21.5 Recursos especiais nas buscas 266
21.6 Joins e Produto Cartesiano 267
21.7 Scrollable Results um comparativo com o JDBC 268
21.8 Subselect com Detached Criteria 270
21.9 StatelessSession 270

Versão: 20.5.21

Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com


.

CAPÍTULO 1

PERSISTÊNCIA COM JPA2 E HIBERNATE

"Eu respeito o poder eterno, não cabe a mim determinar seus limites, eu não afirmo nada, contento-me em
crer que sejam possíveis mais coisas do que nós pensamos." -- Voltaire, Micromegas, considerado o
primeiro conto de ficção científica da história

As ferramentas de persistência são hoje populares não apenas na plataforma Java. Quais seriam os
atrativos dessas ferramentas? Como tirar o maior proveito delas?

1.1 LIVRAR-SE DE SQL?


Essa parece ser a motivação principal que faz as pessoas tomarem conhecimento dos frameworks de
mapeamento objeto relacional (ORM). Como veremos durante o curso, a JPA2 realmente auxilia
bastante nesse quesito: elimina muito trabalho que teríamos em escrever complicadas queries.

Há o outro lado da moeda: utilizar a JPA2 sem se preocupar um pouco com alguns detalhes de
mapeamento, lazy loading e cache, vai certamente gerar um número excessivo de queries, podendo
facilmente derrubar um banco de dados. Veremos que a JPA2 vai bem além da geração de queries: ela
gerencia o estado de persistência dos objetos, de tal modo que cuidará de detalhes muito além de simples
queries, como o cache, tipo de query a ser realizada, estratégia de persistência e transação, e muito mais.

E a relação da JPA2 com o Hibernate? Por qual optar? É importante saber que a JPA2 é apenas uma
especificação, uma série de interfaces, que precisamos de uma implementação (JPA Provider) para poder
utilizá-la. No curso, esse provider será o Hibernate, mas nada impede que você use outros providers,
como o Apache OpenJPA ou o EclipseLink.

Iremos ver, além das características importantes e que usamos sempre na JPA2, detalhes do dia a dia
que são essenciais para o bom funcionamento de sua aplicação, evitando os erros comuns que podem
causar problemas de performance e escalabilidade.

1 PERSISTÊNCIA COM JPA2 E HIBERNATE 1


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

1.2 LEITURA RECOMENDADA


Um livro que recomendamos a leitura é o "Aplicações Java para Web com JSF e JPA", escrito pelo
Gilliard Cordeiro, ativo participante dos grupos de usuários Java e fundador do JUGMS.

Você encontra o livro a venda em http://www.casadocodigo.com.br, inclusive em versão e-book.

1.3 TIRANDO DÚVIDAS


Para tirar dúvidas dos exercícios, ou de Java em geral, recomendamos o fórum do site do GUJ
(http://www.guj.com.br/), onde sua dúvida será respondida prontamente.

Fora isso, sinta-se à vontade para entrar em contato com seu instrutor e tirar todas as dúvidas que
tiver durante o curso.

1.4 PARA ONDE IR DEPOIS?


O curso FJ-26 é uma excelente continuação para o FJ-25, abordando o JSF2 com detalhes, mostrando
bastante da sua integração com a JPA2 através do CDI, além de diversos componentes visuais, detalhes
do ciclo de vida e boas práticas.

Dentre os cursos mais avançados, o FJ-36 aborda tópicos do Java EE para sistemas ainda mais
robustos, abordando RMI, JNDI, EJB, JMS e Web Services. O FJ-91, conhecido curso de Arquitetura e
Design da Caelum, vai atacar inúmeras escolhas que podemos tomar durante a criação de uma aplicação,
mostrando vantagens e desvantagens de cada framework, de cada decisão de design, protocolo etc.

2 1.2 LEITURA RECOMENDADA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

1.4 PARA ONDE IR DEPOIS? 3


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 2

DEFINIÇÃO DO PROJETO

"Nunca penso no futuro - ele já chegará." -- Albert Einstein

Ao término desse capítulo, você será capaz de:

Descrever o projeto que será desenvolvido durante o curso;


Determinar as tecnologias que utilizaremos.

2.1 DESCRIÇÃO DO PROBLEMA


Controlar as despesas hoje em dia tem se mostrado uma tarefa cada vez mais difícil. Os gastos são
realizados para os mais diversos fins: diversão, alimentação, custos fixos (aluguel e condomínio, por
exemplo) entre outros. Pagamos com cartão de crédito, débito, cheque ou dinheiro. Para facilitar esse
controle, vamos criar um sistema para gerenciar finanças de uma pessoa ou empresa.

Para facilitar o acesso dos usuários, o sistema será Web, sendo possível utilizá-lo de qualquer lugar
para manipular as contas e suas transações.

Uma característica importante do sistema é que as movimentações realizadas dentro de cada conta
poderão ser categorizadas, para que possam ser facilmente agrupadas e pesquisadas posteriormente.

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

2.2 TECNOLOGIAS ESCOLHIDAS

4 2 DEFINIÇÃO DO PROJETO
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

No desenvolvimento do sistema de Controle de Finanças, utilizaremos a JPA2 e o Hibernate para a


persistência dos dados. Aprenderemos o que é a JPA2 e seus principais detalhes durante o curso. A
aplicação que será desenvolvida nas aulas estará disponível como um sistema Web, rodando dentro do
servidor de aplicações WildFly 10. Nesta aplicacão utilizaremos o JSF 2.2 para a camada de visualização,
o EJB3 Lite para controle de transações e o CDI para a integração dos containers.

SISTEMA WEB

O FJ-25 foca no desenvolvimento da sua camada de persistência e gerenciamento de estado,


transações e cache. Como a maioria dos sistemas hoje desenvolvidos são Web, a interface com o
usuário utilizará JSF 2.2 e já estará praticamente pronta. O controle de transações com o banco de
dados será feito com o EJB3 Lite, rodando sobre um servidor de aplicações WildFly 10. Para
integrar o JSF com o EJB, usaremos o CDI. No curso, teremos de fazer a ponte do JSF e do EJB com
a JPA. O JSF 2.2 em si é visto com grande profundidade no FJ-26.

2.3 UMA VISÃO GERAL SOBRE O DOMÍNIO DO PROJETO


O usuário do sistema poderá controlar suas contas. Cada conta poderá possuir diversas
movimentações, das quais é preciso saber o valor, a data, uma breve descrição e o tipo, ou seja, se é uma
movimentação de entrada (um valor está sendo recebido) ou se é de saída (um valor está sendo pago).
Além disso, será possível categorizar as movimentações com palavras chaves.

Dessa forma, teríamos basicamente o seguinte relacionamento:

2.4 EXERCÍCIOS: CRIANDO O PROJETO NO ECLIPSE


1. No Desktop, abra o eclipse usando o ícone existente. Quando perguntado sobre o diretório da
workspace , apenas confirme.

2. Crie um novo projeto:

Vá em File -> New -> Project.

2.3 UMA VISÃO GERAL SOBRE O DOMÍNIO DO PROJETO 5


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Escolha a opção Java Project.

Digite fj25-financas como nome do projeto e clique em Finish.

6 2.4 EXERCÍCIOS: CRIANDO O PROJETO NO ECLIPSE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Confirme a mudança de perspectiva.

2.4 EXERCÍCIOS: CRIANDO O PROJETO NO ECLIPSE 7


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

8 2.4 EXERCÍCIOS: CRIANDO O PROJETO NO ECLIPSE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 3

MAPEAMENTO OBJETO RELACIONAL

"Eu não vou te amar: eu te conheço demais para isso" -- Jean Paul Sartre em Huis Clos (Entre quatro
paredes)

Ao término desse capítulo, você será capaz de:

Compreender o que é ORM;


Entender a diferença entre a JPA e o Hibernate;
Criar as tabelas automaticamente através da JPA;
Mapear objetos para tabelas no banco de dados.

3.1 TRABALHANDO COM O BANCO DE DADOS


Atualmente grande parte das aplicações que são desenvolvidas devem, de alguma maneira, se
integrar com um banco de dados. Para trabalharmos com banco de dados, geralmente trabalhamos com
o paradigma relacional, que nos provê mecanismos para relacionar as diferentes informações que o
nosso sistema armazenará.

Para representarmos as informações no banco, utilizamos tabelas e colunas. As tabelas geralmente


possuem chave primária (PK) e podem ser relacionadas por meio da criação de chaves estrangeiras (FK)
em outras tabelas.

Conseguimos trabalhar com o banco de dados criando tabelas, editando e consultando informações
através do SQL (Structured Query Language), que, apesar de ter um padrão ANSI, apresenta diferenças
significativas dependendo do fabricante.

3 MAPEAMENTO OBJETO RELACIONAL 9


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

3.2 TRABALHANDO COM SISTEMAS ORIENTADO A OBJETOS


Quando trabalhamos com uma aplicação Java, seguimos o paradigma orientado a objetos, onde
representamos nossas informações por meio de classes e atributos. Além disso, podemos utilizar
também herança, composição para relacionar atributos, polimorfismo, enumerações, entre outros.

Note que há outras linguagens, além do Java, que suportam o paradigma de orientação a objetos,
como C#, Ruby e Python.

3.3 EVITANDO O SQL DENTRO DO CÓDIGO JAVA


Quando queremos trabalhar com banco de dados em Java podemos utilizar a API JDBC que nos
provê formas de nos conectar com o banco e executarmos nossos comandos SQL. No entanto, o
problema do JDBC é que precisamos escrever código SQL misturado com o nosso código Java. Dessa
forma, sempre que queremos mudar qualquer SQL da nossa aplicação, precisamos alterar uma classe e
recompilá-la.

Para resolver esse problema, surgiram algumas estratégias, como remover os SQLs da aplicação,
extraindo-os para um arquivo externo, como um txt ou xml , por exemplo. No entanto, esse
mecanismo deveria ser desenvolvido para cada projeto e diferentes estratégias existiam para os diferentes
projetos existentes. Frameworks surgiram para fazer esse isolamento, como o IBatis.

O ideal seria que trabalhássemos apenas com a nossa linguagem de programação e todo o trabalho
do banco de dados que envolve SQL, transação e outros cuidados nos fossem abstraídos. Ou seja, para
"gravarmos" um objeto no banco de dados, queremos utilizar o mínimo de código possível, tal como:
conexao.salva(conta);

O método salva da conexão se encarregaria de fazer todo o trabalho pesado para gerar o SQL

10 3.2 TRABALHANDO COM SISTEMAS ORIENTADO A OBJETOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

adequado que será disparado no banco de dados, assim como gerenciaria as necessidades de conexão e
transação. No fundo, o que queremos é diminuir a necessidade do uso de detalhes do paradigma
relacional e programarmos mais focado com o paradigma orientado a objetos em mente. Mais tempo
para nos dedicarmos a lógica de negócios.

3.4 UNINDO OS DOIS MUNDOS ATRAVÉS DO MAPEAMENTO OBJETO


RELACIONAL
O paradigma relacional é bem diferente do orientado a objetos. Detalhes que existem em um não
existem no outro e isso faz com que seja complexo integrar uma aplicação orientada a objetos com um
banco de dados, muitas vezes exigindo ajustes nos códigos para lidar com a diferença entre esses dois
mundos. Essa diferença também é conhecida como impedância (impedance mismatch).

O que precisamos é trabalhar com esses dois paradigmas para conseguirmos programar apenas
orientado a objetos e toda a complexidade da impedância entre os dois modelos esteja abstraída de nós
desenvolvedores.

Temos que, de alguma forma, converter o que existe no mundo orientado a objetos para o relacional.
Como fazer? Será que é trivial?

O primeiro passo é mapear as diferenças entre os modelos. Como é muito comum no Java, já
existem bibliotecas prontas que fazem esse mapeamento para nós, nesse caso, os famosos frameworks
ORM (Object Relational Mapping).

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

3.5 A JAVA PERSISTENCE API - JPA


Ao longo do tempo, surgiram diversos frameworks ORM, cada um implementado de uma forma
diferente do outro. Com o intuito de padronizar esses códigos, a Sun criou a especificação JPA (Java

3.4 UNINDO OS DOIS MUNDOS ATRAVÉS DO MAPEAMENTO OBJETO RELACIONAL 11


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Persistence API), uma API uniforme que todos os frameworks ORM deveriam implementar para serem
reconhecidos pela Sun.

Como toda especificação, ela deve possuir implementações. Entre as mais comuns, podemos citar:
Hibernate, EclipseLink e Apache OpenJPA.

Para este curso, usaremos a versão 2.1 da JPA, a mais recente, que foi lançada oficialmente em maio
de 2013 junto com o Java EE 7. A implementação que vamos utilizar é o Hibernate, por ser líder de
mercado e opensource.

JPA E HIBERNATE, QUAL É A DIFERENÇA?

É fundamental entender que a JPA é uma especificação, um PDF que define uma série de
interfaces e o que deve acontecer após a invocação de cada método. A JPA 2.1 é definida pela JSR
338 e faz parte do Java EE 7:

http://jcp.org/en/jsr/detail?id=338

Já que JPA2 é uma especificação, você poderá aproveitar todo o conhecimento aqui adquirido se
quiser usar o EclipseLink ou quaquer outro provider, bastando trocar a configuração.

Também veremos algumas características mais avançadas que são dependentes de provider, isto é,
será diferente a forma de fazer em cada provider. Detalhes de estatísticas, pooling e configurações
especiais de cache vão variar bastante, como veremos nos capítulos mais avançados.

3.6 INSTALANDO O HIBERNATE E O DRIVER DO MYSQL


Para instalarmos o Hibernate, o primeiro passo é baixar a biblioteca em http://hibernate.org/ .
No site, basta clicar no botão ORM como na imagem abaixo:

12 3.6 INSTALANDO O HIBERNATE E O DRIVER DO MYSQL


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Clique na opção Downloads como na imagem abaixo:

Selecione a versão do Hibernate que você deseja utilizar no projeto. Neste treinamento utilizaremos a
última versão estável para a JPA 2.1, a versão 5.2.6.Final.

3.6 INSTALANDO O HIBERNATE E O DRIVER DO MYSQL 13


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Já para o drive do MySQL,


você deve baixar a biblioteca no site
http://dev.mysql.com/downloads/connector/j/ , escolhendo a opção Select Plataform. Escolha
depois o tipo de compactação que melhor se adapte ao seu descompactador:

Caso seja perguntado se deseja fazer login com uma conta da Oracle, escolha a opção No thanks, just
start my download.

14 3.6 INSTALANDO O HIBERNATE E O DRIVER DO MYSQL


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Após os downloads, basta extrair os arquivos que estão dentro do pacote da distribuição do
Hibernate e o arquivo mysql-connector-java-5.1.xx-bin.jar do pacote do MySQL. Para nossa
aplicação, vamos precisar dos .jars que estão dentro da pasta lib/required do Hibernate e do .jar
do driver do MySQL. Vamos adicioná-los ao classpath da sua aplicação:

No Eclipse, crie uma pasta chamada lib dentro do seu projeto:

Depois, copie somente os .jars citados anteriormente para ela. Não esqueça de dar um
Refresh no projeto ao voltar para o eclipse:

3.6 INSTALANDO O HIBERNATE E O DRIVER DO MYSQL 15


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Abra a pasta lib , selecione todos os .jars , clique com o botão direito do mouse sobre as
seleções e escolha Build Path > Add to Build Path :

Pronto, seu projeto já está com suporte ao Hibernate e ao MySQL.

3.7 MAPEANDO OS MODELOS ATRAVÉS DE ANOTAÇÕES


O primeiro passo para desenvolvermos o nosso sistema de controle financeiro é escrevermos as
classes que serão necessárias. Começaremos pela criação da classe que representará a conta bancária.

16 3.7 MAPEANDO OS MODELOS ATRAVÉS DE ANOTAÇÕES


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Essa classe deverá guardar o número da conta, agência, banco e nome do titular:
package br.com.caelum.financas.modelo

public class Conta {

private String titular;


private String banco;
private String agencia;
private String numero;

// getters and setters que sejam necessários


}

Perceba que a classe conta não tem nada de especial. Ele é um objeto simples Java, um JavaBean, um
POJO.

Vale relembrar que devemos sempre utilizar getters e setters com cuidado: só os criamos de acordo
com a real necessidade. Por exemplo, talvez faça mais sentido, em alguns casos e projetos, ter métodos
como deposita e saca em vez de setSaldo . Veja mais em: http://blog.caelum.com.br/nao-
aprender-oo-getters-e-setters/

Precisamos dizer que a nossa classe Conta será gerenciada pela implementação da JPA que
decidimos utilizar e assim ter sua representação no banco de dados. Para dizermos isso à JPA, basta
utilizarmos a anotação @Entity sobre a classe Conta :
@Entity
public class Conta {
// ...
}

Um detalhe importante sobre a anotação @Entity é que ela vem do pacote javax.persistence ,
o que significa que ela é da especificação JPA, ou seja, ao utilizarmos a anotação já estamos utilizando a
JPA.

Todas as entidades que criarmos para trabalhar com a JPA devem possuir, além da anotação
@Entity , uma indicação de qual atributo representará sua chave primária. Para isso devemos criar um
atributo que represente essa chave. Vamos utilizar um atributo do tipo Integer para representar uma
chave que será um id . Ele deverá estar anotado com @Id :
@Entity
public class Conta {

@Id
private Integer id;

private String titular;


private String banco;
private String agencia;
private String numero;

3.7 MAPEANDO OS MODELOS ATRAVÉS DE ANOTAÇÕES 17


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Somente isso já seria o suficiente para mapearmos a nossa entidade. Dessa forma, no momento em
que precisarmos persistir essa conta no banco de dados, a JPA esperará uma tabela com as seguintes
características: campos texto (varchar, text, etc) para titular, agencia e numero, e um campo numérico
para id. O tipo não precisa ser exato, apenas compatível para as queries.

No entanto, repare que em nenhum momento explicitamos o nome da tabela, ou que queríamos os
campos titular , agencia e numero nas nossas tabelas. Então como ele descobriu isso? Que regra
ele usa para saber quais campos a tabela deveria ter? A JPA segue a ideia de que não precisamos
configurar todos os pequenos detalhes e para isso ela já vem com diversas convenções dentro dela. Essas
convenções dizem que o nome da tabela é o nome da classe da entidade e também que todos os atributos
da sua entidade se tornarão colunas da sua tabela.

Esse conceito é conhecido como Convention over Configuration e é muito comum em frameworks
MVC e ORM.

E se não quisermos a convenção? E se quiséssemos outro nome para a tabela? Sempre que a
convenção não nos servir, podemos fazer configuração, e na JPA essa configuração é sempre feita
usando anotações. Por exemplo, no caso de querermos outro nome de tabela, bastaria anotar a nossa
classe Conta com a anotação @Table indicando no parâmetro name qual nome ela deveria ter:

@Table(name="tb_contas_bancarias")
@Entity
public class Conta {
// ...
}

Uma outra convenção da JPA é com relação ao valor da chave primária. Por padrão, a JPA espera
que o atributo anotado com @Id receba manualmente um valor. No entanto, isso acaba se tornando
bastante trabalhoso muitas vezes, pois precisaríamos gerenciar os valores possíveis para a nossa chave
primária tomando o cuidado de não permitir repetições. Uma solução melhor para isso é deixar o
próprio banco de dados gerar os valores para a chave primária. Muitos bancos de dados possuem
suporte a geração automática de chaves primárias, seja por auto incremento ou sequências.

Para dizermos à JPA que queremos que o banco de dados cuide dessa geração bastaria adicionarmos
à nossa chave primária a configuração adequada. Nesse caso, teríamos que adicionar a anotação
@GeneratedValue no atributo id :

@Entity
public class Conta {

@Id
@GeneratedValue
private Integer id;

private String titular;


private String banco;
private String agencia;
private String numero;

18 3.7 MAPEANDO OS MODELOS ATRAVÉS DE ANOTAÇÕES


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Com o decorrer do curso aprenderemos a customizar os nossos modelos e quais anotações devemos
usar em cada caso.

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

3.8 ESTRATÉGIAS DA JPA PARA CHAVES AUTO GERADAS


Vimos que podemos passar para o banco de dados a responsabilidade da geração automática dos
valores para chave primária simplesmente anotando o atributo com @GeneratedValue . Apenas com
isso a implementação da JPA já vai trabalhar automaticamente com a estratégia de geração de valores
que preferir. Veremos melhor esse caso em breve.

Estratégia (ou strategy) é o nome que se dá às diferentes formas de implementar uma chave cujo
conteúdo é gerenciado pelo próprio banco de dados.

A JPA possui basicamente 4 tipos de estratégias: IDENTITY , SEQUENCE , TABLE e AUTO . No


entanto, é importante lembrar que SEQUENCE e IDENTITY podem não ser compatíveis com o banco de
dados que você estiver usando. Todas essas estratégias são representadas através da enum
GenerationType :

Use valores auto incremento:


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

Especificando a estratégia como IDENTITY forçamos a JPA a escolher utilizar colunas com
valores auto incrementáveis. Atente-se ao fato de que alguns bancos de dados podem não
suportar essa opção (por exemplo PostgreSQL e Oracle).

Gere valores auto incremento através de sequences:


@Id @GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;

Nesse caso, estaremos configurando nossa chave para trabalhar com uma SEQUENCE do
banco de dados. O comportamento padrão nesse caso é uma SEQUENCE global para a

3.8 ESTRATÉGIAS DA JPA PARA CHAVES AUTO GERADAS 19


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

aplicação, onde todas as tabelas compartilhariam uma mesma sequência. Por exemplo, ao
salvar uma Conta ela receberia id 1, depois salvando uma Movimentacao ela receberia o
id 2, e ao salvar outra Conta ela receberia id 3. Em diversos casos, essa abordagem não
chega a ser um problema, mas podemos querer especificar um gerador de chave específico
para cada tabela; ou ainda deixar tabelas simples com o gerador global e algumas específicas
com um gerador individual. Para fazer essa configuração, basta darmos um nome diferente
para cada gerador de ids que desejarmos, através do atributo generator na anotação
@GeneratedValue .

@Entity
public class Conta {

@SequenceGenerator(name = "contaGenerator",
sequenceName = "CONTA_SEQ", allocationSize = 10)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "contaGenerator")
private Integer id;
// outros atributos e métodos
}

@Entity
public class Movimentacao {
@SequenceGenerator(name = "movimentacaoGenerator",
sequenceName = "MOVIMENTACAO_SEQ", allocationSize = 10)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "movimentacaoGenerator")
private Integer id;
// outros atributos e métodos
}

Dessa forma, teremos cada entidade seguindo uma sequência de id própria. Vimos ainda a
anotação @SequenceGenerator , anotação usada para especificar alguns detalhes da sequência, como o
nome e sua allocationSize , que serve pra definir de quantos em quantos números a SEQUENCE sera
chamada. Por exemplo, acima definimos 10, isso significa que a SEQUENCE será chamada pela primeira
vez e devolverá 1, mas já serão buscados 10 números. Para os próximos 9 ids , eles serão buscados da
memória, e só depois do décimo a SEQUENCE será chamada novamente. Isso evita diversas idas ao
banco para gerar a chave. Se não especificarmos nada, o tamanho padrão do allocationSize é 50.
Uma outra opção interessante para o @SequenceGenerator é a definição do valor inicial da sequência,
através do atributo initialValue .

Assim como acontece com a estratégia 'IDENTITY', alguns bancos de dados podem não suportar
esta opção (como por exemplo o MySQL que usaremos durante o curso)

Use uma tabela auxiliar:


@Entity
public class Conta {
@TableGenerator(
name = "CONTA_GENERATOR",

20 3.8 ESTRATÉGIAS DA JPA PARA CHAVES AUTO GERADAS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

table = "GENERATOR_TABLE",
pkColumnValue = "CONTA")
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator="CONTA_GENERATOR")
private Integer id;
// outros atributos e métodos
}

Quando utilizamos a estratégia Table , é necessária uma tabela que vai gerenciar as chaves da nossa
entidade. Podemos ter uma mesma tabela para todo o sistema, que vai diferenciar o dono da chave pelo
conteúdo do atributo pkColumnValue da @TableGenerator . Podemos também ter mais de uma
tabela de controle. O único atributo obrigatório é o nome do gerador, todo o resto podemos não
especificar e deixar a convenção fazer seu trabalho.

MAIS DETALHES SOBRE MAPEAMENTO DE CHAVES SIMPLES E COMPOSTA

Para mais algumas informações de como a JPA lida com o mapeamento de chaves simples e
compostas, você pode consultar o livro Aplicações Java para a Web com JSF e JPA editado pela
Casa do Código (www.casadocodigo.com.br)

Deixe a implementação da JPA escolher:


@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

O tipo AUTO não chega a ser uma estratégia real, ela serve para deixar a implementação da
JPA decidir a melhor estratégia dependendo do banco utilizado, tendo assim uma maior
portabilidade. Além disso, quando não definimos a estratégia, esse é o valor utilizado por
padrão.

A partir da versão 5.0 do Hibernate, ao utilizar a estratégia AUTO , caso o banco de dados suporte a
estratégia SEQUENCE , esta será utilizada. Caso contrário, a estratégia TABLE será a escolhida pelo
framework. No caso do EclipseLink, a estratégia escolhida é sempre a TABLE .

3.9 CONFIGURANDO A JPA


A JPA nos oferece mecanismos necessários para nos conectar com o banco, no entanto, precisamos
fornecer informações para que ela consiga chegar até o banco. Para isso criaremos o arquivo
persistence.xml que ficará na nossa pasta META-INF dentro da pasta src do código fonte (e em
tempo de execução ele será lido a partir dessa pasta, que estará no seu classpath).

Os dados que vão nesse arquivo são divididos entre detalhes específicos da implementação, no caso o
Hibernate, e alguns que são padrões para qualquer implementação de JPA2 que viermos a utilizar.

3.9 CONFIGURANDO A JPA 21


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Vamos começar com as configurações básicas e depois veremos detalhes mais avançados como controle
de cache, connection pool, validação etc.

Para nosso sistema, inicialmente, temos que informar que a implementação que vamos utilizar é o
Hibernate e quais classes devem ser gerenciadas por ele, mesmo tendo anotado nosso modelo,
precisamos indicar novamente nesse arquivo. Além disso, temos as configurações que são obrigatórias
para qualquer aplicação Java que vá conectar a um banco de dados, e que já conhecemos do JDBC: string
de conexão com o banco, o driver, o usuário e senha. Precisamos também informar qual dialeto de SQL
deverá ser usado no momento que as queries são geradas, no nosso caso MySQL.
<persistence version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persi
stence/persistence_2_1.xsd">

<!-- unidade de persistencia com o nome controlefinancas -->


<persistence-unit name="controlefinancas">

<!-- Implementacao do JPA2.1, no nosso caso Hibernate 5.2.6 -->


<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

<!-- Aqui são listadas todas as entidades -->


<class>br.com.caelum.financas.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/fj25" />
<property name="javax.persistence.jdbc.user" value="root" />
<property name="javax.persistence.jdbc.password" value="" />

<!-- Configuracoes especificas do Hibernate -->


<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"
/>

<property name="hibernate.show_sql" value="true" />


<property name="hibernate.format_sql" value="true" />

<!-- poderia ser: update, create, create-drop, none -->


<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
</persistence-unit>
</persistence>

Inserimos mais algumas configurações opcionais para facilitar o nosso desenvolvimento:

hibernate.hbm2ddl.auto estamos usando a opção update que indica ao banco para


atualizar o schema sempre que fizermos alguma alteração no modelo
hibernate.show_sql serve para exibir os SQLs
hibernate.format_sql serve para formatar os SQLs exibidos

22 3.9 CONFIGURANDO A JPA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

ESTRATÉGIAS DE CRIAÇÃO DO BANCO DE DADOS

A propriedade hibernate.hbm2ddl.auto aceita alguns valores para que possamos indicar


como o Hibernate deverá se comportar para efetuar a criação e atualização das tabelas. Um dos
exemplos mais comuns de ser utilizado é a opção update , com a qual o Hibernate tentará fazer
apenas alterações incrementais ao modelo, evitando qualquer exclusão.

Ainda é possível utilizar a opção create-drop onde quando fechamos a


EntityManagerFactory as tabelas são automaticamente excluídas e todos os dados são perdidos.

Outra opção é o create onde o Hibernate remove as tabelas e em seguida as cria a partir das
classes anotadas.

Por fim, também é possível utilizar a opção validate onde é executada apenas uma
verificação para saber se o banco de dados está "sincronizado" com o modelo de classes mas nada é
executado no banco de dados.

3.10 CRIANDO O BANCO DE DADOS E SE CONECTANDO


Para nos comunicarmos com o banco precisamos de um objeto do tipo EntityManager , para obtê-
lo precisamos de uma EntityManagerFactory , que é análoga ao DriverManager de quando
trabalhamos direto com JDBC .

Para adquirir um EntityManagerFactory , usaremos a classe Persistence e com esse objeto


criado, poderemos criar um novo EntityManager . Trabalhando com a JPA, podemos definir diversas
unidades de persistência. Para o nosso projeto definimos apenas uma, a controlefinancas.
EntityManagerFactory factory =
Persistence.createEntityManagerFactory("controlefinancas");

EntityManager manager = factory.createEntityManager();

No momento em que a EntityManagerFactory é criada, uma das operações que são feitas é a
criação das tabelas. Isso é determinado pela configuração hibernate.hbm2ddl.auto definida no
persistence.xml onde colocamos o valor como update , que significa que o Hibernate tentará fazer
atualizações incrementais no schema do banco de dados de acordo com o modelo das classes.

3.10 CRIANDO O BANCO DE DADOS E SE CONECTANDO 23


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

3.11 PERSISTINDO OBJETOS DENTRO DE UMA TRANSAÇÃO


Através de um objeto do tipo EntityManager é possível gravar novos objetos no banco. Para isso,
basta utilizar o método persist dentro de uma transação:

Conta conta = new Conta();


conta.setTitular("José Roberto");
conta.setBanco("Banco do Brasil");
conta.setNumero("123456-6");
conta.setAgencia("0999");

EntityManagerFactory factory =
Persistence.createEntityManagerFactory("controlefinancas");

EntityManager manager = factory.createEntityManager();

manager.getTransaction().begin();
manager.persist(conta);
manager.getTransaction().commit();
manager.close();

3.12 EXERCÍCIOS: MAPEANDO OS MODELOS E CRIANDO O BANCO DE


DADOS
1. Dentro do diretório src crie a pasta META-INF. No Desktop, clique no ícone Atalho para
arquivos dos cursos , entre no diretório 25 e copie o arquivo persistence.xml. Lembre-se, o
persistence.xml é o arquivo de configuração onde você vai informar, por exemplo, qual
implementação da JPA2 está utilizando, além de fazer toda configuração relativa ao banco de dados
que vai utilizar.
<persistence version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persi
stence/persistence_2_1.xsd">

24 3.11 PERSISTINDO OBJETOS DENTRO DE UMA TRANSAÇÃO


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

<!-- unidade de persistencia com o nome controlefinancas -->


<persistence-unit name="controlefinancas">

<!-- Implementacao do JPA2.1, no nosso caso Hibernate 5.2.6 -->


<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

<!-- Aqui são listadas todas as entidades -->


<class>br.com.caelum.financas.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/fj25" />
<property name="javax.persistence.jdbc.user" value="root" />
<property name="javax.persistence.jdbc.password" value="" />

<!-- Configuracoes especificas do Hibernate -->


<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"
/>

<property name="hibernate.show_sql" value="true" />


<property name="hibernate.format_sql" value="true" />

<!-- poderia ser: update, create, create-drop, none -->


<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
</persistence-unit>
</persistence>

2. Vamos preparar nosso projeto com as dependências que o Hibernate, como implementação da JPA2,
precisa.

FAZENDO EM CASA

Caso você esteja fazendo esse passo de casa, pode seguir o passo a passo descrito na sessão
Instalando o Hibernate e o driver do MySQL deste mesmo capítulo.

Copie os JARs do Hibernate para a pasta lib do seu projeto dessa forma:

Clique com o botão direito no nome do projeto fj25-financas e escolha New -> Folder. Escolha lib
como nome dessa pasta e pressione Finish.

3.12 EXERCÍCIOS: MAPEANDO OS MODELOS E CRIANDO O BANCO DE DADOS 25


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Vá ao seu Desktop e clique no ícone Atalho para arquivos dos cursos

Entre no diretório 25 e depois no diretório hibernate-jpa2 ;

Selecione todos os arquivos, clique com o botão direito e escolha Copy;

Cole todos os JARs na pasta workspace/fj25-financas/lib

3. Copie o JAR do driver do MySQL para a pasta lib do projeto também.

No Desktop, entre novamente no ícone Atalho para arquivos dos cursos e depois no
diretório 25 ;

Selecione o driver mais novo do MySQL no diretório mysql-driver , clique com o botão direito
e escolha Copy;

Cole-o na pasta workspace/fj25-financas/lib

4. Vamos adicionar os JARs no classpath do eclipse:

Clique da direita no nome do seu projeto, escolha Refresh.

Abra a pasta lib e selecione todos os arquivos .jars ;

Clique novamente da direita sobre as seleções dos .jars , escolha no menu Build Path -> Add to
Build Path;

26 3.12 EXERCÍCIOS: MAPEANDO OS MODELOS E CRIANDO O BANCO DE DADOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

5. Crie a classe Conta no pacote br.com.caelum.financas.modelo e crie 4 atributos do tipo


String : titular , agencia , numero e banco , além de um id do tipo Integer .

6. Gere os getters e setters usando o eclipse. (Source -> Generate Getters and Setters ou Ctrl + 3 -> ggas).

Lembrando que devemos gerar getters e setters de acordo com cada caso, mesmo em uma entidade
eles podem não ser necessários.

7. Anote a sua classe como uma entidade de banco de dados. Lembre-se que essa é uma anotação do
pacote javax.persistence :

@Entity
public class Conta {
// atributos e métodos
}

8. Anote seu atributo id como chave primária e como campo de geração automática. É boa prática
também definir a estratégia que o banco usará para gerenciar esse campo. Utilizaremos IDENTITY
como estratégia:

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

1. Crie o banco de dados no MySQL. Para isso abra o terminal e rode os seguintes comandos:

no terminal digite: mysql -u root


já dentro do mysql digite: show databases;
se aparecer a base fj25 , significa que já havia um banco criado, que pode conter dados. Apague-
o com drop database fj25;

3.12 EXERCÍCIOS: MAPEANDO OS MODELOS E CRIANDO O BANCO DE DADOS 27


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

agora crie o novo banco: create database fj25;

ainda no mysql digite: quit 1. Crie a classe TestaInsereConta no pacote


br.com.caelum.financas.teste , ela vai instanciar um objeto Conta , e, através de um
EntityManager , fará uma persistência durante uma transação.

package br.com.caelum.financas.teste;

//imports omitidos

public class TestaInsereConta {


public static void main(String[] args) {

EntityManagerFactory factory = Persistence.


createEntityManagerFactory("controlefinancas");

EntityManager manager = factory.createEntityManager();

Conta conta = new Conta();


conta.setTitular("José Roberto");
conta.setBanco("Banco do Brasil");
conta.setNumero("123456-6");
conta.setAgencia("0999");

manager.getTransaction().begin();
manager.persist(conta);
manager.getTransaction().commit();
manager.close();
factory.close();

System.out.println("Conta gravada com sucesso!");


}
}

9. Execute o método main criado. Para verificar a inserção, acesse o MySQL via terminal e verifique se a
conta foi, de fato, gravada. Para fazer isso, siga os seguintes passos:

Vá ao Desktop e abra o Terminal;

Digite mysql -u root;

Digite use fj25;

Execute a seguinte query: select * from Conta;

3.13 EXERCÍCIOS OPCIONAIS: TEMPO DE EXECUÇÃO, CRIAÇÃO DO


BANCO DE DADOS E SCANNING DE ENTIDADES
1. Após o exercício anterior, é possível perceber que existe uma pequena demora para que o INSERT
seja executado. Podemos medir essa demora criando um simples cronômetro. Para isso, adicione na
primeira linha do método main da classe TestaInsereConta o seguinte código:
long inicio = System.currentTimeMillis();

28 3.13 EXERCÍCIOS OPCIONAIS: TEMPO DE EXECUÇÃO, CRIAÇÃO DO BANCO DE DADOS E SCANNING DE


ENTIDADES
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

E logo antes de finalizar o método, imprima o tempo total de execução. Para isso, adicione as
seguintes linhas antes do fim do método:
long fim = System.currentTimeMillis();
System.out.println("Executado em: " + (fim - inicio) + "ms");

Execute a classe e veja quanto tempo demora para o método ser executado. Você considera isso um
tempo aceitável para que um simples insert execute? Execute um insert diretamente no banco de
dados usando o terminal e compare os tempos.

2. Remova a declaração da entidade no persistence.xml e tente executar a classe que insere uma
nova Conta no banco de dados. Repare que vai continuar funcionando. Por quê?

3. Teste as outras formas do Hibernate para fazer a criação e evolução do banco de dados. Para isso, no
persistence.xml altere a propriedade hibernate.hbm2ddl para utilizar os valores create ,
create-drop ou validate . Veja as diferenças.

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

3.13 EXERCÍCIOS OPCIONAIS: TEMPO DE EXECUÇÃO, CRIAÇÃO DO BANCO DE DADOS E SCANNING DE


ENTIDADES 29
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 4

ORGANIZAÇÃO DE PERSISTÊNCIA: DAO E


OUTROS PATTERNS

"Tudo tem alguma beleza, mas nem todos são capazes de ver" -- Confúcio

Ao término desse capítulo, você será capaz de:

Identificar os design patterns DAO, Factory e Repository


Compreender algumas boas práticas ao trabalhar com a JPA

4.1 ENCAPSULANDO A CRIAÇÃO DO ENTITYMANAGER


Para acessarmos o EntityManager , sempre precisamos fazer um código parecido com o seguinte:
EntityManagerFactory factory =
Persistence.createEntityManagerFactory("controlefinancas");

EntityManager manager = factory.createEntityManager();

Mas, o que será que acontece quando criamos uma EntityManagerFactory ? No capítulo anterior,
verificamos que, ao gravar uma nova conta no banco, o processo é um pouco mais demorado do que
normalmente seria para realizar um insert em um banco de dados. Isso acontece justamente pelo fato de
estarmos criando a EntityManagerFactory . Nesse momento, a implementação da JPA geralmente lê
todas as anotações que foram colocadas nas entidades, cria conexões com o banco de dados, entre outras
operações.

Para que consigamos trabalhar com a JPA2, devemos criar sempre uma EntityManagerFactory ,
mas não precisamos criá-la a todo instante que precisarmos salvar um novo objeto. Portanto,
precisamos, de alguma forma, fazer com que EntityManagerFactory seja criada uma única vez no
projeto. Para isso, teremos uma classe chamada JPAUtil . Essa classe se encarregará de criar a
EntityManagerFactory uma única vez, e fará isso com o uso de um atributo estático.

public class JPAUtil {

private static EntityManagerFactory factory =


Persistence.createEntityManagerFactory("controlefinancas");
}

Por fim, a classe JPAUtil terá um método que construirá EntityManager quando for preciso
persistir objetos. Podemos criar um método getEntityManager que pedirá para a factory devolver.

30 4 ORGANIZAÇÃO DE PERSISTÊNCIA: DAO E OUTROS PATTERNS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public class JPAUtil {

private static EntityManagerFactory factory =


Persistence.createEntityManagerFactory("controlefinancas");

public EntityManager getEntityManager() {


return factory.createEntityManager();
}
}

É muito importante saber que um EntityManager deve ter seu ciclo de vida tratado com extremo
cuidado. Ele é um objeto caro: consome recursos importantes, como a conexão do banco, caches, etc. É
fundamental tomar cuidado, como exemplo lembre-se sempre de fechar seus EntityManagers ,
verificar se as transações foram comitadas com sucesso e assim por diante.

No dia a dia o indicado é usar um framework que já tenha filtros para trabalhar com o
EntityManager , muitos deles vão até injetar esse objeto para você, facilitando ainda mais o seu
trabalho, como é o caso do JSF2 pelo CDI. Veremos o pattern open session/entitymanager in view
posteriormente no curso, e seu uso é necessário em muitos casos.

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

4.2 EXERCÍCIOS: CONTROLANDO A CRIAÇÃO DO


ENTITYMANAGERFACTORY COM A JPAUTIL
1. Vamos criar a classe JPAUtil no pacote br.com.caelum.financas.util encapsulando as
chamadas para a EntityManagerFactory .
package br.com.caelum.financas.util;

// imports omitidos

public class JPAUtil {

private static EntityManagerFactory factory =


Persistence.createEntityManagerFactory("controlefinancas");

4.2 EXERCÍCIOS: CONTROLANDO A CRIAÇÃO DO ENTITYMANAGERFACTORY COM A JPAUTIL 31


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public EntityManager getEntityManager() {


return factory.createEntityManager();
}
}

2. Altere a classe TestaInsereConta para que ela use a JPAUtil , removendo a criação da
EntityManagerFactory e buscando um EntityManager através de new
JPAUtil().getEntityManager() .

package br.com.caelum.financas.teste;

// imports omitidos

public class TestaInsereConta {


public static void main(String[] args) {

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

Conta conta = new Conta();

conta.setTitular("Jose Roberto");
conta.setBanco("Banco do Brasil");
conta.setNumero("123456-6");
conta.setAgencia("0999");

manager.getTransaction().begin();

manager.persist(conta);

manager.getTransaction().commit();

manager.close();

System.out.println("Conta gravada com sucesso!");


}
}

3. Execute o TestaInsereConta novamente e veja se tudo continua funcionando conforme esperado.

4.3 ENCAPSULANDO A JPA DENTRO DOS DAOS


Conforme nossa aplicação vai crescendo, os códigos que dependem da EntityManager podem se
espalhar. Muitas vezes, algumas operações que dependem de regras específicas acabam sendo duplicadas
no código, por conta desse espalhamento, trazendo dificuldades na manutenção.

Para evitar esse problema, vamos desde o começo encapsular todas as operações que lidarão com o
banco de dados e com a JPA dentro de uma classe, cujo propósito será esconder as operações que serão
feitas com o banco de dados em uma interface mais específica para os nossos problemas.

A classe que encapsulará o código para trabalhar com os objetos persistidos de uma determinada
classe segue um Design Pattern chamado DAO - Data Access Object. Para criarmos um DAO que
encapsulará o acesso aos dados da Conta podemos criar uma classe chamada ContaDao :

32 4.3 ENCAPSULANDO A JPA DENTRO DOS DAOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public class ContaDao {

O próximo passo é criar o método que faz a gravação da conta no banco de dados. Para isso,
queremos ter algo como:
public class ContaDao {
// atributos aqui...

public void salva(Conta c) {


this.manager.persist(c);
}
}

Mas, como conseguiremos a EntityManager ? Uma primeira alternativa seria abrir a


EntityManager através da JPAUtil que criamos anteriormente, dentro do próprio método salva .
Com isso teríamos:
public class ContaDao {
public void salva(Conta c) {

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

manager.getTransaction().begin();
manager.persist(c);
manager.getTransaction().commit();
manager.close();
}
}

Mas temos um problema grave ao fazermos isso. Quando abrimos a EntityManager dentro do
método salva , ela só pode ser fechada dentro do mesmo método e, caso não a fechemos, ela ficará
aberta para sempre. O ideal seria que nosso DAO não se preocupasse com abertura de EntityManager s
nem com tratamento de transações.

Para conseguirmos isso, vamos fazer com que nosso DAO dependa de uma EntityManager para
funcionar e sempre que for preciso usar um DAO, deverá ser provido uma EntityManager para ele.
Bastaria declararmos essa dependência no construtor do DAO :
public class ContaDao {

private EntityManager manager;

public ContaDao(EntityManager manager) {


this.manager = manager;
}

public void salva(Conta c) {


this.manager.persist(c);
}
}

Agora, podemos evoluir o nosso DAO adicionando uma busca pelo id . Para isso, precisamos
invocar o método find na EntityManager passando como parâmetro qual entidade desejamos fazer

4.3 ENCAPSULANDO A JPA DENTRO DOS DAOS 33


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

a pesquisa e qual o id que vamos procurar.

public Conta busca(Integer id) {


return this.manager.find(Conta.class, id);
}

Para fazermos a exclusão de alguma Conta , poderíamos invocar o método remove , passando um
objeto com a entidade que deve ser excluída:
public void exclui(Conta conta) {
this.manager.remove(conta);
}

Dessa forma, nosso DAO agora está como abaixo:

// imports omitidos
public class ContaDao {

private EntityManager manager;

public ContaDao(EntityManager manager) {


this.manager = manager;
}

public void adiciona(Conta conta) {


this.manager.persist(conta);
}

public void remove(Conta conta) {


this.manager.remove(conta);
}

public Conta busca(Integer id) {


return this.manager.find(Conta.class, id);
}
}

ONDE ESTÁ O MÉTODO PARA ALTERAR UMA CONTA?

Por enquanto, deixaremos o nosso DAO sem o método para fazer a alteração das contas. Antes
de vermos como fazê-lo, precisamos aprender alguns detalhes de como a JPA2 trata o ciclo de vida
de nossos objetos e seus estados.

4.4 QUERIES: SELECIONANDO TODAS LINHAS


Uma das operações mais comuns que podemos realizar em uma entidade é a busca de todas as
informações a seu respeito, que estão cadastradas no banco de dados. Essas informações podem ser
conseguidas com a execução de uma Query, pelo método createQuery da EntityManager passando
como parâmetro uma String , cujo valor é parecido com um SQL , indicando de onde queremos
buscar as informações, como abaixo:

34 4.4 QUERIES: SELECIONANDO TODAS LINHAS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public List<Conta> lista() {


return this.manager.createQuery("select c from Conta c").getResultList();
}

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

4.5 PARA SABER MAIS: REPOSITORY


Ultimamente, existe uma corrente bem grande no desenvolvimento de software caminhando para a
ideia de que a modelagem da sua aplicação deve refletir fielmente o domínio que o software se propõe a
trabalhar.

Isso vai desde nomes de classes até os nomes dos métodos. Essa corrente ficou conhecida como o
Domain Driven Design. Eric Evans, um dos seus maiores evangelizadores, escreveu o livro sobre o
assunto chamado Domain Driven Design - Tackling Complexity From The Heart of Software, que é
considerado por muitos desenvolvedores uma leitura obrigatória.

Um dos conceitos que o Domain Driven Design introduz é a questão de uma linguagem única entre a
modelagem e a forma com que o domínio é tratado pelos especialistas e os desenvolvedores. Essa
linguagem única é conhecida como Ubiquitous Language e deve ser refletida no código no momento do
desenvolvimento. A importância da linguagem ubíqua é que com os desenvolvedores e os especialistas
no domínio se referindo aos mesmos termos, diminui-se o ruído da comunicação entre essas equipes e
como consequência, uma diminuição de falhas por conta de mal compreensão do domínio.

Um outro ponto que é muito discutido é a substituição dos DAOs por objetos que estejam mais
atrelados ao domínio e dessa forma possuam menos acoplamento com a infraestrutura. Eles teriam uma
interface mais voltada ao negócio, com verbos e substantivos que possam ser usado na conversa com os
usuários do sistema. Esses objetos seguem o pattern Repository, como uma Agenda para armazenar
Contato s.

No fundo, o objetivo do Domain Driven Design é que o quanto mais parecido o seu código estiver
com o domínio que está sendo trabalhado, melhor, e para isso existem diversas técnicas.

Indo mais longe, há ainda o pattern Active Record, onde a própria entidade possuiria métodos de
busca, leitura, alteração, como conta.save() . Apesar de muito comum em linguagens dinâmicas, essa

4.5 PARA SABER MAIS: REPOSITORY 35


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

abordagem possui complicações para ser implementada em linguagens estaticamente tipadas como Java,
e não teve muita adoção.

4.6 EXERCÍCIOS: ENCAPSULANDO CÓDIGO ATRAVÉS DO DAO


1. Vamos criar um DAO específico para cuidar das Contas . Para isso, crie a classe ContaDao no
pacote br.com.caelum.financas.dao .

2. Na classe ContaDao adicione um construtor para receber a EntityManager e armazene-a em um


atributo de instância:

public class ContaDao {

private EntityManager manager;

public ContaDao(EntityManager manager) {


this.manager = manager;
}
}

3. O próximo passo é criar os métodos adiciona , remove , busca e lista , como:

public Conta busca(Integer id) {


return this.manager.find(Conta.class, id);
}

public List<Conta> lista() {


return this.manager.createQuery("select c from Conta c",
Conta.class).getResultList();
}

Faça o mesmo para adiciona e remove .

4. Altere a sua classe TestaInsereConta para utilizar o ContaDAO em vez de utilizar diretamente o
EntityManager , substituindo a invocação de persist por adiciona do nosso DAO.

public class TestaInsereConta {


public static void main(String[] args) {
EntityManager manager = new JPAUtil().getEntityManager();

ContaDao dao = new ContaDao(manager);

Conta conta = new Conta();


conta.setTitular("Jose Roberto");
conta.setBanco("Banco do Brasil");
conta.setNumero("123456-6");
conta.setAgencia("0999");

manager.getTransaction().begin();

dao.adiciona(conta);

manager.getTransaction().commit();
manager.close();

System.out.println("Conta gravada com sucesso!");

36 4.6 EXERCÍCIOS: ENCAPSULANDO CÓDIGO ATRAVÉS DO DAO


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

}
}

5. Vá ao terminal e verifique no MySQL se a Conta foi realmente adicionada no banco de dados.

6. Vamos testar a busca por id , para isso crie uma classe chamada TestaPesquisaIdConta no
pacote br.com.caelum.financas.teste :

public class TestaPesquisaIdConta {


public static void main(String[] args) {
EntityManager manager = // recupera um EntityManager

ContaDao dao = new ContaDao(manager);

Conta encontrado = dao.busca(1); // usar um ID que exista no banco

System.out.println(encontrado.getTitular());

manager.close();
}
}

7. Vamos fazer também a listagem das Conta s com o uso da classe TestaListagemConta , que deve
acessar nosso DAO e fazer:
public class TestaListagemConta {
public static void main(String[] args) {
EntityManager manager = // recupera um EntityManager

ContaDao dao = new ContaDao(manager);

List<Conta> lista = dao.lista();

for (Conta conta : lista) {


System.out.println(conta.getNumero());
}

manager.close();
}
}

8. Crie a classe TestaRemoveConta para testar a exclusão de uma Conta pelo ContaDao :
public class TestaRemoveConta {
public static void main(String[] args) {
EntityManager manager = // recupera um EntityManager

ContaDao dao = new ContaDao(manager);

manager.getTransaction().begin();

Conta conta = dao.busca(1); // usar um ID que exista no banco


dao.remove(conta);

manager.getTransaction().commit();
manager.close();
}
}

9. Execute novamente o teste da listagem das contas e verifique se ela foi realmente excluída.

4.6 EXERCÍCIOS: ENCAPSULANDO CÓDIGO ATRAVÉS DO DAO 37


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

10. Para finalizarmos o CRUD da Conta vamos fazer a alteração. Para isso crie a classe
TestaAlteraConta com o seguinte conteúdo:

public class TestaAlteraConta {


public static void main(String[] args) {
EntityManager manager = // recupera um EntityManager

ContaDao dao = new ContaDao(manager);

manager.getTransaction().begin();

Conta conta = dao.busca(2); // usar um ID que exista no banco


conta.setTitular("Joãozinho");

manager.getTransaction().commit();
manager.close();
}
}

11. Teste todos os métodos do CRUD da Conta que você criou.

4.7 ESTADOS DAS ENTIDADES E CICLO DE VIDA


Na JPA, todo objeto passa por um ciclo de vida que contempla desde o momento em que ele é
instanciado pela primeira vez com new , passando pelo momento em que ele é salvo no banco e pode
chegar até quando ele é removido do banco de dados. Vamos entender melhor isso.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

4.8 O ESTADO MANAGED


Quando encapsulamos nossas operações da JPA no DAO, criamos um método que buscava registros
no banco de dados pela sua id . Ele usava de forma transparente o método find() do
EntityManager , que devolvia um objeto (ou entidade) com a representação do registro relacional,
armazenado no banco, com aquela determinada id .
public Conta busca(Integer id) {

38 4.7 ESTADOS DAS ENTIDADES E CICLO DE VIDA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

return this.manager.find(Conta.class, id);


}

O que fizemos na verdade, foi um mapeamento objeto-relacional via JPA, para recuperar registros
do banco de dados. Esse objeto devolvido pelo método find() tem como garantia que, enquanto
estiver em uma sessão aberta do EntityManager , manterá sincronismo com sua representação no
banco de dados de algum forma. Esse estado do objeto é conhecido na JPA como Managed
(Gerenciado). Nesse estado, o objeto (ou entidade) é mantido no contexto de persistência (ou
Persistence Context) e garante-se que sua referência em memória será automaticamente sincronizada
com sua representação no banco de dados sempre que houver alguma alteração (como uma chamada de
setter por exemplo). Isso se dá no momento da chamada do método commit da transação aberta do
EntityManager .

public class TestaGerenciandoUmaConta {


public static void main(String[] args) {

// Recupera um EntityManager
EntityManager manager = new JPAUtil().getEntityManager();

// Chama o DAO passando um EntityManager


ContaDao dao = new ContaDao(manager);

// Abre uma transação


manager.getTransaction().begin();

// Busca uma conta gerenciada (managed)


Conta conta = dao.busca(1); // ID 1 deve existir no banco

// Altera o titular
conta.setTitular("Novo titular");

// Consolida as modificações
manager.getTransaction().commit();

manager.close();

}
}

Observe que o find quando executado faz a JPA gerar um SELECT para buscar o registro no
banco, depois, quando o commit é feito, a JPA automaticamente gera um UPDATE para as alterações
feitas no objeto, mantendo assim o seu sincronismo com sua representação no banco de dado.
Hibernate:
select
conta0_.id as id1_0_0_,
conta0_.agencia as agencia2_0_0_,
conta0_.banco as banco3_0_0_,
conta0_.numero as numero4_0_0_,
conta0_.titular as titular5_0_0_
from
Conta conta0_
where
conta0_.id=?
Hibernate:
update

4.8 O ESTADO MANAGED 39


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Conta
set
agencia=?,
banco=?,
numero=?,
titular=?
where
id=?

Sendo assim, nosso grande objetivo é tornar nossas entidades Managed para que a JPA possa
gerenciá-las dentro do Persistence Context.

4.9 O ESTADO TRANSIENT


Mas quando criamos um objeto pela primeira vez, usando o operador new , ele ainda não passou
pela JPA e certamente não possui uma representação no banco de dados (ou seja, ainda não possui uma
id atribuida). Dizemos então que ele está no estado Transient. Para que nosso objeto seja incluido no
banco de dados, receba uma id e torne-se persistente é preciso que ele passe para o estado Managed.
Daí, será incluido no Persistence Context e gerenciado pela JPA. Podemos fazer isso chamando o método
persist do EntityManager . Porém como acabamos de passar esse objeto que era Transient para
Managed, ele só vai de fato para o banco quando fizermos commit na transação.
EntityManager manager = JPAUtil.getEntityManager();

// Cria a conta no estado transient


Conta conta = new Conta();

// Passa a contra para o estado managed


manager.persist(conta);

PERSIST() FAZ O OBJETO VIRAR MANAGED

Perceba que logo após o persist , o objeto se torna Managed. Portanto, se for feita qualquer
alteração neste objeto após o persist() , o Hibernate se encarregará de fazer o devido UPDATE .
Conta contaNova = new Conta();
contaNova.setTitular("Marcos");

EntityManager manager = // recupera o EntityManager

Transaction t1 = manager.getTransaction();
t1.begin();
manager.persist(contaNova);
t1.commit();

Transaction t2 = manager.getTransaction();
t2.begin();
contaNova.setTitular("Manolo");
t2.commit();

40 4.9 O ESTADO TRANSIENT


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

4.10 O ESTADO DETACHED


Uma outra situação que pode acontecer é quando criamos um novo objeto e passamos para ele uma
id de um registro com representação no banco de dados (uma id já atribuida pelo banco a um
registro). Essa é uma situação que lidamos muito no cotidiano, por exemplo quando usamos formulários
de alteração na web. Nesse caso, objetos que já foram gerenciados em algum momento (ou seja, existem
no banco de dados e possuem um id atribuida), são alterados, mas no momento estão desanexados de
um Persistence Context. Dizemos que esses objetos estão no estado Detached (desanexado). Nesse
estado, o objeto possui uma representação no banco de dados, mas nada vai garantir seu sincronismo
com o mesmo, uma vez que ele está desanexado de um contexto de persistência.

EntityManager manager = // recupera um EntityManager

// cria a conta no estado transient


Conta conta = new Conta();

// passa a conta para o estado detached


conta.setId(1); // ID 1 já existe no banco de dados

Existem outras formas de se passar para o estado Detached. Simplesmente fechando ( close() ) ou
limpando ( clear() ) a sessão do EntityManager , todos os objetos que estiverem no Persistence
Context passaram de Managed para Detached imediatamente, ou se chamarmos explicitamente o
método detach do EntityManager passando um objeto que deva ser desanexado. Só não podemos
esquecer que um objeto Detached não será mais sicronizado com o banco de dados.
EntityManager manager = // recupera um EntityManager

// Recuperando contas com ID 1, 2 e 3 para estado managed


Conta conta1 = manager.find(Conta.class, 1);
Conta conta2 = manager.find(Conta.class, 2);
Conta conta3 = manager.find(Conta.class, 3);

// Passando conta1 para o estado detached explicitamente


manager.detach(conta1);

// Fechando o EntityManager
// Conta2 e Conta3 também passam para estado detached
manager.close();

Quando queremos atualizar as informações do banco de dados com um objeto no estado Detached
ou Transient com id preenchido, utilizamos o método merge do EntityManager . Esse método cria
uma entidade no estado managed, copiará as informações da entidade detached que foi passada como
argumento e por fim devolve a entidade managed como retorno do método. Note que o argumento do
método merge não muda para o estado managed, portanto se fizermos qualquer modificação no objeto
que depois da chamada do merge, essas modificações não serão persistidas no banco de dados.

EntityManager manager = // recupera um EntityManager

manager.getTransaction().begin();

// Busca uma conta no banco e retorna um objeto managed

4.10 O ESTADO DETACHED 41


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Conta conta = manager.find(Conta.class, 1);

// Passa o objeto managed para detached


manager.detach(conta);

// Altera o titular de um objeto detached


conta.setTitular("Novo Titular antes do merge");

// Atualiza o banco de dados com as informações do objeto detached


// A variável conta continua no estado detached, porém contaGerenciada
// fica no estado managed
Conta contaGerenciada = manager.merge(conta);

// Altera novamente o titular da conta, porém como conta não é managed,


// essa modificação não será refletida no banco de dados. Para que a
// alteração fosse persistida, teríamos que modificar o objeto da variável
// contaGerenciada
conta.setTitular("Novo titular depois do merge");

manager.getTransaction().commit();

manager.close();

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

4.11 O ESTADO REMOVED


Também é possível que queiramos remover um objeto do banco de dados. Partindo da explicação
que só no estado Managed o objeto possui um vínculo sincronizado em memória com sua representação
no banco de dados, a remoção pode ser feita pelo método remove do EntityManager . Após isso, o
objeto é removido do banco de dados, sai do Persistence Context, porém continua existindo em memória
em um estado conhecido como Removed.
EntityManager manager = // recupera um EntityManager

manager.getTransaction().begin();

// Busca uma conta no banco e retorna um objeto managed


Conta conta = manager.find(Conta.class, 1);

// Remove o objeto do banco de dados mas é


// mantido em memória no estado removed

42 4.11 O ESTADO REMOVED


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

manager.remove(conta);

// Efetua a exclusão definitiva no banco de dados


manager.getTransaction().commit();

manager.close();

Podemos reverter qualquer remoção de registros no banco de dados feitos pela JPA simplesmente
invocando o método persist para um objeto no estado Removed. Imediatamente ele passa para o
estado Managed e é sincronizado com o banco.

FLUSH() FORÇA O SINCRONISMO DOS OBJETOS MANAGED

Uma forma de forçar que todas as entidades Managed no Persistence Context sejam
sincronizadas com o banco de dados imediatamente, antes mesmo do commit da transação, é
chamar o método flush do EntityManager .

Muito mais interessante do que considerar o Hibernate, ou qualquer outra implementação da JPA2,
apenas como um gerador de SQL, é olhar para ele como um gerenciador do estado persistente dos
nossos objetos. Portanto, usamos o Hibernate para fazer com que os nossos objetos estejam no estado
Managed, já que assim terão a representação no banco.

É importante conhecer esses estados e o ciclo de vida de uma entidade em relação ao seu
EntityManager , pois lhe poupará de problemas simples que podem comprometer a aplicação.

O diagrama abaixo mostra exatamente como transitar entre os possíveis estados das entidades na
JPA.

4.11 O ESTADO REMOVED 43


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 5

COMPLEMENTANDO O MODELO COM O


USO DE RELACIONAMENTOS

"Dificuldade em um relacionamento indica uma longa história de problemas." -- Provérbio Chinês

Ao término desse capítulo, você será capaz de:

Mapear relacionamentos entre entidades;


Compreender os detalhes do armazenamento de enumerações no banco de dados;
Pesquisar e persistir objetos envolvidos em relacionamentos.

5.1 RELACIONANDO A MOVIMENTAÇÃO COM UMA CONTA


Sempre que trabalhamos com uma conta bancária, nós realizamos pagamentos, recebemos salários,
transferimos dinheiro para outras contas e assim por diante. As operações que realizamos em nossas
contas são também conhecidas como movimentações, que a partir de agora vamos representar em nosso
sistema. Cada Movimentacao deve possuir uma Conta , ter uma descrição, data de realização e o valor
movimentado. Dessa forma, teríamos o seguinte código:
public class Movimentacao {

private Integer id;


private String descricao;
private LocalDateTime data;
private Conta conta;
private BigDecimal valor;

// getters e setters
}

Utilizaremos BigDecimal para não perder informações de um número com ponto flutuante, que é
bastante importante num caso como esse, onde guardamos valores financeiros. Mais informações e
detalhes sobre o uso dessa classe versus double :

http://blog.caelum.com.br/arredondamento-no-java-do-double-ao-bigdecimal/

44 5 COMPLEMENTANDO O MODELO COM O USO DE RELACIONAMENTOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

5.2 TRABALHANDO COM ENUMERAÇÕES E DATAS


Precisamos também informar qual é o tipo da nossa movimentação, no caso, se é de entrada ou
saída. Para isso, poderíamos criar um atributo do tipo String ou código com um atributo int , mas
essa abordagem teria alguns problemas: teríamos que definir códigos para os possíveis tipos que podem
ser informados para a nossa movimentação; com isso, a chance de um tipo ser passado errado (fazendo
com que nossa classe se comportasse de maneira equivocada) seria grande.

Para contornar esse problema, teríamos que fazer validações nos nossos métodos e gastaríamos
tempo codificando detalhes que não fazem parte do problema que deveria ser resolvido e que na verdade
são problemas de infra estrutura, algo que já vimos que não queremos cuidar e quem deve ser
responsável por isso é o framework ORM, no caso a implementação da JPA que estamos utilizando.

No Java 5, foi introduzida uma nova forma de declarar tipos que nos permite criar objetos que
representem constantes no nosso sistema, a Enum . Para representar o tipo de movimentação, teríamos:
public enum TipoMovimentacao {
ENTRADA, SAIDA;
}

Agora, a nossa classe Movimentacao ficaria:

public class Movimentacao {


private Integer id;
private String descricao;
private LocalDateTime data;
private Conta conta;
private BigDecimal valor;

private TipoMovimentacao tipoMovimentacao;


}

Precisamos configurar a Movimentacao para ela ser gerenciada pela JPA. A classe com as anotações

5.2 TRABALHANDO COM ENUMERAÇÕES E DATAS 45


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

básicas fica da seguinte forma:


@Entity
public class Movimentacao {
@Id
@GeneratedValue(GenerationType.IDENTITY)
private Integer id;
private String descricao;
private LocalDateTime data;
private Conta conta;
private BigDecimal valor;

private TipoMovimentacao tipoMovimentacao;


}

Um detalhe aqui: temos um atributo que é uma Enum e este não tem uma representação direta no
banco de dados. Por exemplo, se temos um atributo do tipo String , geralmente na tabela, ele viraria,
no caso do MySQL, uma coluna do tipo varchar. Para a Enum , no entanto, não temos uma
representação direta. Temos que anotar nosso atributo com uma anotação diferente, para ensinar à JPA
como tratá-lo. Existe uma anotação chamada @Enumerated , e é essa que vamos usar para anotar o tipo
da movimentação.

A JPA define duas abordagens para mapear atributos do tipo Enum . Podemos falar para ela criar um
campo de um tipo numérico na tabela e então ela mapearia cada constante criada a partir da Enum
como um número na tabela.

Para isso, ele segue a ordem das constantes da nossa Enum . Por exemplo, para o
TipoMovimentacao , a ENTRADA seria salva como 0 e a SAIDA como 1.O problema dessa abordagem é
que, caso criássemos mais uma constante, e esta fosse colocada no inicio da nossa Enum , ela agora
passaria a ser salva como 0 e todo nosso código legado seria quebrado. Para conseguirmos isso basta
anotar o atributo tipo com a anotação @Enumerated .

Para exemplificar o mapeamento usando este estilo, nosso atributo ficaria da seguinte forma:

@Enumerated(EnumType.ORDINAL)
private TipoMovimentacao tipoMovimentacao;

A outra abordagem existente é informar à JPA que você quer mapear a Enum como uma String .
Com isso, no banco, cada constante seria representada pelo seu nome. A ENTRADA por exemplo, seria
salva com o texto ENTRADA. Dessa forma não precisamos nos preocupar com criações de novas
constantes, com certeza ela terá um nome diferente e não vai impactar no código legado. Para isso,
vamos usar a mesma anotação Enumerated mas vamos informar que é para ser gravado no banco
como texto. A nossa classe com essa alteração fica da seguinte forma:
@Entity
public class Movimentacao {
@Id
@GeneratedValue(GenerationType.IDENTITY)
private Integer id;
private String descricao;
private LocalDateTime data;

46 5.2 TRABALHANDO COM ENUMERAÇÕES E DATAS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

private Conta conta;


private BigDecimal valor;

@Enumerated(EnumType.STRING)
private TipoMovimentacao tipoMovimentacao;
}

Por default, a JPA vai usar a EnumType.ORDINAL , portanto vamos sobrescrever o comportamento
padrão e especificar que queremos usar o EnumType.STRING para evitarmos problemas futuros com a
ordem dos elementos da Enum .

Para representarmos a data em que uma movimentação é realizada, utilizaremos o tipo


LocalDateTime , fornecido por uma nova API lançada com o Java 8 e disponibilizada no pacote
java.time .

O problema aqui é que a JPA 2.1 foi lançada antes do Java 8 e não possui um suporte nativo para as
classes da nova API TIME, ou seja, pela especificação não é possível armazenar diretamente um
LocalDateTime em uma coluna do tipo TimeStamp. Para resolver esse problema seguindo a
especificação, precisaríamos converter o nosso atributo para java.sql.Timestamp , como explicado no
box abaixo.

Felizmente, nossa implementação da JPA 2.1, o Hibernate 5.6.2, já possui esse conversor que é
necessário, portanto nada precisamos fazer ao mapear nossa classe Movimentacao como uma entidade
da JPA.

5.2 TRABALHANDO COM ENUMERAÇÕES E DATAS 47


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CRIANDO UM CONVERSOR PARA LOCALDATETIME

Caso utilizemos uma implementação da JPA 2.1 que não possua um conversor assim como o
Hibernate 5, o nosso atributo data do tipo LocalDateTime será armazenado no banco de dados
em uma coluna do tipo BLOB ou Binary Large Object , ao invés de fazê-lo em uma coluna do
tipo TimeStamp .

Para que os dados sejam armazenados da forma desejada, precisamos construir um conversor
que transforme objetos do tipo LocalDateTime em java.sql.TimeStamp antes de serem
persistidos, conforme mencionado anteriormente. Então, basta criarmos uma classe que
implemente a interface AttributeConverter definida pela JPA 2.1. Teríamos algo assim:

@Converter(autoApply = true)
public class LocalDateTimeAttributeConverter implements AttributeConverter<LocalDateTime, Timest
amp> {

@Override
public Timestamp convertToDatabaseColumn(LocalDateTime data) {
if(data == null){
return null;
}
return Timestamp.valueOf(data);
}

@Override
public LocalDateTime convertToEntityAttribute(Timestamp data) {
if(data == null){
return null;
}
return data.toLocalDateTime();
}
}

Observe que a interface já define dois métodos para convertermos nossa entidade para o tipo da
coluna do banco de dados e vice-versa. Por fim, anotamos a nossa classe com @Converter para
indicarmos à JPA que temos um conversor de atributos. A propriedade autoApply define que o
conversor sempre será utilizado para atributos desse tipo, no nosso caso, o LocalDateTime .

48 5.2 TRABALHANDO COM ENUMERAÇÕES E DATAS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

TRABALHANDO COM DATAS EM VERSÕES ANTERIORES DO JAVA

Quando trabalhamos com datas na versão Java 7 ou em versões anteriores, normalmente


usamos o tipo Calendar . Mas, como cada banco lida de formas diferentes com dados desse tipo,
precisamos dizer para a JPA qual a forma que queremos armazenar a data no banco.

É necessário anotar o atributo data da Movimentacao com @Temporal, e 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.

No nosso caso, guardaríamos a data como TIMESTAMP :

@Entity
public class Movimentacao {

// demais atributos omitidos

@Temporal(TemporalType.TIMESTAMP)
private Calendar data;

5.3 DEFININDO A CARDINALIDADE DO RELACIONAMENTO


Como dito anteriormente, a implementação da JPA utilizada sempre tentará mapear os tipos do Java
para os tipos adequados no banco de dados. Dessa forma, ao criar um atributo do tipo String no Java,
ele será mapeado para varchar no MySQL e varchar2 no Oracle. Isso vale para os principais tipos
do Java.

No entanto, nossa entidade Movimentacao possui um atributo Conta . Como a JPA mapeará esse
atributo do tipo Conta no banco de dados? Um varchar , um decimal , um int ? Como ela não
conhece por padrão esse tipo de dados, nós precisamos mapeá-lo.

O que queremos mapear é um relacionamento entre ambas as entidades, que no banco de dados
deverá ser refletido por uma chave estrangeira. A única informação que precisamos dizer é qual a
cardinalidade da Movimentacao com relação a Conta . Como, nesse caso eu posso ter na minha
aplicação diversas movimentações, mas cada uma só possui uma única conta, a cardinalidade desse
relacionando é muitos para um. Essa cardinalidade nós representamos no nosso código Java com a
anotação @ManyToOne , como a seguir:

@Entity
public class Movimentacao {

5.3 DEFININDO A CARDINALIDADE DO RELACIONAMENTO 49


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

@Id
@GeneratedValue(GenerationType.IDENTITY)
private Integer id;
private String descricao;
private LocalDateTime data;

@ManyToOne
private Conta conta;

private BigDecimal valor;

@Enumerated(EnumType.STRING)
private TipoMovimentacao tipoMovimentacao;
}

Com isso, a implementação da JPA esperará que exista uma tabela chamada Movimentacao e uma
das suas colunas se chamará conta_id que é uma chave estrangeira apontando para o id da tabela
Conta .

Para aqueles que precisam do máximo de customização, você ainda pode definir atributos sobre a
coluna de chave estrangeira, por exemplo:
@ManyToOne
@JoinColumn(name="conta_pk")
private Conta conta;

5.4 EXERCÍCIOS: CRIANDO O MODELO DE MOVIMENTACAO


1. Vamos criar a entidade Movimentacao e mapear o relacionamento com a Conta .

Crie a classe Movimentacao no pacote br.com.caelum.financas.modelo .

Adicione os atributos da movimentação:


public class Movimentacao {

private Integer id;


private String descricao;
private LocalDateTime data;
private BigDecimal valor;
private Conta conta;

// getters e setters
}

Agora, devemos adicionar os mapeamentos básicos da JPA para a nossa entidade. Para isso, vamos
adicionar a anotação @Entity na nossa classe e as anotações @Id e
@GeneratedValue(strategy=GenerationType.IDENTITY) no atributo id .

Adicione no persistence.xml o mapeamento para a classe Movimentacao . Ele deve ficar da


seguinte forma:
<class>br.com.caelum.financas.modelo.Movimentacao</class>

50 5.4 EXERCÍCIOS: CRIANDO O MODELO DE MOVIMENTACAO


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Agora, crie a Enum TipoMovimentacao no pacote br.com.caelum.financas.modelo com as


constantes de ENTRADA e SAIDA :
public enum TipoMovimentacao {
ENTRADA, SAIDA;
}

Adicione o atributo tipoMovimentacao na classe Movimentacao , além do getter e setter:


private TipoMovimentacao tipoMovimentacao;

Anote o atributo tipoMovimentacao com a anotação @Enumerated , indicando que queremos


que uma String seja guardada:
@Enumerated(EnumType.STRING)
private TipoMovimentacao tipoMovimentacao;

2. Vamos agora anotar nosso atributo conta com a anotação @ManyToOne para indicar ao Hibernate
que ele representa o relacionamento no banco de dados.
@ManyToOne
private Conta conta;

3. Crie uma nova classe de teste, cujo método main adicionará uma nova Movimentacao sem
nenhuma Conta . Verifique se a tabela foi criada corretamente, com a chave estrangeira. Caso
necessário, execute no console do mysql :
desc Movimentacao;

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

5.5 PERSISTINDO OBJETOS ENVOLVIDOS EM RELACIONAMENTOS


Agora que mapeamos o relacionamento, podemos utilizá-lo para armazenar as movimentações feitas
a partir de uma Conta . Por exemplo, para criarmos uma nova conta com uma nova movimentação
teríamos:

5.5 PERSISTINDO OBJETOS ENVOLVIDOS EM RELACIONAMENTOS 51


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Movimentacao movimentacao = new Movimentacao();


movimentacao.setData(LocalDateTime.now());
movimentacao.setDescricao("conta de luz - abril/2010");
movimentacao.setValor(new BigDecimal("54"));
movimentacao.setTipoMovimentacao(TipoMovimentacao.SAIDA);

Precisamos agora persistir a Movimentacao . Para isso, poderíamos buscar uma EntityManager
através da JPAUtil e persistir a Movimentacao :
EntityManager manager = new JPAUtil().getEntityManager();
manager.getTransaction().begin();

Movimentacao movimentacao = new Movimentacao();


movimentacao.setData(LocalDateTime.now());
movimentacao.setDescricao("conta de luz - abril/2010");
movimentacao.setValor(new BigDecimal("54"));
movimentacao.setTipoMovimentacao(TipoMovimentacao.SAIDA);

manager.persist(movimentacao);
manager.getTransaction().commit();
manager.close();

O problema é que, até o momento, essa Movimentacao não possui nenhuma Conta associada a
ela. Para resolver esse problema, poderíamos criar uma Conta nova e associá-la a essa movimentação,
como a seguir:
EntityManager manager = new JPAUtil().getEntityManager();
manager.getTransaction().begin();

Conta conta = new Conta();


conta.setBanco("Banco Santander");
conta.setNumero("99999-9");
conta.setAgencia("999");
conta.setTitular("Maria");

Movimentacao movimentacao = new Movimentacao();


movimentacao.setConta(conta);
movimentacao.setData(LocalDateTime.now());
movimentacao.setDescricao("conta de luz - abril/2010");
movimentacao.setValor(new BigDecimal("54"));
movimentacao.setTipoMovimentacao(TipoMovimentacao.SAIDA);

manager.persist(movimentacao);

manager.getTransaction().commit();
manager.close();

Repare que movimentacao.setConta(conta) associa a Conta à Movimentacao .

No entanto, ao executarmos esse código em um método main , ele não vai funcionar. O que estará
errado com nosso código? É um problema comum de aparecer, e conhecendo bem o ciclo de vida e
estado das entidades na JPA fica fácil reconhecer esses erros. Vamos testar.

5.6 EXERCÍCIOS: TENTANDO CRIAR UMA NOVA MOVIMENTAÇÃO


RELACIONADA COM UMA CONTA

52 5.6 EXERCÍCIOS: TENTANDO CRIAR UMA NOVA MOVIMENTAÇÃO RELACIONADA COM UMA CONTA
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

1. Vamos adicionar uma nova Movimentacao associada a uma Conta . Para isso, crie a classe
TestaSalvaMovimentacaoComConta no pacote br.com.caelum.financas.teste :

public class TestaSalvaMovimentacaoComConta {


public static void main(String[] args) {

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


manager.getTransaction().begin();

Conta conta = new Conta();


conta.setBanco("Banco santander");
conta.setNumero("99999-9");
conta.setAgencia("999");
conta.setTitular("Maria");

Movimentacao movimentacao = new Movimentacao();


movimentacao.setConta(conta);
movimentacao.setData(LocalDateTime.now());
movimentacao.setDescricao("conta de luz - abril/2010");
movimentacao.setValor(new BigDecimal("100"));
movimentacao.setTipoMovimentacao(TipoMovimentacao.SAIDA);

manager.persist(movimentacao);

manager.getTransaction().commit();
manager.close();
}
}

2. Execute essa classe e repare que não vai funcionar. Leia a mensagem de erro com atenção. A seguir
vamos entender o problema que está acontecendo e como resolvê-lo.

5.7 E QUANDO FALHA? ENTENDENDO A


TRANSIENTPROPERTYVALUEEXCEPTION
Ao persistirmos uma entidade que possua uma associação, segundo a JPA, todas as entidades que são
relacionadas precisam estar persistidas também, caso contrário, será lançado uma
IllegalStateException que conterá como causa uma TransientPropertyValueException . Essa
exceção indica que algum objeto das relações que tentamos salvar não estavam devidamente persistidos.

Podemos resolver esse problema simplesmente persistindo a Conta antes de persistir a


Movimentacao . Dessa forma, bastaria adicionar manager.persist(conta) em nosso código antes de
persistirmos a Movimentacao .

Nesse caso, teríamos:

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


manager.getTransaction().begin();

Conta conta = new Conta();


conta.setBanco("Banco Santander");
conta.setNumero("99999-9");
conta.setAgencia("999");
conta.setTitular("Maria");

5.7 E QUANDO FALHA? ENTENDENDO A TRANSIENTPROPERTYVALUEEXCEPTION 53


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

manager.persist(conta);

Movimentacao movimentacao = new Movimentacao();


movimentacao.setConta(conta);
movimentacao.setData(LocalDateTime.now());
movimentacao.setDescricao("conta de luz - abril/2010");
movimentacao.setValor(new BigDecimal("54"));
movimentacao.setTipoMovimentacao(TipoMovimentacao.SAIDA);
manager.persist(movimentacao);

manager.getTransaction().commit();
manager.close();

No caso de queremos associar a Movimentacao a uma Conta previamente existente, bastaria


atribuir o id para uma Conta e passar essa conta para a Movimentacao que será persistida.

Aqui tem mais detalhes sobre essa e outras exceptions:

http://blog.caelum.com.br/transientobjectexception-lazyinitializationexception-e-outras-famosas-
do-hibernate/

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

5.8 BUSCANDO OBJETOS EM RELACIONAMENTOS


E quando precisamos buscar as informações da conta da movimentação de id 20, como devemos
buscá-la?

Pensando da forma relacional, precisaríamos fazer um join entre as tabelas Movimentacao e


Conta por meio do id da Conta para conseguirmos as informações da Conta . Com isso, teríamos
um SQL que pode ser parecido com o abaixo:
select
c.*,
m.*
from
Movimentacao m
inner join Conta c on (m.conta_id = c.id)
where
m.id = 20

54 5.8 BUSCANDO OBJETOS EM RELACIONAMENTOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

O problema dessa abordagem é que o join é necessário para ligar as duas tabelas, já que ambas não
se conhecem. O que é diferente no modelo de objetos, pois a Movimentacao sabe que ela possui uma
Conta . Nesse caso, bastaria pedirmos para a JPA a Movimentacao de id 20 e em seguida, na
movimentação retornada invocarmos o método getConta , como a seguir:

Movimentacao movimentacao = manager.getReference(Movimentacao.class, 20);

System.out.println(movimentacao.getConta().getTitular());

Com apenas duas linhas de código conseguimos o que procurávamos. Ao executar essa query, o
Hibernate vai gerar um SQL parecido com o seguinte:
select
movimentac0_.id as id1_1_,
movimentac0_.conta_id as conta6_1_1_,
movimentac0_.data as data1_1_,
movimentac0_.descricao as descricao1_1_,
movimentac0_.tipo as tipo1_1_,
movimentac0_.valor as valor1_1_,
conta1_.id as id0_0_,
conta1_.agencia as agencia0_0_,
conta1_.banco as banco0_0_,
conta1_.numero as numero0_0_,
conta1_.titular as titular0_0_
from
Movimentacao movimentac0_
left outer join
Conta conta1_
on movimentac0_.conta_id=conta1_.id
where
movimentac0_.id=?

5.9 ATUALIZANDO OBJETOS ENVOLVIDOS EM RELACIONAMENTOS


Para alterar os objetos que estão envolvidos em relacionamentos, podemos seguir a mesma estratégia
que utilizamos para a conta. Basta recuperar o objeto dentro de uma transação e alterarmos suas
propriedades da forma que desejarmos. No momento em que a transação for comitada a alteração será
refletida no banco de dados, já que o objeto encontra-se no estado Managed. Um exemplo é visto no
código a seguir, onde alteramos o nome do titular da Conta da Movimentacao de id 1 :

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


manager.getTransaction().begin();
Movimentacao movimentacao = manager.getReference(Movimentacao.class, 1);

movimentacao.getConta().setTitular("Maria Cecilia");
manager.getTransaction().commit();
manager.close();

5.10 CRIANDO O DAO DE MOVIMENTAÇÃO


Por fim, para não espalharmos o código da JPA por toda a nossa aplicação, vamos encapsulá-la
dentro de um DAO. Vamos criar o MovimentacaoDao . Esse DAO ficará bastante parecido com o

5.9 ATUALIZANDO OBJETOS ENVOLVIDOS EM RELACIONAMENTOS 55


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

ContaDao . Futuramente vamos adicionar funcionalidades específicas para as movimentações dentro


desse DAO.

O DAO de Movimentacao terá os métodos com as operações básicas que podemos realizar sobre a
entidade e ficará parecido com:

public class MovimentacaoDao {

private EntityManager manager;

public MovimentacaoDao(EntityManager manager) {


this.manager = manager;
}

public void adiciona(Movimentacao movimentacao) {


this.manager.persist(movimentacao);
}

public Movimentacao busca(Integer id) {


return this.manager.find(Movimentacao.class, id);
}

public List<Movimentacao> lista() {


return this.manager.createQuery("select m from Movimentacao m",
Movimentacao.class).getResultList();
}

public void remove(Movimentacao movimentacao) {


this.manager.remove(movimentacao);
}
}

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

5.11 EXERCÍCIOS: PERSISTINDO E PESQUISANDO EM


RELACIONAMENTOS
1. Crie a classe MovimentacaoDao no pacote br.com.caelum.financas.dao e, da mesma forma que
fizemos com Conta , crie os métodos adiciona , busca , lista e remove .
2. Vamos utilizar o MovimentacaoDao para testarmos a gravação. Para isso, abra a classe

56 5.11 EXERCÍCIOS: PERSISTINDO E PESQUISANDO EM RELACIONAMENTOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

TestaSalvaMovimentacaoComConta e faça-a utilizar o novo DAO criado.

3. Vamos pesquisar a Conta da Movimentacao que acabamos de adicionar.

Crie a classe TestaBuscaContaDaMovimentacao no pacote br.com.caelum.financas.teste ,


instancie o MovimentacaoDao com um EntityManager e, através do método busca , pegue a
Movimentacao que tem id 1 (ou algum outro id que tenha uma movimentacao com a conta
relacionada) e imprima o nome do titular dessa conta
( movimentacao.getConta().getTitular() ):
Execute a classe e veja se o resultado é o esperado.

5.12 PARA SABER MAIS: OPERAÇÕES EM CASCATA


Se tentarmos remover uma Conta que possua movimentações perceberemos que acontecerá uma
exceção indicando que a restrição da Foreign Key entre a Conta e a Movimentacao não permite que a
exclusão seja feita.

Nesse caso é comum encontrarmos código que excluem todas as movimentações para que seja
possível excluir a Conta . Nesse caso uma abordagem que pode ser seguida é a remoção em cascata,
onde todos os objetos que ficariam "órfãos" também são excluídos.

Com a JPA podemos fazer isso com o uso do atributo cascade nas anotações de cardinalidade do
relacionamento.

Apesar de ser um recurso interessante, abusar dos efeitos de cascata pode tornar o código difícil de
prever: muitos efeitos colaterais podem ocorrer ao invocarmos um simples persist e precisaremos
sempre estudar o mapeamento para saber o que pode acontecer.

5.13 CALLBACKS PARA ENTIDADES


Os Entity Beans têm anotações referentes a eventos de ciclo da vida. Um evento, por exemplo, é a
atualização ou remoção de uma entidade.

Por exemplo, podemos automaticamente preencher a data de alteração quando a movimentação for
salva ou atualizada no banco:

@Entity
public class Movimentacao {

//outros atributos

private LocalDateTime data;

@PrePersist
@PreUpdate
public void preAltera() {
System.out.println("Atualizando a data da movimentacao");

5.12 PARA SABER MAIS: OPERAÇÕES EM CASCATA 57


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

this.setData(LocalDateTime.now());
}

//outros atributos, getters e setters


}

O método anotado com @PrePersist e @PreUpdate também se chama callback. Os callback_s são
chamados automaticamente quando há alguma mudança no ciclo da vido do objeto e um exemplo de
_inversão de controle que veremos mais para frente.

Além do @PrePersist e @PreUpdate temos os seguintes callbacks para entidades:

@PrePersist - antes do EntityManager salvar a instância no BD


@PreRemove - antes do EntityManager remover a instância no BD
@PreUpdate - antes do EntityManager atualizar a instância no BD
@PostRemove - depois do EntityManager remover a instância no BD
@PostPersist - depois do EntityManager salvar a instância no BD
@PostUpdate - depois do EntityManager atualizar a instância no BD
@PostLoad - depois do EntityManager carregar a instância no BD

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

5.14 ENTITYLISTENER
Para deixar as entidades livres dos métodos de callback (e para não repetir código
denecessariamente), existe a possibilidade de definir os callbacks em uma classe separada. A classe
funciona como um Listener e deve ser declarada através da anotação @EntityListener na entidade.

Em um Listener poderíamos gravar um log, enviar um email, fazer auditoria ou avisar outros
componentes (mandar uma mensagem, por exemplo). Pode ser um grande substituto para os triggers
comumente encontrados nos grandes sistemas, tornando essa regra de negócio independente de banco
de dados.

58 5.14 ENTITYLISTENER
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Veja o código de exemplo:

public class MovimentacaoListener {

@PrePersist
public void prePersist(Object entidade) {
System.out.println("Avisa o financeiro que existe uma nova movimentacao ");
}
}

E, na entidade, usamos a anotação EntityListeners :


@Entity
@EntityListeners(MovimentacaoListener.class)
public class Movimentacao {

//atributos e métodos
}

@EntityListener é anotado na classe da entidade. Nesse exemplo, o método prePersist na


classe MovimentacaoListener é chamado antes de gravar uma movimentação.

Os listeners podem ser usados para várias entidades diferentes. Repare que um Listener recebe um
Object.

5.15 EXERCÍCIO OPCIONAL - ENTITYLISTENERS


1. Vamos escrever um listener para as entidades do tipo Movimentacao que automaticamente grava a
data da última alteração no banco.

Adicione a na classe Movimentacao :


@PrePersist
@PreUpdate
public void preAltera() {
System.out.println("Atualizando a data da movimentacao");
this.setData(LocalDateTime.now());
}

2. Adicione uma nova movimentação no banco (mas sem data) e verifique se o callback for chamado.

5.15 EXERCÍCIO OPCIONAL - ENTITYLISTENERS 59


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 6

INTRODUÇÃO A JAVA EE

"Para obtermos êxito no mundo temos de parecer idiotas mas sermos espertos." -- Baron de Montesquieu

Nesse capítulo, você entenderá o porquê do Java EE existir e como instalar o servidor de aplicação
WildFly 10.

6.1 GERENCIAMENTO DO JPA


Usamos nos capitulos anteriores o JPA através do Hibernate. Inicializamos o JPA pela classe
JPAUtil e gerenciamos o EntityManager e a transação manualmente. Segue um trecho de código
que escrevemos várias vezes:
EntityManager manager = new JPAUtil().getEntityManager();
manager.getTransaction().begin();

//usa o manager

manager.getTransaction().commit();
manager.close();

Repare que somos responsáveis por abrir e fechar todos os recursos, além de cuidar das transações.
Abrimos um EntityManager através do JPAUtil , que por sua vez cria uma
EntityManagerFactory . Depois começamos uma transação, pedindo a transação ao EntityManager .
Por fim comitamos a transação e fechamos o EntityManager .

Isso é um exemplo típico que mostra a ausência de inversão de controle. O nosso código precisa
controlar o ciclo de vida dos objetos indicados pelo uso de factories ( JPAUtil ) e chamadas do
begin() , commit() ou close() . Pior, essas chamadas normalmente se espalham pelo nosso código
dificultando a manutenção e legibilidade.

A coisa ainda se torna mais difícil quando queremos utilizar além da JPA outros componentes. Por
exemplo, um pool de conexões, uma biblioteca de segurança ou um log. Todas essas preocupações caem
sobre o desenvolvedor. Não há nenhuma infraestrutura pronta para utilizar e logo estamos gastando
mais tempo desenvolvendo funcionalidades que não são do domínio como por exemplo:

Autenticação
Autorização
Log e Auditoria

60 6 INTRODUÇÃO A JAVA EE
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Gerenciamento de conexão com banco de dados


Gerenciamento de transação
Concorrência

Neste caso, dizemos que é a regra de negócio que está controlando a infraestrutura.

Esse cenário pode ficar ainda mais desanimador se você imaginar que uma aplicação tem muito mais
do que meia dúzia de regras de negócio. Sendo assim, as chamadas aos serviços de infra-estrutura
ficariam espalhadas por toda a aplicação, dificultando sua manutenção.

6.1 GERENCIAMENTO DO JPA 61


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

6.2 INVERSÃO DE CONTROLE


Será que minha aplicação deveria saber de onde vem a conexão e como configurá-la? Será que
devemos nos preocupar com esses detalhes da aplicação?

Nossa aplicação deveria se concentrar nas regras de negócio (ou lógica de negócio, business logic).
Porém, acabamos nos preocupando com toda a infraestrutura da aplicação, e todos os seus detalhes,
como o tamanho do pool de conexões e o gerenciamento do JPA.

Estes serviços de infraestrutura costumam estar espalhados por todas as lógicas de negócios.
Exemplos disso são log e persistência no banco de dados, que não deveriam ser escritos pelo
programador, pois o ideal é que ele foque em regras do negócio.

Seria interessante a nossa lógica de negócios ser chamada por alguém que já nos entregue todos os
serviços de infraestrutura prontos.

Em uma aplicação grande, perderíamos muito tempo escrevendo um pool de conexões realmente
eficiente, por exemplo. E, nesta mesma aplicação, a lógica de negócios já é demasiadamente complexa,
impedindo que você gaste tempo com a infraestrutura e detalhes de performance, clusterização,
segurança, etc.

Estamos invertendo o controle da nossa aplicação. Não somos mais responsáveis pela infraestrutura
da aplicação, e melhor, não somos responsáveis por fazer essas chamadas de serviços comuns: alguém
será responsável por fazer isso.

62 6.2 INVERSÃO DE CONTROLE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

O programador deveria implementar apenas as regras de negócios!

6.3 JAVA EE - JAVA ENTERPRISE EDITION


Vimos que as aplicações, principalmente Web, têm um perfil semelhante, sempre precisando de
serviços, como log, segurança, pool de conexão ou transação. Outros exemplos seriam um serviço para
mandar e receber mensagens (síncronas e assíncronas), APIs para trabalhar com XML e Web Services,
persistência ou Web.

Java EE nada mais é do que uma série de especificações definidas pelo Java Community Process (JCP
- jcp.org). Cada especificação é definida em um Java Specification Request (JSR), um documento que
descreve detalhadamente como deve funcionar um determinado serviço.

A principal ideia é o desenvolvedor seguir um padrão no desenvolvimento, sempre usando as


mesmas configurações e interfaces independente de qual servidor ou biblioteca que execute o serviço.
Ou seja, o desenvolvedor ganha uma certa liberdade e pode escolher entre vários fornecedores a melhor
solução para ele.

Segue uma lista das principais especificações do Java EE:

Java Persistence API - JPA, JSR-338


Java Transaction API - JTA, JSR-907
Enterprise Javabeans - EJB, JSR-318
Context e Dependency Injection - CDI, JSR-299
Java Server Faces - JSF, JSR-314
Java API for XML Web Services - JAX-WS, JSR-224
Java Architecture for XML Binding - JAXB, JSR-222
Java Message Service - JMS, JSR-914

6.3 JAVA EE - JAVA ENTERPRISE EDITION 63


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Java Naming and Directory Interface - JNDI

Além das já conhecidas Servlets, JSP, JSTL e algumas outras.

JAVA EE 7

A nova versão do Java EE já saiu com várias atualizações que facilitam ainda mais a vida do
desenvolvedor com foco na produtividade. Além de atualizações de vários especificações como JMS
2.0, JAX-RS 2.0, JSF 2.2 entre outras, também foram introduzidas novas especificações para
processamento em lote e JSON.

Veja também o artigo no blog da Caelum:

blog.caelum.com.br/novidades-javaee7-2/

6.4 SERVIDOR DE APLICAÇÃO


Um servidor de aplicação tem que implementar as especificações definidas no Java EE. Para ser
compatível, ele será testado e certificado pela Oracle. O papel dele é servir a sua aplicação com uma
infraestrutura robusta e de fácil utilização.

Existem vários servidores no mercado, a maioria compatível com a versão 1.4 do Java EE. Alguns dos
mais famosos são:

Oracle Glassfish
JBoss Application Server
WildFly
Apache Geronimo
IBM WebSphere
Oracle WebLogic

Segue uma lista completa de todos os servidores compatíveis com o Java EE 7:

http://www.oracle.com/technetwork/java/javaee/overview/compatibility-jsp-136984.html

64 6.4 SERVIDOR DE APLICAÇÃO


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

SERVLET CONTAINER

Um servlet container não atende todas as especificações mencionadas. Ele somente implementa
as especificações de JSP e Servlets. O Tomcat (http://tomcat.apache.org) e Jetty são famosos serlvet
containers.

O servidor de aplicação WildFly, por sua vez, usa o Undertow e implementa todas as
tecnologias do Java EE.

No Java EE 6 foram introduzidas sub-profiles. A ideia dos profiles é definir conjuntos de


especificações mínimas Java EE que um servidor precisa implementar. No entanto o Tomcat ou o
Jetty não se encaixam em nenhum dos profiles definidos.

No caso do Tomcat há uma versão que além dos servlets implementa outras especificações
como JSF, EJB e JTA. Esse profile se chama Web Profile e o servidor Apache TomEE
(http://tomee.apache.org) o atende.

RI - REFERENCE IMPLEMENTATION

O servidor Glassfish é a base para a implementação de referência (RI) do Java EE. Isso significa
que o Glassfish sempre será o pioneiro na implementação das novas especificações, e que você pode
confiar nele para saber como o servidor deve se comportar em determinados casos.

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

6.4 SERVIDOR DE APLICAÇÃO 65


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

6.5 PARA SABER MAIS: JBOSS AS SE CHAMA WILDFLY


Para melhor diferenciar a marca JBoss do servidor de aplicações JBoss AS, foi escolhido pela
comunidade um novo nome para o servidor JBoss AS. O resultado é o WildFly, que será utilizado para
as próximas versões do servidor de aplicação da JBoss. WildFly vai focar no Java EE 7 e todos os nomes
antigos serão mantidos.

6.6 EXERCÍCIOS: INSTALAÇÃO DO JBOSS AS 10 - WILDFLY


1. Entre no diretório Atalho para arquivos dos cursos , disponível no seu Desktop , e em
seguida entre na pasta 25 . Copie a pasta wildfly-10.x.x para a pasta pessoal
( /home/jpaXXXX ).

Wildfly corresponde à versão 10 do JBoss AS.

6.7 DIRETÓRIOS DO WILDFLY


No diretório de instalação do WildFly, os principais diretórios são:

bin - para iniciar e parar o WildFly

bin/client - libs necessárias pelo cliente, aqui você encontra os JARs que, por exemplo,
implementam a busca no serviço de nomes JNDI do WildFly e classes que são responsáveis
por gerar os stubs magicamente, através das proxies dinâmicas

docs - documentação

standalone - Arquivos referentes ao modo standalone

standalone/deployments - Projetos deployados no modo standalone

standalone/configuration - configuração de serviços do modo standalone

domain - Arquivos referentes ao modo domain (veremos mais a respeito do modo domain
nos apêndices)

domain/servers - Servers referentes a um domain.

domain/configuration - configuração de serviços do modo domain.

O WildFly possui dois modos de operação - modo standalone, que geralmente usamos para
desenvolver as funcionalidades de uma aplicação, e modo domain, que serve, na verdade, para reger um
grupo de servidores. Utilizaremos, por hora, somente o modo standalone.

66 6.5 PARA SABER MAIS: JBOSS AS SE CHAMA WILDFLY


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

6.8 EXERCÍCIOS: CONFIGURANDO O WILDFLY NO WTP DO ECLIPSE


1. Caso ainda não esteja aberta, abra a view Servers. Digite Ctrl+3 e em seguida Servers .

Clique com o botão direito dentro da aba Servers e escolha New/Server.

6.8 EXERCÍCIOS: CONFIGURANDO O WILDFLY NO WTP DO ECLIPSE 67


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Caso não possua a opção JBoss Community -> WildFly 10.x , procure por Red Hat JBoss
Middleware -> JBoss AS, Wildfly, & EAP Server Tools :

68 6.8 EXERCÍCIOS: CONFIGURANDO O WILDFLY NO WTP DO ECLIPSE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Clique em Next , aguarde o carregamento dos plugins do JBoss e aceite os termos dos plugins:

6.8 EXERCÍCIOS: CONFIGURANDO O WILDFLY NO WTP DO ECLIPSE 69


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Clique em Finish e proceda com a instalação dando OK se aparecer algum box de confirmação da
instalação. Ao final do procedimento, será solicitado que você reinicie o Eclipse.

Faça isso e volte novamente para a aba Servers . Clique com o botão direito e vá em New ->
Server . Agora expanda a opção JBoss Community e escolha WildFly 10.x .

70 6.8 EXERCÍCIOS: CONFIGURANDO O WILDFLY NO WTP DO ECLIPSE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Aperte Next até chegar na tela de escolha do diretório do WildFly. Clique em Browse e selecione a
pasta onde você descompactou o WildFly.

6.8 EXERCÍCIOS: CONFIGURANDO O WILDFLY NO WTP DO ECLIPSE 71


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Vamos utilizar as configurações padrão mas com uso de cache no servidor, por isso precisaremos
alterar o arquivo de configuração do servidor (Configuration file) de standalone.xml para
standalone-ha.xml. Faça isso clicando em Browser e buscando o arquivo em
/home/jpaXXXX/wildfly-10.1.0.Final/standalone/configuration/ . Clique em OK e depois
em Finish .

72 6.8 EXERCÍCIOS: CONFIGURANDO O WILDFLY NO WTP DO ECLIPSE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Ao final do processo, você deverá ver o WildFly na aba Servers :

2. Clique na opção do WildFly na lista de servidores e depois clique no ícone de start.

Verifique se o WildFly está rodando entrando em http://localhost:8080/ .

6.8 EXERCÍCIOS: CONFIGURANDO O WILDFLY NO WTP DO ECLIPSE 73


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

6.9 EM CASA
Em sua casa, faça o download do WildFly no site http://wildfly.org/downloads/.

Escolha a última versão da série 10.1.x Final, que implementa a especificação Java EE 7, incluindo a
especificação JPA 2.1, CDI 1.1 e EJB 3.2.

Você precisa da variável de ambiente JAVA_HOME configurada, para o diretório de instalação do seu
JDK.

Basta, agora, executar o script standalone dentro do diretório bin . Existe um arquivo .sh para
ambientes Unix e .bat caso esteja no Windows.

Ao fazer o download do Eclipse em sua casa pelo site http://www.eclipse.org/downloads/eclipse-


packages/, prefira a opção for Java EE Developers, que possui algumas funcionalidades que precisaremos
para trabalhar com as tecnologias do curso. Essa é a mesma versão que utilizaremos durante o curso,
também.

74 6.9 EM CASA
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Outro ponto importante: certifique-se de que seu Eclipse aponta para uma JDK e não para uma JRE.
Para isso, faça o download da JDK em
http://www.oracle.com/technetwork/java/javase/downloads/index.html e, em seguida, no Eclipse, vá em
Window -> Preferences -> Java -> Installed JREs -> Add e adicione a JDK que você fizer
download. Isso evitará conflitos e problemas com o plugin do JBoss AS.

6.9 EM CASA 75
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 7

CUIDANDO MELHOR DAS CONEXÕES


COM POOL E DATASOURCE

"O primeiro problema para todos, homens e mulheres, não é aprender, mas desaprender" -- Gloria Steinem

Ao término desse capítulo, você será capaz de:

Entender porque usar um pool de conexões


Configurar um DataSource pelo WildFly
Conhecer o pool C3P0 para aplicações sem servidor de aplicação

7.1 ESCALABILIDADE E CONEXÕES COM O BANCO, QUAL A RELAÇÃO


ENTRE ELES?
É comum aplicações terem problemas de escalabilidade ao trabalhar com banco de dados. Diversos
fatores podem colaborar para isso, como queries muito pesadas executando várias vezes. No entanto, um
dos gargalos mais comuns é o processo de conexão com o banco de dados.

Uma aplicação que possui diversos usuários enviando requisições precisa se conectar ao banco de
dados para conseguir realizar consultas e devolver resultados para os clientes. Abrir conexões a todo o
momento é bastante custoso, pois um socket deve ser aberto e gera um tráfego na rede para chegar até o
servidor de banco de dados.

Um overhead que pode ser evitado. Como podemos melhorar?

76 7 CUIDANDO MELHOR DAS CONEXÕES COM POOL E DATASOURCE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

7.2 CONNECTION POOL DO HIBERNATE


Em um ambiente real, com vários clientes simultâneos querendo conectar no banco de dados,
precisamos nos preocupar com a política de aquisição de conexões.

Em um sistema Web, por exemplo, vamos abrir uma nova Connection para cada request feito? E se
estivermos criando um portal com milhares de acessos simultâneos, nosso banco aguentaria milhares de
conexões? Além disso, abrir uma conexão é um processo relativamente custoso para se fazer a cada novo
request.

Será que é melhor então criar apenas uma conexão e compartilhá-la com todos os clientes? E se
alguém faz uma query mais demorada? Todos os outros ficam esperando?

Perceba que não queremos uma conexão só, mas também não queremos uma conexão para cada um.
Queremos um número N de conexões. E, claro, esse número depende da aplicação que estamos
desenvolvendo (número de clientes, quanto o banco aguenta, quantas aplicações usam o mesmo banco,
etc).

A parte da aplicação que gerencia quantas conexões devem ser abertas e as gerencia para os clientes é
chamada de Connection Pool. É boa prática, quando trabalhamos com Bancos de Dados, sempre usar
um Connection Pool, inclusive quando não se usa Hibernate e sim JDBC puro.

O Hibernate já tem suporte nativo com seu próprio pool de conexões. No entanto, esse não é o
recomendado para o uso em produção. Ao iniciar a EntityManagerFactory da sua aplicação, o Hibernate
gera o seguinte log:

11:57:28,699 INFO DriverManagerConnectionProvider:64 - Using Hibernate built-in


connection pool (not for production use!)

7.3 CONFIGURAÇÃO DO DATASOURCE NO WILDFLY

7.2 CONNECTION POOL DO HIBERNATE 77


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Com a configuração da conexão ou pool no persistence.xml , cada aplicação fica conhecendo os


dados de autenticação do banco, ou seja, login e senha está visível no persistence.xml . Além disso,
cada aplicação dentro de servidor gerenciará o seu próprio pool.

Nem sempre isso é desejável ou possível. Há aplicação onde o desenvolvedor não deve conhecer os
dados de autenticação do banco. Também pode fazer sentido compartilhar um único pool com várias
aplicações dentro do mesmo servidor de aplicação.

Por fim, se o servidor oferece um pool de conexões como serviço para a nossa aplicação, podemos
também alterar o tamanho do pool sem "redeployar" a aplicação. Dependendo do servidor, essa alteração
pode ser feita ao vivo, sem causar um desligamento do servidor.

A solução para esses problemas, no mundo Java EE, se chama DataSource . Um datasource
representa uma ligação genérica com uma fonte de dados (normalmente banco do dados), encapsula os
dados de autenticação e usa também um pool de conexões por baixo.

O datasource é um serviço que o servidor de aplicação fornece e fica disponível dentro de um


registro (JNDI - Java Naming and Directory Implementation) para ser acessível por qualquer aplicação
dentro do mesmo servidor.

Uma vez feita a configuração do datasource no servidor, podemos nos referenciar a ele no
persistence.xml .

Como o WildFly possui um banco embutido, o H2, nem é preciso configurar essse datasource. Ele já
está pronto e acessível pelo contexto JNDI: java:jboss/datasources/ExampleDS .

A configuração do datasource padrão está no arquivo standalone-ha.xml , na pasta


standalone/configuration da instalação do Wildfly. As informações mais importantes estão
contidas na tag :
<subsystem xmlns="urn:jboss:domain:datasources:4.0">
<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS"
pool-name="ExampleDS" enabled="true" use-java-context="true">
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;
DB_CLOSE_ON_EXIT=FALSE</connection-url>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>
</subsystem>

Foi declarado o nome JNDI e os dados de acesso. Também podemos colocar configurações de pool
no arquivo standalone-ha.xml , da seguinte forma:
<datasource jndi-name="java:jboss/datasources/ExampleDS"
pool-name="ExampleDS" enabled="true" use-java-context="true">
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;

78 7.3 CONFIGURAÇÃO DO DATASOURCE NO WILDFLY


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

DB_CLOSE_ON_EXIT=FALSE</connection-url>
<driver>h2</driver>
<pool>
<min-pool-size>10</min-pool-size>
<max-pool-size>20</max-pool-size>
<prefill>true</prefill>
</pool>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>

Como a configuração do banco está separada, o arquivo persistence.xml fica muito mais simples:
<persistence>
<persistence-unit name="loja" transaction-type="JTA">

<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>

</persistence-unit>
</persistence>

Repare que usamos a tag jta-data-source para referenciar o datasource no contexto JNDI. O
ExampleDS representa, como já foi mencionado, o nome JNDI do banco H2, ou seja, é apenas um
apelido. No entanto, poderia ser utilizado qualquer outro banco. Só pelo arquivo persistence.xml
não tem como saber qual banco é realmente usado. A configuração está totalmente desacoplada da
aplicação. Veremos mais adiante como configurar um outro datasource para o MySQL.

Repare que isso faz parte do conceito de inversão de controle. A aplicação não se preocupa com
destalhes da configuração do banco. Os dados de acesso e o pool de conexões não fazem parte da
aplicação, pois ela delega esta preocupação para o servidor de aplicação usando um datasource.

A desvantagem dessa abordagem é que sua aplicação fica dependente de uma configuração do
servidor de aplicação e perde a portabilidade. Sempre antes de deployar a aplicação é preciso configurar
o datasource.

TRANSAÇÃO JTA

O tipo de transação deve ser obrigatoriamente JTA , que é o padrão da especificação Java EE.
Dentro de um servidor de aplicação não temos outra escolha. Por enquanto, como não precisamos
nos preocupar com transações, deixaremos o servidor decidir o que fazer com JTA (novamente
inversão de controle).

Veremos mais sobre o gerenciamento nos próximos capítulos.

7.4 DATASOURCE COM MYSQL

7.4 DATASOURCE COM MYSQL 79


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Como nosso banco será o MySQL, precisaremos definir um datasource pra ele. Antes disso, devemos
colocar o JAR do driver do MySQL no Wildfly. Isso pode ser feito de diversas maneiras, entretanto a
mais recomendada consiste em criar um módulo contendo o recurso.

UM POUCO SOBRE A ARQUITETURA DO WILDFLY

Diferentemente das versões 4, 5 e 6, o WildFly possui uma arquitetura baseada em módulos. O


WildFly, na verdade, nada mais é do que uma junção de módulos trabalhando em conjunto. Pense
em módulo como uma unidade que sabe executar uma determinada função. Podemos definir
nossos próprios módulos; por exemplo, criaremos um módulo para armazenar o driver do MySQL.
Os JARs contidos em cada módulo são carregados no servidor somente quando forem necessários
para o funcionamento da aplicação. Esse carregamento é também conhecido como "Modular Class
Loading" do servidor. No WildFly, todas as implementações das especificações estão contidas em
módulos, cada um com uma função específica.

Para criarmos um módulo no WildFly que contenha o driver do MySQL, precisamos seguir algumas
etapas.

Os módulos são criados no diretório /modules


Criamos os subdiretórios com/mysql/main
Obrigatoriamente precisamos do subdiretório main! Caso contrário, o Wildfly não saberá
invocar nosso módulo
Em modules/com/mysql/main copiamos o JAR do driver do MySQL
Em seguida, criamos um arquivo module.xml
O arquivo module.xml deve possuir o seguinte conteúdo:

<?xml version="1.0" encoding="UTF-8"?>


<module xmlns="urn:jboss:module:1.1" name="com.mysql">
<resources>
<resource-root path="mysql-connector-java-5.1.24-bin.jar"/>
</resources>
<dependencies>
<module name="javax.api"/>
</dependencies>
</module>

Seu diretório modules/system/layers/base/com/mysql/main deve estar similar a:

Finalmente, precisamos registrar o driver no arquivo standalone-ha.xml (que está no diretório


/standalone/configuration do Wildfly). Na tag <drivers> adicionamos uma referência
ao módulo que contém o driver do MySQL:

<driver name="com.mysql" module="com.mysql">


<xa-datasource-class>

80 7.4 DATASOURCE COM MYSQL


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
</xa-datasource-class>
</driver>

Agora que criamos um módulo para o JAR do driver do MySQL, vamos efetivamente declarar o
DataSource do banco. No arquivo standalone-ha.xml, dentro da tag <datasources> , adicionamos
uma nova tag de <datasource ...> :

<datasource jndi-name="java:/fj25DS"
pool-name="fj25DS" enabled="true" use-java-context="true">
<connection-url>jdbc:mysql://localhost:3306/fj25</connection-url>
<driver>com.mysql</driver>
<pool>
<min-pool-size>10</min-pool-size>
<max-pool-size>100</max-pool-size>
<prefill>true</prefill>
</pool>
<security>
<user-name>root</user-name>
</security>
</datasource>

Repare que definimos também o tamanho do pool, ou seja a quantidade de conexões que o pool vai
ter no mínimo ( min-pool-size ) e no máximo ( max-pool-size ).

Para definir um datasource que pode participar em uma transação distribuída (JTA) é preciso definir
um xa-datasource . Precisaremos, então, especificar a classe a ser utilizada no driver para estruturar
uma transação distribuída. Na tag <drivers> , onde houver a declaração do módulo contendo o driver
do MySQL, devemos adicionar a declaração da classe para transação distribuída:
<driver name="com.mysql" module="com.mysql">
<xa-datasource-class>
com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
</xa-datasource-class>
</driver>

Como foi discutido anteriormente, o persistence.xml usa apenas o nome JNDI para se
referenciar ao DataSource :
<persistence>
<persistence-unit name="financas" transaction-type="JTA">

<jta-data-source>java:/fj25DS</jta-data-source>

</persistence-unit>
</persistence>

7.4 DATASOURCE COM MYSQL 81


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

7.5 EM CASA: TAREFA DO ADMINISTRADOR - CONFIGURAÇÃO DO


DATASOURCE NO WILDFLY
1. Inicialmente, vamos criar um módulo no WildFly contendo o JAR do driver do MySQL. No
diretório do WildFly, vá na pasta modules/system/layers/base. Lá crie uma pasta com (caso não
esteja criado). Entre na pasta com e crie nela uma pasta mysql. Novamente, entre na pasta mysql e
crie uma pasta chamada main, obtendo o caminho modules/system/layers/base/com/mysql/main.
Caso esteja fazendo essa tarefa pelo console, de dentro da pasta com, basta executar o comando:

mkdir -p mysql/main

Dentro do diretório main, copie o arquivo module.xml e o JAR do driver do MySQL que estarão
dentro da pasta Caelum/25 presente no seu Desktop.

2. Abra o arquivo standalone-ha.xml (que estará no diretório /standalone/configuration do seu


Wildfly) e procure a tag <drivers> . Veja que já existe uma declaração de um driver, vamos mantê-
la e adicionar mais uma. Adicione a declaração do driver contido no módulo que criamos (na pasta
Caelum/25 há um exemplo desse XML no arquivo datasource-wildfly.txt ):

<driver name="com.mysql" module="com.mysql">


<xa-datasource-class>
com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
</xa-datasource-class>
</driver>

3. Ainda no arquivo standalone-ha.xml, procure a tag <datasources> e adicione uma nova


datasource:

<datasource jndi-name="java:/fj25DS"
pool-name="fj25DS" enabled="true" use-java-context="true">
<connection-url>jdbc:mysql://localhost:3306/fj25</connection-url>
<driver>com.mysql</driver>
<pool>
<min-pool-size>10</min-pool-size>
<max-pool-size>100</max-pool-size>

82 7.5 EM CASA: TAREFA DO ADMINISTRADOR - CONFIGURAÇÃO DO DATASOURCE NO WILDFLY


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

<prefill>true</prefill>
</pool>
<security>
<user-name>root</user-name>
</security>
</datasource>

Reinicie o Wildfly, e, se tudo correr bem, você deve encontrar uma linha similar a essa no seu log:

15:34:24,352 INFO [org.jboss.as.connector.subsystems.datasources]


(MSC service thread 1-8) JBAS010400: Bound data source java:/fj25DS

7.6 USANDO O C3P0 COMO POOL PARA APLICAÇÕES SEM


DATASOURCE
O Hibernate liga automaticamente o pool de conexões que vem embutido, mas o manual diz
claramente para você não usá-lo em produção.

O Hibernate possui integração com vários pools de conexão reconhecidos do mercado, entre eles o
apache dbcp pool, proxool e o C3P0. Nós vamos usar o C3P0 que é bastante indicado nos livros pela
própria equipe do Hibernate.

Para configurar o C3P0, além do JAR no classpath do seu projeto, você precisa colocar as seguintes
linhas no persistence.xml :

<property name="hibernate.c3p0.min_size" value="5" />


<property name="hibernate.c3p0.max_size" value="15" />
<property name="hibernate.c3p0.timeout" value="180" />
<property name="hibernate.c3p0.idle_test_period" value="100" />

Só pelo fato de termos alguma propriedade com hibernate.c3p0. , o Hibernate já ativa o C3P0
como implementação de pool. A configuração min_size indica o número mínimo de conexões que o
pool deixa preparada a qualquer momento. A max_size indica o número máximo de conexões no pool.
Esses valores têm que ser determinados com base em diversos fatores da sua aplicação (carga de
usuários, quanto seu banco aguenta e etc).

O timeout indica depois de quanto tempo uma conexão inativa (idle) deve ser retirada do pool. O
C3P0 usa um sistema inteligente em que threads testam se as conexões estão vivas de tanto em tanto
tempo, configurando o idle_test_period .

Existe a possibilidade de você ligar o test_on_burrow de alguns connection pools, fazendo com que
o Hibernate teste se a conexão está viva toda vez que uma conexão for pega do pool. O C3P0 diz que isso
é extremamente mais lento que testar de tempos em tempos com idle_test_period , porém evita ao
máximo que uma conexão quebrada seja utilizada. Mais informações:

http://www.hibernate.org/214.html

7.7 EXERCÍCIOS OPCIONAIS: CONFIGURANDO E GERENCIANDO O


7.6 USANDO O C3P0 COMO POOL PARA APLICAÇÕES SEM DATASOURCE 83
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

POOL COM O C3P0


1. ATENÇÃO: Para testar o C3P0 vamos usar o projeto fj25-financas (standalone). Aproveite
então e dê um Stop no seu servidor de aplicações Wildfly pelo Eclipse.

2. Vamos testar a abertura de conexão ilimitada pelo Hibernate. Crie uma classe
TestaAberturaConexoes e gere o método main .

No método main faça um loop de 1 a 20 e em cada iteração abra um EntityManager :

for(int i=0; i<20; i++) {


EntityManager manager = new JPAUtil().getEntityManager();
manager.getTransaction().begin();
System.out.println("Criado EntityManager número " + i);
}

Antes de fechar o método main , mas depois do loop, deixe a Thread dormir. Não esqueça de deixar
o Eclipse tratar a Exception com throws InterruptedException :
// Para que o thread durma por 30s
Thread.sleep(30 * 1000);

Execute o método main .

Depois verifique as conexões abertas no MySQL (dentro do intervalo de 30s), para isso conecte-se
com o banco de dados e visualize as conexões abertas:

mysql -u root fj25


show processlist;

(opcional) Teste também o método main sem a abertura da transação.

3. Vamos mudar o comportamento do Hibernate e registrar um pool de conexões. Adicione as linhas


de configuração do C3P0 ao persistence.xml :
<properties>
<!-- Outras configurações (MySQL e Hibernate) -->

<property name="hibernate.c3p0.min_size" value="5" />


<property name="hibernate.c3p0.max_size" value="15" />
<property name="hibernate.c3p0.timeout" value="180" />
<property name="hibernate.c3p0.idle_test_period" value="100" />
</properties>

4. Copie os JARs do C3P0 que estão na pasta do Desktop Caelum/25/c3p0 para a pasta lib do seu
projeto ( ~/workspace/fj25-financas/lib ). Não esqueça de dar um Refresh no projeto, quando
voltar ao Eclipse, e de adicionar os novos JARs ao Build Path da aplicação (botão direito nos
JARs , Build Path -> Add to Build Path )

84 7.7 EXERCÍCIOS OPCIONAIS: CONFIGURANDO E GERENCIANDO O POOL COM O C3P0


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

5. Execute a classe TestaAberturaConexoes novamente. A execução deve ficar parada na iteração 15


por conta da limitação que fizemos na configuração acima.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

7.7 EXERCÍCIOS OPCIONAIS: CONFIGURANDO E GERENCIANDO O POOL COM O C3P0 85


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 8

GERENCIAMENTO DA JPA COM EJB LITE


DENTRO DE UMA APLICAÇÃO WEB

"A língua é um sabre que pode trespassar um corpo" -- Yonsan

Ao término desse capítulo, você será capaz de:

Visualizar as operações com o banco de dados através de uma interface Web.


Usar Enterprise Java Bean para gerenciar JPA
Usar a transação JTA com JPA e EJB

8.1 A IDEIA DO PROJETO WEB


A partir desse momento, vamos deixar de utilizar classes simples com métodos main para realizar
os testes de nossas queries. Em seu lugar, utilizaremos uma aplicação web que possuirá interfaces nas
quais poderemos testar as pesquisas que desenvolvermos em nossos DAOs de uma maneira bem mais
útil e real.

O projeto web utilizará o JSF 2 e já possuirá telas iniciais para que precisemos apenas trabalhar com
os DAOs, entidades e queries, de tal maneira que não seja necessário conhecimento das tags do JSF nem
dos Managed Bean s. O JSF 2 em si é profundamente abordado no curso FJ-26, juntamente com CDI.

O projeto web que utilizaremos será composto por telas onde é possível fazer o CRUD de Conta e
Movimentacao . Além disso, possui uma tela com um menu principal onde temos disponível algumas
opções de relatórios que vamos desenvolver conforme a evolução no treinamento.

86 8 GERENCIAMENTO DA JPA COM EJB LITE DENTRO DE UMA APLICAÇÃO WEB


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

8.2 EXERCÍCIO: COLOCANDO O PROJETO WEB NO AR


1. Vamos configurar o projeto web para visualizarmos os resultados dos DAOs através de uma
interface mais elegante do que o console.

No Eclipse, vá ao Menu File -> New -> Project

Escolha a opção: Web -> Dynamic Web Project

Clique na opção: Next

Em Project Name defina o nome do projeto como fj25-financas-web:

8.2 EXERCÍCIO: COLOCANDO O PROJETO WEB NO AR 87


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Clique em Finish. O novo projeto deve ser criado na sua workspace.

Não confirme a mudança de perspectiva.

2. Agora vamos importar um projeto previamente criado, contendo, além das classes que já foram
escritas durante o curso, as classes e páginas relativas ao JSF para que possamos visualizar os
resultados das nossas consultas numa interface web. Para isso siga os seguintes passos:

Clique com o botão direito em cima do nome do projeto, no caso fj25-financas-web e escolha a
opção: Import

88 8.2 EXERCÍCIO: COLOCANDO O PROJETO WEB NO AR


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Escolha a opção: General -> File System e clique na opção: Next

Clique na opção: Browse (From directory)

No campo de texto para a busca, digite /caelum/cursos/25/fj25-financas-web

No diálogo, marque o checkbox da pasta fj25-financas-web

8.2 EXERCÍCIO: COLOCANDO O PROJETO WEB NO AR 89


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Clique em Finish.

Se aparecer um questionamento sobre a sobrescrita dos arquivos, selecione Yes To All

3. Por fim, precisamos associar o projeto que importamos ao WildFly .

Para isso, vá até a aba Servers e clique com o botão direito sobre o WildFly . Em seguida
escolha a opção Add and Remove... :

Associe o projeto ao servidor e clique em Finish:

90 8.2 EXERCÍCIO: COLOCANDO O PROJETO WEB NO AR


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Verifique se o projeto foi "Deployado" para o servidor:

4. Reinicie o servidor e acesse a URL http://localhost:8080/fj25-financas-web

Você deverá ver a seguinte tela:

8.2 EXERCÍCIO: COLOCANDO O PROJETO WEB NO AR 91


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

5. Navegue pelas telas para se acostumar com a aplicação. Note que muitos recursos ainda não vão
funcionar, pois não estamos usando os DAOs e nem o banco de dados que configuramos nos
passos anteriores.

8.3 INTEGRANDO JPA E O DATASOURCE


Vimos no capítulo anterior que o WildFly disponibiliza um datasource, que nada mais é do que um
provedor de conexões para a nossa aplicação. Poderíamos acessar o datasource diretamente e pedir uma
conexão, porém esse não é o nosso objetivo, já que estamos usando a JPA que abstrai toda a API JDBC e
acesso à conexão.

No entanto, a JPA ainda não sabe onde se encontra o datasource. Para isso, é necessário configurar o
endereço dele no persistence.xml . Repare que usamos a tag jta-data-source abaixo para
referenciar o datasource no contexto JNDI:
<persistence>
<persistence-unit name="financas" transaction-type="JTA">

<jta-data-source>java:/fj25DS</jta-data-source>

<!-- outras configurações omitidas -->

</persistence-unit>
</persistence>

A separação da configuração da conexão em um datasource torna o persistence.xml ainda mais


simples.

8.4 GERENCIAMENTO DA JPA DENTRO DO WILDFLY


Já sabemos que para inicializar o JPA é preciso criar uma EntityManagerFactory que por sua vez é

92 8.3 INTEGRANDO JPA E O DATASOURCE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

responsável pela criação de EntityManager s. Como usamos um datasource, seria preciso buscar a
EntityManagerFactory no JNDI antes de subir a JPA. Dentro de um servidor de aplicação isso não é
necessário.

O servidor já vem com um provider de JPA pré-configurado. No caso do WildFly, o provedor é o


Hibernate. Todos os JARs estão dentro do servidor e a nossa aplicação não terá nenhuma biblioteca no
classpath.

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

8.5 INTRODUÇÃO AOS ENTERPRISE JAVA BEANS


Mas as vantagens não param por aí: todo o trabalho de abrir, fechar e referenciar EntityManager s
pode ser delegado ao container do servidor. Esse container aplica o princípio do inversão de controle
(IoC) e ajuda inicializando a JPA e injetando um EntityManager quando necessário. Esta é a idéia dos
Enterprise Java Beans (EJB), que são objetos administrados pelo EJB container e que tem
automaticamente acesso à persistência com a JPA.

Os EJBs representam uma parte fundamental da especificação Java EE e fornecem serviços


importantes, como a transação, segurança, clusterização e, claro, a persistência com a JPA.

CONTEXTO JNDI

Em geral, cada serviço registrado no JNDI está disponível para as aplicações dentro do WildFly.
Podemos enxergar o JNDI como um registro para cadastrar qualquer objeto. Esse registro é
organizado em forma de árvore onde os nós tem um objeto associado.

É possível cadastrar e acessar o contexto JNDI por uma API, mas normalmente isso não é
preciso e o EJB container fará esse trabalho seguindo o modelo da inversão de controle.

8.6 INJEÇÃO COM @PERSISTENCECONTEXT

8.5 INTRODUÇÃO AOS ENTERPRISE JAVA BEANS 93


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Usar JPA dentro do servidor é muito simples uma vez que temos um DataSource configurado.
Basta adicionar no arquivo persistence.xml a tag vista na seção anterior e criar uma classe que recebe
o EntityManager . Essa classe será o nosso DAO - nela injetaremos o EntityManager pela anotação
@PersistenceContext .

public class ContaDao {

@PersistenceContext
private EntityManager manager;

public void adiciona(Conta conta){


this.manager.persist(conta);
}
}

8.7 TIPOS DE EJBS


Para a injeção realmente funcionar é preciso que a classe DAO se torne um EJB. Só assim o EJB
container fará o trabalho. Para entender um pouco melhor, há três tipos de EJBs: Session Beans, Entity
Beans e Message Driven Beans.

Os Entity Beans são as nossas entidades, por exemplo a classe Conta anotada com @Entity . Os
Message Driven Beans são relacionados com a especificação JMS, receptores de mensagens, vistos em
detalhes no treinamento FJ-36.

No nosso caso, precisamos dos Session Beans - o EJB mais utilizado do mercado. Um Session Bean
nada mais é do que um objeto administrado pelo EJB Container e que ganha vários serviços, como
transação ou injeção de recursos (como já discutimos).

A configuração do Session Bean é muito simples e sem nenhuma burocracia. Basta usar a anotação
@Stateless em cima da classe para transformar a classe ContaDao em um Session Bean Stateless.

@Stateless
public class ContaDao {

@PersistenceContext
private EntityManager manager;

public void adiciona(Conta conta){


this.manager.persist(conta);
}

Com essa simples configuração, o EJB Container vai cuidar de todo o ciclo de vida do Session Bean e
injetará o EntityManager quando for necessário.

94 8.7 TIPOS DE EJBS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

OUTROS TIPOS DE SESSION BEANS

Vimos o tipo mais importante dos EJBs: o Session Bean Stateless (SBSL). Além dele, há mais 2
outros tipos de Session Beans: Stateful e Singleton.

Segue uma breve explicação de cada um:

@Stateless - um Session Bean que é compartilhado entre clientes.


@Singleton - um Session Bean onde é garantido que existe apenas uma instância no
container.
@Stateful - um Session Bean que é único para cada cliente.

No próximo capítulo veremos mais sobre os Session Bean s.

EJB - SERVIÇOS DO CONTAINER

Estamos vendo apenas uma parte do que EJBs são capazes e oferecem para o desenvolvedor. Há
vários outros serviços que um Enterprise Java Beans recebe de graça. Segue uma lista dos serviços
mais importantes:

Remotabilidade com RMI


Web Services
Transação com JTA
Segurança com JAAS
Interceptadores
Injeção de dependências
Callbacks de ciclo da vida
Agendamento e TimerService
Integração com JPA
Integração com JMS
Acesso sincronizado (Thread safety)
Pool de objetos
Tratamento de exceções

No treinamento FJ-36 é visto em detalhe os tópicos de integração entre sistemas com JMS e
Web Services (SOAP e REST).

8.7 TIPOS DE EJBS 95


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

8.8 GERENCIAMENTO DE TRANSAÇÃO: MODO DECLARATIVO


O gerenciamento de transação é sem dúvida algo que não pode faltar dentro da especificação Java
EE. O Java EE atende este aspecto pela especificação JTA (Java Transaction API) e os EJBs
automaticamente suportam este tipo de transação.

Quando trabalhamos com JPA sem servidor de aplicação, somos responsáveis pela abertura (begin),
consolidação (commit) e cancelamento (rollback) de transações. Existe o objeto EntityTransaction da
JPA, que permite a demarcação programática de transações - início e fim. Porém, dentro do servidor de
aplicação isso não é possível:

manager.getTransaction().begin(); // impossível dentro do AS


manager.persist(conta);
manager.getTransaction().commit(); // impossível dentro do AS

Como a transação é do tipo JTA, não podemos utilizar uma transação gerenciada pelo JPA. O EJB
Container vai automaticamente abrir e fechar a transação sem o desenvolvedor se preocupar com isso,
novamente aplicando o conceito de inversão de controle. Para salvar uma conta dentro de um Session
Bean não precisamos alterar em nada o código:
@Stateless
public class ContaDao {

@PersistenceContext
private EntityManager manager;

public void adiciona(Conta conta){


this.manager.persist(conta); // já tem uma TX aberta
}
}

Não configuramos nada sobre o gerenciamento de transações e, mesmo assim, a aplicação já


funciona e os dados são inseridos corretamente. Isso foi possível pois o EJB Container assume padrões
referentes à configuração e gerenciamento de transações. Essa técnica é conhecida como Conventions

96 8.8 GERENCIAMENTO DE TRANSAÇÃO: MODO DECLARATIVO


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

over Configuration.

O EJB Container fica com a responsibilidade de iniciar, consolidar (fazer commit) ou desfazer a
transação (rollback). O programador não pode, e nem deve, usar os métodos de controle transacional
nessa estratégia. Essa estratégia é conhecida como Container Managed Transaction - CMT.

No modo CMT, o padrão é executar todos os métodos do Session Bean dentro de transação. O EJB
Container faz uma verificação toda vez que um método de negócio é invocado para saber se uma
transação já está aberta ou não. Se não houver transação aberta, o EJB Container abre uma nova antes de
começar a processar o método. A transação é fechada no final do método em que ela foi aberta. Em
outras palavras, se já existe uma transação aberta use-a, caso contrário, inicie uma nova. Essa estratégia é
conhecida como REQUIRED .

Além do estilo REQUIRED , há outras formas que veremos no capítulo de transação.

8.9 INTEGRANDO JSF COM EJB ATRAVÉS DE CDI


Vamos utilizar a partir de agora os DAOs que desenvolvemos para que a nossa aplicação web possa
persistir as informações em um banco de dados. Até o momento, a aplicação possui lógicas que não
fazem interação nenhuma com o banco de dados, mas todas as telas já estão prontas para uso. No
decorrer do curso, alteraremos a lógica para fazer a integração com o banco de dados, em vez de simular
o retorno dos dados.

Estamos usando a JPA na camada de persistência e o EJB auxilia no gerenciamento da transação.


Como camada de visualização (view), vamos utilizar o JSF nas páginas:

As páginas do sistema são definidas por arquivos .xhtml que declaram os componentes JSF. Segue
um pequeno trecho do arquivo contas.xhtml :
<h:form>

<h:outputText value="Titular"/>
<h:inputText value="#{contasBean.conta.titular}"/>

8.9 INTEGRANDO JSF COM EJB ATRAVÉS DE CDI 97


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

<!-- outros inputs omitidos -->

<h:commandButton value="Gravar" action="#{contasBean.grava}"/>


</h:form>

Nele definimos um formulário, onde ficam os componentes de input (nesse exemplo o titular da
conta) e, no final, há um botão para confirmar o cadastro da conta. Repare que usamos a expression
language para associar o componente input ou botão com uma classe Java, por exemplo no atributo
action: #{contasBean.grava} .

A expressão procura uma classe ContasBean , que já está preparada, dentro do pacote
br.com.caelum.financas.mb . Dentro do pacote já há várias outras classess. São essas classes que
recebem os dados da a interface gráfica, devolvendo-os quando necesário. Nas classes encontram-se os
métodos que serão chamados quando apertarmos um botão. Por exemplo, encontraremos o método
grava na classe ContaBean com o seguinte código:

@Named
@ViewScoped
public class ContasBean {

public void grava() {


System.out.println("Fazendo a gravação da conta");
}
}

Como podemos observar, o código acima não faz nada. Apenas imprime uma mensagem no console
indicando que a operação foi feita. Precisamos alterar esses dados para utilizar os DAOs, lembrando que
os DAOs são objetos administrados pelo container EJB. Queremos usar o Session Bean em uma classe
relacionada a JSF. O problema é que precisamos integrar o container EJB com o container JSF.

Para tal, o Java EE oferece uma solução elegante e flexível através da especificação CDI (Context e
Dependency Injection). CDI preocupa-se puramente com o gerenciamento de objetos e a injeção de
dependências, até bem parecido com o EJB. A diferença é que o CDI, por ser mais leve, não implementa
por padrão serviços como transação ou segurança, deixando isso para o EJB Container.

Vamos então utilizar CDI para integrar JSF com o EJB:

98 8.9 INTEGRANDO JSF COM EJB ATRAVÉS DE CDI


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

No código acima da classe ContasBean já usamos um pouco do CDI. Com a anotação @Named ,
podemos referenciar a classe com a Expression Language no arquivo xhtml . A anotação @ViewScoped
informa ao CDI que um objeto do tipo ContasBean deve ser instanciado e preservado enquanto a tela
(view) estiver ativa. Isso é feito guardando temporariamente o objeto na sessão do usuário e
automaticamente removendo-o quando o usuário sair da tela atual:
@Named
@ViewScoped
public class ContasBean {
// demais códigos omitidos
}

Como estamos dentro de um servidor de aplicação, o CDI também gerencia o ContasBean , se


integrando com o JSF. Podemos então pedir para ele injetar o nosso DAO, ou seja, o CDI vai pedir ao
container EJB uma referência do Session Bean e vai passar essa referência para o ContasBean . Repare
como fica simples o uso de um EJB com o CDI:
@Named
@ViewScoped
public class ContasBean {

@Inject
private ContaDao contaDao;

// use o contaDao
}

O único passo que nos falta é invocar o método adiciona do ContaDao . Para isso, precisamos
passar para o método uma Conta , que será populada pelo JSF com os dados que virão da tela. Para isso,
basta termos o atributo Conta na classe ContaBean e seu respectivo getter.
@Named
@ViewScoped
public class ContasBean {

@Inject
private ContaDao contaDao;

private Conta conta = new Conta(); // implementar o getter

public void grava() {


System.out.println("Fazendo a gravação da conta");
this.contaDao.adiciona(conta);
}
}

8.10 EXERCÍCIOS: INTEGRANDO OS DAOS AO PROJETO WEB


Vamos começar a utilizar o DAO de contas, que já havíamos feito, junto com o sistema Web que
acabamos de importar. Embora estejamos usando JSF2 para isso, o conhecimento dessa tecnologia não é
pré-requisito. Vamos explorar aspectos genéricos da integração da JPA/Hibernate com Web.

8.10 EXERCÍCIOS: INTEGRANDO OS DAOS AO PROJETO WEB 99


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

1. O primeiro passo é transformar os DAOs que escrevemos anteriormente em EJB Session Bean para
que o EntityManager e a transação sejam gerenciados pelo container.

Abra a classe ContaDao . Em cima da classe adicione a anotação @Stateless :

@Stateless
public class ContaDao {
// código omitido
}

O segundo passo é injetar o EntityManager . Use a anotação @PersistenceContext em cima


do atributo EntityManager :
@PersistenceContext
private EntityManager manager;

O arquivo persistence.xml já vem pré-configurado para ser usado com nosso datasource. Abra
uma vez o arquivo e verifique a configuração pelo elemento jta-data-source .

2. A classe ContasBean do JSF é usada por trás da tela de adição e listagem de novas Conta s. Por
enquanto, está em branco.

Use o ContaDao que preparamos para adicionar o atributo conta no banco de dados. Isso deve
ser feito no método grava() da classe ContasBean , que está no pacote
br.com.caelum.financas.mb , que deve, ainda, atualizar a lista de contas após a adição.

Mas, para o método grava() funcionar, é preciso pedir ao CDI para injetar o ContaDao que
será usado no ContasBean . Declare então um atributo de instância contaDao e anote-o com
@Inject :

@Named
@ViewScoped
public class ContasBean {

@Inject
private ContaDao contaDao;

// demais atributos e métodos omitidos

Agora veja um esboço da implementação do método grava() :


public void grava() {
contaDao.adiciona(conta);
this.contas = contaDao.lista();
limpaFormularioDoJSF();
}

Reinicie o servidor e acesse a aplicação pela URL: http://localhost:8080/fj25-financas-web

Acesse o link Cadastro de contas e cadastre novas contas. Repare no console do servidor se o SQL
de INSERT foi impresso.

100 8.10 EXERCÍCIOS: INTEGRANDO OS DAOS AO PROJETO WEB


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

E se você tentar alterar uma conta? O que acontece? Em breve vamos resolver essa questão.

3. Agora que conseguimos gravar novas contas, vamos fazer com que a listagem das contas já
cadastradas apareça, atualizada, logo que entramos na página. Para isso, vamos alterar o método
getContas da ContasBean . Use o ContaDao para obter a listagem de contas e grave esse
resultado no atributo this.contas da classe.

É importante verificar se o atributo já não foi preenchido, para evitar executar uma nova query
desnecessariamente. Um esboço da implementação:
public List<Conta> getContas() {
if (this.contas == null) {
this.contas = contaDao.lista();
}
return this.contas;
}

Reinicie o servidor e acesse o cadastro de contas. A listagem é exibida na própria página.

4. Permitiremos a exclusão das contas. Use novamente o ContaDao para a remoção através do método
remove . Um detalhe importante é que o JSF nos dará apenas o ID da conta a ser removida. Antes
de remover, precisamos recuperar o objeto completo através do find() no método remover do
DAO. E, após a remoção, precisamos atualizar a lista de contas.

Implemente o método remove() do ContasBean fazendo exatamente isso:


public void remove() {
contaDao.remove(this.conta);
this.contas = contaDao.lista();

limpaFormularioDoJSF();
}

Reinicie o servidor, acesse novamente o cadastro e teste a remoção.

Atenção: A remoção só funcionará caso não tenha nenhuma movimentação associada.

5. Faça o mesmo processo para as movimentações. Primeiro transforme a classe MovimentacaoDao


em um EJB Session Bean. Depois implemente na classe MovimentacoesBean os métodos grava() ,
getMovimentacoes() e remove() .

Um detalhe importante sobre o cadastro de uma Movimentacao é que isso envolve um


relacionamento com a Conta associada. Como fazer isso? Nossa tela JSF já nos disponibiliza a
ID da Conta a ser relacionada através de um atributo contaId dentro da classe
MovimentacoesBean .
Busque a Conta dessa ID usando o ContaDao e relacione à
movimentação sendo inserida. Um esboço do método grava() para as movimentações:
public void grava() {
Conta contaRelacionada = contaDao.busca(contaId);
movimentacao.setConta(contaRelacionada);

8.10 EXERCÍCIOS: INTEGRANDO OS DAOS AO PROJETO WEB 101


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

movimentacaoDao.adiciona(movimentacao);
}

Implemente o restante do método grava() e faça também os métodos getMovimentacoes() e


remove() .

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

8.11 EXERCÍCIOS: ALTERAÇÃO DA CONTA


1. Precisamos permitir a alteração das contas, que até o momento não está funcionando. Utilizaremos o
método grava() do ContasBean para fazer essa alteração. Além disso, precisamos criar o método
altera() no DAO.

Crie o método altera() na classe ContaDao chamando o método merge do


EntityManager .

Por fim, precisamos alterar o método grava() da classe ContasBean para fazer a alteração da
Conta caso o ID seja informado. Para isso, vamos fazer um if antes de adicionar a conta no
DAO:
if (this.conta.getId() == null) {
contaDao.adiciona(conta);
} else {
contaDao.altera(conta);
}

Reinicie o servidor e veja que a alteração da Conta agora funciona.

102 8.11 EXERCÍCIOS: ALTERAÇÃO DA CONTA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 9

EJB 3.1 - CICLO DE VIDA DE SESSION


BEANS

"Vencer a si próprio é a maior da vitórias." -- Platão

9.1 EM QUE MOMENTO ACONTECE A INJEÇÃO?


No capítulo anterior já usamos os EJBs Session Beans para os nossos DAOs. Sendo um Session Bean,
cada DAO é administrado pelo EJB Container e ganha uma série de funcionalidades. Nesse capítulo
entraremos com mais detalhe no ciclo de vida desses objetos.

Para lembrar, ao definir um Session Bean Stateless, usamos a anotação @Stateless :


@Stateless
public class ContaDao {

Como a JPA já vem bem integrada com os Session Beans, usamos a anotação
@PersistenceContext para receber um EntityManager :

@Stateless
public class ContaDao {

@PersistenceContext
private EntityManager manager;

Uma pergunta que poderia surgir é: Em que exato momento acontece a injeção do
EntityManager ?

9 EJB 3.1 - CICLO DE VIDA DE SESSION BEANS 103


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

9.2 CICLO DE VIDA DE UM STATELESS SESSION BEAN


O container determina quando é necessário criar instâncias de um Stateless Session Bean. O cliente
que usa o bean não tem influência direta na política de construção desses objetos. A criação de uma
instância se resume em três passos:

O container invoca o método newInstance() .


As dependências da instância são injetadas pelo container.
O método anotado com @PostConstruct é invocado se ele existir.

Ou seja, para responder a pergunta inicial, o EntityManager é injetado após da criação do Session
Bean (item 2).

O método de pós-construção é opcional, podemos usar quando acharmos necessário. Basta colocar
um método anotado com @PostConstruct :

@Stateless
public class ContaDao {

@PersistenceContext
private EntityManager manager;

@PostConstruct
void aposInjecao() {
//EntityManager já está disponível
System.out.println("método chamado pelo container na criação");
}

É importante saber que o método é automaticamente chamado pelo container na inicialização do


Session Bean. Esses tipos de métodos ligados ao ciclo de vida também são chamados de callback.

Assim como existe um callback de pós-construção, também existe um de pré-destruição, indicado


pela anotação @PreDestroy . Novamente, podemos colocar este método quando acharmos necessário:

104 9.2 CICLO DE VIDA DE UM STATELESS SESSION BEAN


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

@PreDestroy
void preDestroi() {
System.out.println("método chamdo antes o bean morrer")
}

No entanto a questão é: Quando um bean morre?

CLIENTE DE SESSION BEAN

Se um cliente está na mesma JVM do Session Bean ele é denominado cliente local. Eis uma lista
de possíveis tipos de clientes locais:

Um outro Session Bean


Uma Servlet
Um Managed Bean
Uma instância de uma classe comum que está na mesma JVM do Session Bean.

9.3 POOL DE OBJETOS


As instâncias de um Stateless Session Bean, depois de criadas, "vivem" em um pool esperando
chamadas dos clientes. Importante: se o sistema está sobrecarregado, ou seja, há muitos clientes pedindo
pelo DAO, o container pode criar mais instâncias para aguentar a carga. A melhor parte é que isso é feito
automaticamente para você.

Cada vez que o cliente chama um método, o container seleciona alguma instância do pool para
atender ao chamado. No final do processamento, a instância é devolvida ao pool para aguardar mais
clientes.

Se o número de chamadas diminui, as instâncias que estão no pool ficam mais tempo ociosas. Ao
perceber essa situação, o container pode eliminar algumas instâncias para liberar memória. Quando uma
instância é eliminada, o container invoca o método de callback anotado com @PreDestroy .

9.3 POOL DE OBJETOS 105


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

9.4 ATRIBUTOS DE INSTÂNCIA DE UM STATELESS SESSION BEAN


Se você não tomar o devido cuidado com o ciclo de vida de um Session Bean Stateless, poderá
cometer um erro grave. Suponha que você definiu um atributo de instância no seu bean e este atributo
não é adimistrado pelo container como o nosso EntityManager . Como as instâncias podem ser
compartilhadas (não simultaneamente) por diversos clientes, o atributo que você criou pode ser lido
e/ou modificado por mais de um cliente.

Portanto, você precisa ter muito critério para colocar atributos de instância em um Session Bean
Stateless. Sempre tenha em mente que esses atributos podem ser compartilhados (não simultaneamente)
por mais de um cliente. Por exemplo, um atributo que representa uma configuração não tem problema
ser compartilhado. Já um atributo que guarda o carrinho de compras de um usuário pode gerar um
problema grave.

Apesar do Session Bean Stateless ser um objeto compartilhado, ao mesmo tempo apenas um cliente
pode fazer a chamada. O acesso ao bean é sincronizado (objetos são threadsafe). Isso significa que, se
temos um Pool com máximo de 5 instâncias, apenas 5 clientes podem ser atendidos ao mesmo tempo. Se
todos os clientes demoram na liberação, outros clientes ficam esperando. Por isso, o Pool pode oferecer
um timeout de acesso para configurar tempo limite de uso.

106 9.4 ATRIBUTOS DE INSTÂNCIA DE UM STATELESS SESSION BEAN


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

9.5 CONFIGURANDO O POOL DE INSTÂNCIAS DO WILDFLY


Você pode controlar o tamanho do pool de instâncias de um Session Bean Stateless no WildFly. Um
detalhe importante é que essa configuração é específica do WildFly e é feita com a anotação @Pool
junto a algumas configurações extras no arquivo standalone.xml contido no diretório do WildFly nas
subpastas standalone/configuration .

No exemplo, vamos configurar o pool para disponibilizar no máximo um 5 instâncias. Inicialmente,


faremos algumas alterações no arquvio standalone-ha.xml . Dentro da tag <subsystem
xmlns="urn:jboss:domain:ejb3:4.0"> , encontramos a subtag <pools> e adicionamos as
informações referentes ao pool que desejamos criar, dentro da tag <bean-instance-pools> :

<pools>
<bean-instance-pools>
<strict-max-pool name="slsb-strict-max-pool" max-pool-size="5"
instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
</bean-instance-pools>
</pools>

Damos um nome ao nosso pool (no caso, o WildFly já vem com esse pool de Beans por padrão e o
nomeia como slsb-strict-max-pool) e em seguida configuramos seus parâmetros, tais como max-pool-
size. Nesse exemplo configuramos o pool com máximo de 5 objetos. Também definimos um timeout de
5 minutos ( instance-acquisition-timeout , instance-acquisition-timeout-unit ), ou seja
nenhuma chamada pode demorar mais de 5 minutos.

A seguir, anotamos a classe do EJB com a anotação específica do WildFly @Pool e informamos qual
pool previamente configurado no arquivo standalone-ha.xml deve ser utilizado (passamos o nome
do pool como parâmetro da anotação). Para que possamos usá-la, o JAR jboss-ejb3-ext-api-
2.0.0.jar deve ser adicionado ao classpath de sua aplicação.

9.5 CONFIGURANDO O POOL DE INSTÂNCIAS DO WILDFLY 107


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Note que o import da anotação deve ser do pacote org.jboss.ejb3.annotation.Pool.

@Stateless
@Pool("slsb-strict-max-pool")
public class ContaDao{

Como o slsb-strict-max-pool já é o padrão do Wildfly, não é preciso definir o nome através da


anotação. No entanto, podemos criar um novo pool e associar um pool específico pela anotação.

9.6 UM BEAN NA APLICAÇÃO: SINGLETON


Vimos a configuração do WildFly no XML e pela anotação @Pool para definir a quantidade de
objetos no pool. O pool faz sentido quando queremos melhorar a escalabilidade da aplicação pois
conseguimos atender várias chamadas ao mesmo tempo.

No entanto há situações onde o uso de um pool parece desnecessário. Por exemplo, imagine que
queremos oferecer algumas configurações para toda a aplicação, algo como:
@Stateless
public class ConfiguracoesDaAplicacao{

public String getNomeServidor() {


return ...
}

public String getNomeContext() {


return ...
}
}

Para este tipo de Session Bean não faz sentido usar um pool de objetos, é suficiente ter apenas uma
instância em memória. Nas versões anteriores do EJB 3.1 era preciso configurar o pool para garantir
apenas uma instância. Como vimos, a configuração é totalmente específica de servidor e perdemos assim
a portabilidade da aplicação.

Para resolver isso foram criados o Singleton Session Bean, que possuem as mesmas características
do Session Bean Stateless mas garantem uma única instância por aplicação.

Basta usar a anotação @Singleton no lugar de @Stateless :


@Singleton
public class ConfiguracoesDaAplicacao {

9.7 UM BEAN POR CLIENTE: STATEFUL


Há um terceiro tipo de EJB Session Bean: o Session Bean Stateful. A ideia desses beans é bem
parecida com a da HttpSession. Nos Session Bean Stateful também existe para cada cliente um objeto
dedicado. Então podemos guardar nele dados que são do cliente apenas e que queremos reutilizar entre
chamadas diferentes. A utilização mais usada no mercado é um carrinho de compras, por exemplo:

108 9.6 UM BEAN NA APLICAÇÃO: SINGLETON


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

@Stateful
public class Carrinho{

private List<Produto> compras;

//metodos para adicionar e remover um produto, getTotal etc


}

Apesar do Session Bean Stateful estar presente desde da primeira versão de EJB, eles nunca fizeram
muito sucesso. Normalmente usa-se a sessão HTTP para guardar dados do cliente, ou, caso queiramos
uma solução persistente, os dados são guardados no banco de dados.

SESSION E STATELESS?

A palavra session passa a ideia de que o estado da conversação com o cliente é mantido. A
palavra stateless passa a ideia oposta. Não se confuda! Stateless Session Bean não mantém estado
conversacional.

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

9.8 QUAL TIPO DE SESSION BEAN DEVO USAR?


Qual devemos usar? Isso vai depender do caso. É importante perceber que manter um Stateless
Session Bean é algo muito mais barato e simples para o servidor, do que manter um Stateful Session
Bean.

Algumas características indicam que seu bean deveria ser Stateful, como possuir atributos
relacionados a um cliente/usuário e possuir mais de um método.

9.9 INTERFACES LOCAIS E REMOTAS DE SESSION BEAN


Na versão 3.0 do EJB era necessário definir uma interface para cada Session Bean. A partir da versão
3.1 o uso foi simplificado e as interfaces se tornaram opcionais.

9.8 QUAL TIPO DE SESSION BEAN DEVO USAR? 109


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Segue um exemplo com o uso de interface:

public interface ContaDao {

void adiciona(Conta conta);


//outros métodos
}

E através da anotação @Local será definida a interface do EJB. Local significa que queremos usar o
Session Bean apenas dentro do mesmo servidor:
@Stateless
@Local(ContaDao.class)
public class ContaDaoBean implements ContaDao {
//...

É possível liberar os Session Bean e usá-los remotamente através de outras aplicações Java. Nesse
caso faremos uma chamada remota e por isso devemos declarar a interface como remoto:
@Stateless
@Remote(ContaDao.class)
public class ContaDaoBean implements ContaDao {
//...

Dessa maneira até podemos liberar o EJB Session Bean para ser usado através Web Services (SOAP).
Isso é de um jeito fácil de configurar, basta usar a anotação @WebService :
@WebService
@Stateless
@Remote(ContaDao.class)
public class ContaDaoBean implements ContaDao {
//...

É importante mencionar que a remotabilidade não faz parte do EJB lite. EJB lite está sendo usado
quando instalamos um servidor no Java EE Web Profile. Para realmente usufruir da remotabilidade
devemos usar um servidor de aplicação completo (Full Profile).

No site da Oracle há uma lista dos servidores e a qual profile eles pertencem:

http://www.oracle.com/technetwork/java/javaee/overview/compatibility-jsp-136984.html

9.10 PARA SABER MAIS: LOOKUP PELO JNDI


A infraestrutura de um servidor de aplicação está organizado pelo JNDI. Nesse registro encontramos
os prinipais serviços. Da mesma forma todos os Session Bean ficam automaticamente registrados no
JNDI. O Java EE padronizou o nome no JNDI, os Session Beans, todos os servidores devem seguir o
padrão seguinte:
java:global/nomDaAplicacao/nomeDoModulo/nomeDoEJB

Através desse nome seria possível procurar um Session Bean dentro do contexto do JNDI.
Normalmente isto não é preciso e deve ser evitado, pois a injeção de dependência já faz este papel.

110 9.10 PARA SABER MAIS: LOOKUP PELO JNDI


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

9.11 EXERCÍCIOS: UTILIZANDO SESSION BEANS E POOL DE OBJETOS


1. Do pacote br.com.caelum.financas.service abra a classe Agendador.

Transforme o Agendador em um Session Bean Stateless:

@Stateless
public class Agendador {

2. A classe Agendador já possui um atributo estático ( totalCriado ) para contar as instâncias no


pool de objetos.

Coloque os callback de criação para incrementar o atributo. Adicione também o callback de


destruição:

@PostConstruct
void posConstrucao() {
System.out.println("criando agendador");
totalCriado++;
}

@PreDestroy
void preDestruicao() {
System.out.println("destruindo agendador");
}

Verifique o método executa . Repare que ele faz nada mais do que simular uma execução
demorada. Além disso, imprimimos a quantidade de instâncias criadas em cada chamada.

3. Abra a classe CicloDeVidaBean . Faça com que ela receba injetado a classe Agendador e em seu
método executeAgendador() chame o método executa() :
public class CicloDeVidaBean {

@Inject
private Agendador agendador;

9.11 EXERCÍCIOS: UTILIZANDO SESSION BEANS E POOL DE OBJETOS 111


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public void executeAgendador() {


agendador.executa();
}

4. Observação: Todas as configurações abaixo são específicas do WildFly e não portável entre servidores
de aplicações.

Vamos configurar o pool de instâncias para ter, no máximo, cinco instâncias. Para que haja no
máximo cinco instâncias, devemos alterar o arquivo standalone-ha.xml contido no diretório do
WildFly na pasta standalone/configuration .

Encontre a tag <subsystem xmlns="urn:jboss:domain:ejb3:4.0"> e localize o seguinte trecho


de XML:
<pools>
<bean-instance-pools>
<strict-max-pool name="slsb-strict-max-pool" max-pool-size="20"
instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
<strict-max-pool name="mdb-strict-max-pool" max-pool-size="20"
instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
</bean-instance-pools>
</pools>

No pool chamado slsb-strict-max-pool altere o atributo max-pool-size para 5.

5. Reinicie o WildFly e acesse pelo navegador:

http://localhost:8080/fj25-financas-web/cicloDeVida.xhtml

No primeiro teste chame o Agendador pela interface web. Submeta o formulário uma vez. Depois
verifique a quantidade de instâncias criadas pelo EJB Container no console no Eclipse.

Repita o teste e submeta uma vez o formulário pela interface web. Repare que a quantidade de
instâncias não aumentou.

Agora teste várias chamadas, submeta o formulário várias vezes (mais do que 5 vezes). Depois fique
acompanhando o trabalho no WildFly pelo console.

Quantas instâncias foram criadas no Pool? O que acontece se temos 6 clientes querendo acessar o
Agendador ?

6. Vamos desabilitar o pool de objetos. No arquivo standalone-ha.xml procure a linha:


<stateless>
<bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>
</stateless>

e comente a segunda linha:

<stateless>
<!--<bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>-->

112 9.11 EXERCÍCIOS: UTILIZANDO SESSION BEANS E POOL DE OBJETOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

</stateless>

Reinicie o WildFly e submeta o formulário algumas vezes (mais de duas). Verifique o console. Qual é
a politica de criação do Wildfly agora?

7. Na classe Agendador substitua a anotação @Stateless por @Singleton (do pacote


javax.ejb ):

//@Stateless
@Singleton
public class Agendador {

Republique a aplicação no WildFly. Faça novamente alguns testes pela interface web. Repare que
apenas uma instância é criada.

8. (Opcional) Ao testar e verificar o console podemos perceber algumas exceções ao chamar o


Agendador . O EJB jogou uma javax.ejb.ConcurrentAccessTimeoutException .

O timeout padrão é de 500 milisegundos, um valor muito baixo para nossa execução. Use anotação
@AccessTimeout para aumentar o tempo limite de timeout.

Por exemplo, na classe Agendador use:


@AccessTimeout(unit = TimeUnit.SECONDS, value = 5)

9.11 EXERCÍCIOS: UTILIZANDO SESSION BEANS E POOL DE OBJETOS 113


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 10

AGENDAMENTO DE TAREFAS

"A grandeza não consiste em receber honras, mas em merecê-las." -- Aristóteles

10.1 AGENDAMENTOS PRECISOS COM TIMERSERVICE


Vamos imaginar que seja necessário enviar um email semanal informando o total das
movimentações de cada usuário do sistema. Precisamos de um serviço responsável por fazer o papel do
agendamento e definir o tempo de atualização. Para tal, o EJB oferece o TimerService , que podemos
receber através da injeção de dependências.

Para obter o TimerService , vamos utilizar a anotação @Resource . Implementaremos um método


anotado com @Timeout , que será chamado periodicamente pelo container a medida que o tempo
definido em nosso Timer expira.

A aplicação nem precisa saber como foi criado o TimerService e onde ele está, pois o Java EE
Container injeta essa dependência nos Session Beans.

A primeira ideia é injetar o TimerService para usar ele no construtor e criar um javax.ejb.Timer:

@Singleton
public class Agendador {

@Resource
private TimerService timerService;

public Agendador() {
// Aqui ainda não aconteceu a injeção.
timerService.createTimer(10000L, "timeout"); // ERRO !!
}

@Timeout
public void atualiza(Timer timer) {
//chamado pelo container periodicamente
}
}

O código acima apresenta um erro comum que quase todo mundo que trabalha ou trabalhou com
EJB3 comete uma vez na vida: o TimerService foi utilizado no construtor, só que a injeção é feita
depois que o objeto foi construído, ou seja, depois do construtor.

Portanto, quando o construtor executa, o atributo timerService está null . Para solucionar essa
situação, podemos usar o callback @PostConstruct :

114 10 AGENDAMENTO DE TAREFAS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

@Singleton
public class Agendador {

@Resource
private TimerService timerService;

@PostConstruct
public void posConstrucao(){
timerService.createTimer(10000L, "timeout"); // CERTO !!
}

@Timeout
public void agendado(Timer timer) {
//chamado pelo container periodicamente
}

Nesse caso o método agendado(..) anotado com @Timeout é chamado pelo container 10
segundos após o agendamento.

A String passada como argumento ao createTimer pode ser útil para que o método anotado
com @Timeout saiba qual timer gerou essa invocação. Há também a opção de passarmos mais
configurações como argumento, através de um TimerConfig , como veremos adiante.

Também pode fazer sentido inicializar o agendamento quando a aplicação será carregada. Podemos
aproveitar uma novidade do EJB 3.1 e configurar o bean Agendador com a anotação @Startup:
@Singleton
@Startup
public class Agendador {

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

10.2 AGENDAMENTO EXATO


Outra novidade do EJB 3.1 é o uso de expressões parecidas com o serviço cron , conhecido nos
sistemas operacionais UNIX, para agendar a execução de um método exato ou periódico. Para isso foi
criado uma ScheduleExpression que podemos passar para o TimerService :
ScheduleExpression expression = new ScheduleExpression();
expression.hour("16");
expression.minute("25");
expression.second("30");

10.2 AGENDAMENTO EXATO 115


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

//timer agendado para 16hs:25min:30seg


this.timerService.createCalendarTimer(expression);

Através da classe ScheduleExpression é possível definir horários exatos e intervalos com precisão.
Seguem alguns exemplos:
//toda segundas 9hs da manhã
ScheduleExpression expression = new ScheduleExpression();
expression.dayOfWeek("Mon");
expression.hour("9");

//qualquer hora aos 40min


ScheduleExpression expression = new ScheduleExpression();
expression.hour("*");
expression.minute("40");

//as 9 e 18 horas cada 15 minutos


ScheduleExpression expression = new ScheduleExpression();
expression.hour("9,18");
expression.minute("*/15"); //igual ao "15,30,45"

//entre 2 e 4 horas nos minutos 30,35,40,45,50,55


ScheduleExpression expression = new ScheduleExpression();
expression.hour("2-4");
expression.minute("30/5");

Podemos até configurar os segundos, timezone ou algum dia específico no mês. Mais exemplos se
encontram na documentação da classe ScheduleExpression :
http://download.oracle.com/javaee/7/api/javax/ejb/ScheduleExpression.html

10.3 EXPRESSÕES DECLARATIVAS COM @SCHEDULE


Vimos como usar a classe ScheduleExpression . No EJB 3.1 também entrou uma anotação para
simplificar mais ainda o agendamento. Podemos usar a anotação @Schedule , que possui vários
atributos para definir um agendamento fixo de execução:
@Schedule(hour="18",minute="30") //as 18:30
void agendado() {
System.out.println("metodo agendado pela anotacao @Schedule");
}

Com isso nem é preciso usar a anotação @Timeout . O método anotado com @Schedule é
chamado pelo EJB container no horário definido.

Um Timer é persistido
O container EJB persiste os Timers em um banco de dados e recupera quando o servidor for
reiniciado. Se não desejamos persistir o agendamento, podemos definir isso usando um objeto do tipo
TimerConfig :

ScheduleExpression expression = // codigo omitido

TimerConfig config = new TimerConfig();

116 10.3 EXPRESSÕES DECLARATIVAS COM @SCHEDULE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

config.setPersistent(false);

this.timerService.createCalendarTimer(expression, config)

ou via anotação @Schedule :


@Schedule(hour="18", minute="30", persistent=false) //as 18:30, não persistido

10.4 EXERCÍCIOS: AGENDAMENTO


1. Vamos trabalhar com o TimerService visto neste capítulo.

Na classe Agendador declare um atributo do tipo TimerService , e anote-o com @Resource ,


para recebê-lo por injeção de dependências.
@Resource
private TimerService timerService;

2. Na classe Agendador coloque o método seguinte:


public void agenda(String expressaoMinutos, String expressaoSegundos) {

3. Implemente o método agenda , que nos permite escolher de quanto em quanto tempo o sistema
será acionado, de acordo com seus parâmetros:
ScheduleExpression expression = new ScheduleExpression();
expression.hour("*");
expression.minute(expressaoMinutos);
expression.second(expressaoSegundos);

TimerConfig config = new TimerConfig();


config.setInfo(expression.toString());
config.setPersistent(false);

this.timerService.createCalendarTimer(expression, config);

System.out.println("Agendamento: " + expression);

4. Escreva agora o método que será acionado cada vez que a regra do scheduler for válida. O método
pode ter qualquer nome, pode receber um objeto Timer como argumento e deve ser anotado com
@Timeout .

Cuidado: Timer é do pacote javax.ejb :


@Timeout
public void verificacaoPeriodicaSeHaNovasContas(Timer timer) {
System.out.println(timer.getInfo());
//aqui poderiamos acessar o banco de dados
//com JPA para verificar as contas periodicamente
}

5. Abra a classe AgendadorFormBean e injete o Agendador :


@Inject

10.4 EXERCÍCIOS: AGENDAMENTO 117


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

private Agendador agendador;

No método agendar chame o Agendador , passando os valores do formulário:


public void agendar() {
agendador.agenda(expressaoMinutos, expressaoSegundos);
}

Reinicie a aplicação e acesse http://localhost:8080/fj25-financas-web/agendamento.xhtml

Teste o Timer Service !

6. (opcional) Teste a anotação @Schedule , coloque mais um método na classe Agendador :


@Schedule(hour="*", minute="*/1", second="0", persistent=false)
public void enviaEmailCadaMinutoComInformacoesDasUltimasMovimentacoes() {
System.out.println("enviando email a cada minuto");
}

Para carregar o Agendador na inicialização da aplicação coloque a anotação @Startup em cima da


classe.

Caso você quisesse mandar o email semanal, poderia usar o seguinte código:
@Schedule(hour="9", minute="0", second="0", dayOfWeek="Mon", persistent=false)
public void enviaEmailCadaMinutoComInformacoesDasUltimasMovimentacoes() {
System.out.println("enviando email semanal");
}

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

118 10.4 EXERCÍCIOS: AGENDAMENTO


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 11

TRANSAÇÕES E EXCEÇÕES

"O primeiro problema para todos, homens e mulheres, não é aprender, mas desaprender" -- Gloria Steinem

11.1 CONFIABILIDADE DOS DADOS


As aplicações inevitavelmente estão sujeitas a erros em qualquer momento da execução. Esses erros
poderiam acontecer no meio de um método, por exemplo. Em outras palavras, não há como a aplicação
garantir que toda operação será completada.

Algumas operações são críticas, pois obrigatoriamente elas devem terminar com sucesso ou, se
algum erro ocorrer no meio do caminho, o estado inicial antes da operação começar precisa ser
recuperado.

Daí surge o conceito de transação. Uma transação deve garantir que os passos de uma operação
sejam totalmente executados ou, se um erro ocorrer, os passos já executados sejam "desfeitos".

Vamos exemplificar o poder do serviço de transação que o EJB Container oferece. Suponha que o
cliente fez uma chamada a um Session Bean, digamos que seja o DAO. Digamos que o sistema precisa
alimentar dois bancos de dados diferentes.

O EJB Container garante que todos os passos do processo acima descrito serão executados até o fim
ou desfeitos se algo ocorrer de errado em qualquer ponto do processamento.

Do ponto de vista de uma aplicação Java EE, o gerenciamento de transações pode ficar totalmente

11 TRANSAÇÕES E EXCEÇÕES 119


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

transparente. Porém, se ela quiser fazer essa tarefa manualmente, o servidor oferece mecanismos para
facilitar esse processo.

Portanto, há dois estilos para se trabalhar com transações: programaticamente ou declarativamente.


No primeiro, a aplicação faz o gerenciamento de transações colocando chamadas no meio das regras de
negócio (BMT - Bean Managed Transactions). No segundo, o servidor fica com toda a responsabilidade
(CMT - Container Managed Transactions).

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

11.2 GERENCIAMENTO DE TRANSAÇÃO: MODO PROGRAMÁTICO


(BMT)
O Session Bean que deseja gerenciar manualmente as transações deve utilizar uma anotação para
avisar o EJB Container: a @TransactionManagement .
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class ContaDao {
// ...
}

Nos métodos de negócio, o Dao pode demarcar o código que deve ser executado dentro de uma
transação. Para isso, esse Session Bean precisa "conversar" com o UserTransaction que pode ser
obtido com injeção de dependência.

@Resource
private UserTransaction ut;

Há três métodos fundamentais que podem ser invocados no UserTransaction : begin() ,


commit() e rollback() . Esses métodos lançam diversas checked exceptions. A utilização deles é
extremamente complexa. Veja o código abaixo do adiciona :
public void adiciona(Conta conta) {

try {

120 11.2 GERENCIAMENTO DE TRANSAÇÃO: MODO PROGRAMÁTICO (BMT)


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

this.ut.begin();

this.manager.persist(conta);

this.ut.commit();

} catch (NotSupportedException e) {
throw new EJBException(e);
} catch (SystemException e) {
throw new EJBException(e);
} catch (SecurityException e) {
throw new EJBException(e);
} catch (IllegalStateException e) {
throw new EJBException(e);
} catch (RollbackException e) {
throw new EJBException(e);
} catch (HeuristicMixedException e) {
throw new EJBException(e);
} catch (HeuristicRollbackException e) {
throw new EJBException(e);
} finally {
try {
if (this.ut.getStatus() == Status.STATUS_ACTIVE) {
this.ut.rollback();
}
} catch (IllegalStateException e) {
throw new EJBException(e);
} catch (SecurityException e) {
throw new EJBException(e);
} catch (SystemException e) {
throw new EJBException(e);
}
}

A complexidade dessa API é alta e o seu uso é desaconselhado, a não ser que tenhamos um caso
muito particular. A próxima estratégia será extremamente mais simples.

Transações gerenciadas pelo Session Bean deixam a responsabilidade de controle transacional para o
programador. Ele terá que abrir, consolidar (commit) ou desfazer (rollback).

Elas podem ser úteis caso você precise executar algum código associado ao rollback. Um outro
possível uso é para o caso de você querer uma transação dentro de uma classe que não recebe esse
serviço pronto do container. Um exemplo seria ter uma transação dentro de um servlet da sua aplicação.

11.3 O MODO DECLARATIVO (CMT)


Antes desse capítulo, não configuramos nada sobre o gerenciamento de transações. Mesmo assim, a
aplicação funcionou e os dados foram inseridos ou alterados corretamente. Isso foi possível, pois o EJB
Container assume padrões referentes à configuração e gerenciamento de transações. Essa técnica é
conhecida como Conventions over Configuration. Outro termo comumente empregado para descrever a
técnica é Configuration by Exception (configure só o que for exceção).

11.3 O MODO DECLARATIVO (CMT) 121


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

O EJB Container fica com a responsibilidade de iniciar, consolidar (fazer commit) ou desfazer a
transação (rollback). O programador não pode e nem deve usar os métodos de controle transacional
nessa estratégia. Essa estratégia é conhecida como Container Managed Transaction - CMT.

O padrão é CMT mas se a aplicação quer deixar explícito que deseja utilizar o modo CMT ela pode
anotar a classe do Session Bean com a
@TransactionManagement(TransactionManagementType.CONTAINER) .

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class ContaDao {
// ...
}

Lembre que a anotação @TransactionManagement serve para forçar algum dos modos de controle
transacional e o padrão é CMT.

No modo CMT, o padrão é executar todos os métodos de negócio dentro de uma transação. O JEE
Container faz uma verificação toda vez que um método de negócio é invocado para saber se uma
transação já está aberta ou não. Se não houver transação aberta, o JEE Container abre uma nova antes de
começar a processar o método. A transação é fechada no final do método em que ela foi aberta. Em
outras palavras, se já existe uma transação aberta use-a, caso contrário, inicie uma nova. Essa estratégia é
conhecida como REQUIRED .

Mudando a estratégia de transação


Além do estilo REQUIRED , há outros que são listados abaixo:

MANDATORY : Uma transação deve ser aberta antes do método ser chamado. Caso contrário,
lança uma TransactionRequiredException .

NOT_SUPPORTED : Deve executar sem transação. Se existir alguma aberta, é suspensa


temporariamente até o método acabar.

SUPPORTS : Executa o código com ou sem transação.

NEVER : Só pode executar sem transação. Se existir alguma aberta, uma exception é lançada.

REQUIRED : Abre uma transação nova se não existir. É o modo padrão.

REQUIRES_NEW : Sempre abre uma nova transação. Se existir alguma aberta, é suspensa
temporariamente até o método acabar.

Para configurar outro estilo, um Session Bean pode utilizar a anotação @TransactionAttribute
em cada método.

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void adiciona(Conta conta) {

122 11.3 O MODO DECLARATIVO (CMT)


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

this.manager.persist(conta);
}

Outra possibilidade, é definir um estilo diferente do REQUIRED , que é o padrão para todos os
métodos de um Session Bean. Basta anotar a classe em vez dos métodos.
@Stateless
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class ContaDao {
// ...
}

TRANSAÇÕES NO JPA - ENTITYTRANSACTION

O EntityManager possui um objeto para você trabalhar manualmente com as transações:

EntityTransaction tx = manager.getTransaction();
tx.begin();
// ...
tx.commit();

É ilegal fazer essas chamadas se o EntityManager foi injetado para você, pois aí a transação
estará sendo gerenciada pelo servidor.

No entanto, é possível que um EntityManager se aproveite de uma transação existente


usando o método manager.joinTransaction() .

11.4 TRATAMENTO DE EXCEÇÕES


Todo erro que ocorre na execução é chamado de exception. No EJB, as exceções são classificadas em
duas categorias: de aplicação (Application Exception) ou de sistema (System Exceptions).

As exceções de aplicação representam erros diretamente relacionados à execução de uma regra de


negócio. Por exemplo, o cliente tentou efetuar um saque em uma conta sendo que o saldo dessa conta é
insuficiente para essa operação.

As exceptions de sistema representam erros de infraestrutura. Por exemplo, não foi possível abrir
uma conexão com algum Data Source. Em geral, são erros dos quais a aplicação não tem influência
direta.

O que acontece com a transação se uma exceção ocorrer? A resposta parece óbvia, o servidor deve
fazer um rollback, mas isso não é sempre verdade. Podem existir algumas exceções que não são erros
fatais e podem ser recuperadas, como por exemplo exceções relativas a problemas de validação.

O comportamento do EJB Container quando ocorre uma exception depende fortemente do tipo de
exception que ocorre (System Exception ou Application Exception).

11.4 TRATAMENTO DE EXCEÇÕES 123


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

11.5 SYSTEM EXCEPTIONS


Se ocorrer uma System Exception ou Errors , o EJB Container segue os seguintes passos:

Escreve no log as informações relevantes sobre a System Exception.


Marca a transação corrente para rollback.
Elimina a instância do Bean que sofreu a System Exception.
Caso o Bean que sofreu a System Exception tenha sido invocado por outro Bean, também
elimina a instância desse outro Bean.
Lança EJBTransactionRolledbackException para o cliente.

Por padrão, toda exception do tipo unchecked, ou seja, que herda de RuntimeException é
considerada uma System Exception.

Por exemplo, podemos criar uma própria exceção utilizada nas classes DAO:

public class DaoException extends RuntimeException {}

Ao lançar essa exceção, como se trata de um RuntimeExecption , automaticamente é feito um


rollback.

11.6 APPLICATION EXCEPTIONS


Exceções do tipo checked (herdam de Exception ) são consideradas Application Exceptions e
não causam rollback por padrão, por exemplo:
public class ValorInvalidoException extends Exception {}

Para mudar este comportamento, usa-se a anotação @ApplicationException na classe da sua


exception. Por exemplo, no código abaixo, o ValorInvalidoException causará rollback, mesmo

124 11.5 SYSTEM EXCEPTIONS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

sendo do tipo checked.

@ApplicationException(rollback=true)
public class ValorInvalidoException extends Exception { ... }

Como discutido anteriormente, as exceptions unchecked por padrão são System Exceptions.
Entretanto, você tem a possibilidade de alterar esse padrão com a anotação @ApplicationException
que pode fazer uma unchecked ser Application Exception. Por exemplo:
@ApplicationException(rollback=true)
public class ValorInvalidoException extends RuntimeException {}

Essa unchecked exception causaria o rollback da transação. Uma System Exception teria o mesmo
efeito causando um rollback na transação. Porém, há duas diferenças importantes aqui: a primeira é que,
usando uma Application Exception, o cliente recebe exatamente a exceção ValorInvalidoException e
não a EJBTransactionRolledbackException ; a segunda é que o Session Bean não é eliminado
quando se usa Application Exception o que não acontece quando se usa System Exception. Dessa forma, o
cliente poderia fazer um tratamento dessa exceção e executar o Session Bean novamente.

É possível também fazer a declaração das exceções via XML. Para isso, no arquivo ejb-jar.xml ,
basta adicionar a seguinte configuração:

<application-exception>
<exception-class>fqn.da.minha.Classe</exception-class>
<rollback>true</rollback>
</application-exception>

PARA SABER MAIS: SETROLLBACKONLY()

Para fazer um rollback programaticamente dentro de uma transação gerenciada pelo


servidor, podemos injetar javax.ejb.SessionContext :
@Resource
SessionContext context;

Usando o SessionContext é possível marcar a transação como rollback :


context.setRollbackOnly();

Para saber se a transação já foi marcada, existe o método getRollbackOnly() .

11.7 EXERCÍCIOS: MANIPULANDO A TRANSAÇÃO COM EJB 3


1. Na classe MovimentacaoDao , vamos forçar que uma System Exception seja lançada quando a
movimentação possuir um valor negativo.

Abra a classe MovimentacaoDao e procure o método adiciona . Acrescente no final um if que

11.7 EXERCÍCIOS: MANIPULANDO A TRANSAÇÃO COM EJB 3 125


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

verifca o valor da movimentação:


public void adiciona(Movimentacao movimentacao) {

manager.persist(movimentacao);

if(movimentacao.getValor().compareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException("Movimentacao negativa");
}
}

Como a exceção que estamos forçando é uma unchecked (System Exception), o EJB Container
executará o rollback e a movimentação não será adicionada.

2. Republique a aplicação e acesse pelo navegador:

http://localhost:8080/fj25-financas-web/movimentacoes.xhtml

Tente cadastrar uma movimentação com valor negativo. Ao cadastrar recebemos uma
EJBException com a RuntimeException embrulhada, indicando que algo inesperado aconteceu.

Acesse o MySQL e faça um select na tabela de movimentações para verificar se realmente foi feito
um rollback.

3. Vamos testar as Application Exceptions.

Crie uma exceção própria e anote ela com @ApplicationException :


package br.com.caelum.financas.exception;

@ApplicationException
public class ValorInvalidoException extends RuntimeException {

public ValorInvalidoException(String message) {


super(message);
}

Ela é unchecked mas reconfiguramos o padrão, transformando a exceção em uma Application


Exception. Lembre-se, Application Exceptions não causam rollback por padrão.

4. Na classe MovimentacaoDao , no método adiciona substitua a RuntimeException por


ValorInvalidoException .

Tente novamente o cadastro de uma movimentação inválida! Perceba que agora receberemos uma
ValorInvalidoException e que a movimentação é adicionada no banco, ou seja, não há rollback.

5. Faça com que o rollback seja executado. Na anotação @ApplicationException coloque a opção de
rollback para true .

Tente novamente inserir uma movimentação e verifique o banco.

126 11.7 EXERCÍCIOS: MANIPULANDO A TRANSAÇÃO COM EJB 3


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

11.8 EXERCÍCIO OPCIONAL - @TRANSACTIONATTRIBUTE


1. No session bean ContaDao , adicione no método adiciona() a anotação
@TransactionAttribute(TransactionAttributeType.MANDATORY) .

@TransactionAttribute(TransactionAttributeType.MANDATORY)
public void adiciona(Conta conta) {
this.manager.persist(conta);
}

2. Republique a aplicação e acesse pelo navegador:

http://localhost:8080/fj25-financas-web/contas.xhtml

Tente cadastrar uma nova conta. Ao cadastrar, recebemos uma


EJBTransactionRequiredException , pois o TransactionAttributeType.MANDATORY indica ao
Container EJB que o método adiciona depende de uma transação já iniciada anteriormente para
então poder ser invocado.

11.9 EXERCÍCIO OPCIONAL - BMT


1. No session bean ContaDao , tire qualquer @TransactionAttribute .

2. Adicione no session bean ContaDao a anotação no nível da classe:


@TransactionManagement(TransactionManagementType.BEAN)
@Stateless
public class ContaDao{
//codigo omitido
}

3. Injete o objeto do tipo javax.transaction.UserTransaction usando a anotação @Resource :


@TransactionManagement(TransactionManagementType.BEAN)

11.8 EXERCÍCIO OPCIONAL - @TRANSACTIONATTRIBUTE 127


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

@Stateless
public class ContaDao {

@Resource
private UserTransaction ut;

//codigo omitido
}

4. Use o objeto UserTransaction para gerenciamento da transação no método adiciona(Conta) :

public void adiciona(Conta conta) {


try {
this.ut.begin();
} catch (Exception e) {
throw new EJBException(e);
}

this.manager.persist(conta);

try {
this.ut.commit();
} catch (Exception e) {
try {
this.ut.rollback();
} catch (Exception e1) {
throw new EJBException(e1);
}
throw new EJBException(e);
}
}

Gerenciar transações é algo complicado. O código acima ainda precisa de alguns finally para
garantir a invocação do rollback quando necessário. O servidor já faz isso para você ao usar CMT,
e essa deve ser sua preferência!

128 11.9 EXERCÍCIO OPCIONAL - BMT


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 12

CRIANDO QUERIES AVANÇADAS PARA


OS RELATÓRIOS

"Quando se rouba de um autor, chama-se plágio; quando se rouba de muitos, chama-se pesquisa" --
Wilson Mizner

Ao término desse capítulo, você será capaz de:

Escrever consultas orientadas a objetos usando JPQL


Compreender as diferenças entre SQL e JPQL
Realizar operações avançadas de busca usando JPQL

12.1 ENTENDENDO A JPQL


No nosso DAO , fizemos os métodos básicos para um CRUD. Entre estes métodos estava o lista .
Vamos lembrar como ele ficou:
public List<Conta> lista() {
return manager.createQuery("select c from Conta c", Conta.class).
getResultList();
}

Escrevemos algo parecido com SQL mas estamos em um mundo completamente diferente. Onde
está o nome da tabela? Não precisamos saber, mesmo que a Conta tivesse sido anotada com @Table e
um nome de tabela diferente da convenção fosse informado, isso não seria importante. Quando
escrevemos from Conta estamos criando uma consulta que retorna todos objetos do tipo Conta .
Como isso será executado e outros detalhes de implementação ficam a cargo do EntityManager . Essa
maneira de executar consultas orientadas a objeto é conhecida como Java Persistence Query Language
mas é mais comumente chamada de JPQL (e HQL - Hibernate Query Language quando estamos
utilizando instruções específicas do Hibernate).

Por exemplo, uma operação importante que devemos ter no nosso sistema é a possibilidade de serem
listadas todas as movimentações de uma determinada conta. Vamos realizar essa consulta usando a
JPQL. Vamos colocar esse novo método na classe onde concentramos todos os métodos de persistência
relativos à Movimentacao , no caso a classe MovimentacaoDao . Para realizar as consultas, precisamos
do EntityManager que injetamos pela anotação @PersistenceContext :
@Stateless

12 CRIANDO QUERIES AVANÇADAS PARA OS RELATÓRIOS 129


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public class MovimentacaoDao {

@PersistenceContext
private EntityManager manager;

// outros métodos aqui


}

Agora, poderíamos adicionar o método para retornar todas as movimentações de determinada


Conta . O código ficaria assim:

public List<Movimentacao> listaTodasMovimentacoes(Conta conta) {


Query query = this.manager.createQuery("from Movimentacao m "
+ " where m.conta.id = " + conta.getId());
return query.getResultList();
}

Essa maneira resumida de escrever a JPQL, é uma funcionalidade que o Hibernate nos oferece mas
não é garantido pela especificação então, a partir de agora, escreveremos da seguinte maneira:
select m from Movimentacao m where ...

Demos um apelido (alias) para os objetos da classe Movimentacao , no nosso caso, m, e é através
dele que acessamos as propriedades. O nosso método com a query alterada, recuperando todas as
movimentações da conta, fica da seguinte forma:

public List<Movimentacao> listaTodasMovimentacoes(Conta conta) {


Query query = manager.createQuery("select m from Movimentacao m "
+ "where m.conta.id=" + conta.getId());
return query.getResultList();
}

Perceba que escrevemos bem parecido com SQL, mas em nenhum momento nos preocupamos em
qual tabela estamos trabalhando, muito menos com nomes de colunas. Nossa consulta toda é baseada no
que sabemos dos nossos objetos.

Por exemplo, sabemos que a Movimentacao tem um atributo que se chama conta e este um atributo
que se chama id, com isso podemos caminhar por eles.

Para realizar a consulta invocamos o método createQuery que recebe uma JPQL e nos retorna um
objeto do tipo Query . Com este objeto podemos solicitar que agora seja feita a a listagem, no caso
invocamos o getResultList .

O problema aqui é que a consulta ainda está com um gosto de SQL, estamos comparando o id .
Seria mais interessante se pudéssemos passar para o JPQL o nosso objeto do tipo Conta e ele fosse
responsável por descobrir, que nesse caso, o relacionamento estava se dando pelo atributo id daquela
conta. Além do mais, sabemos que ficar concatenando queries pode nos levar a problemas grandes,
como SQL Injection.

Para resolver ambos os problemas, o JPQL tem uma maneira de definir parâmetros, bem similar ao
PreparedStatement . Na nossa consulta, queremos definir um parâmetro na query para indicar que no

130 12.1 ENTENDENDO A JPQL


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

lugar dele vai entrar a conta recebida como parâmetro do método.

Temos duas maneiras de fazer isso no JPQL: a primeira é idêntica ao PreparedStatement , vamos
definindo as posições dos parâmetros. Abaixo temos o método modificado.
public List<Movimentacao> listaTodasMovimentacoes(Conta conta) {
Query query = this.manager.createQuery("select m from Movimentacao m "
+ " where m.conta = ?1");
query.setParameter(1, conta);
return query.getResultList();
}

Essa primeira forma é conhecido como Positional Parameter Notation. O problema dela é que
quando temos muitos parâmetros fica fácil confundir-se com os números, com isso acabamos errando as
posições. A segunda maneira que podemos fazer é ir dando nomes aos parâmetros da query. Desse jeito,
teríamos o seguinte código:
public List<Movimentacao> listaTodasMovimentacoes(Conta conta) {
Query query = this.manager.createQuery("select m from Movimentacao m "
+ " where m.conta = :conta");
query.setParameter("conta", conta);
return query.getResultList();
}

Essa segunda maneira é conhecida como Named Parameter Notation. Com ela, fica mais fácil
identificar os parâmetros e a chance de troca de nomes é menor do que a de troca dos números. Para
manter a lista organizada, vamos sempre ordenar a listagem pela data de movimentação de forma
ascendente. Como a JPQL é uma espécie de SQL orientada a objetos, a grande maioria das cláusulas são
exatamente as mesmas, incluindo a ordenação:
public List<Movimentacao> listaTodasMovimentacoes(Conta conta) {
Query query = this.manager.createQuery("select m from Movimentacao m "
+ " where m.conta = :conta "
+ " order by m.data asc");
query.setParameter("conta", conta);
return query.getResultList();
}

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

12.1 ENTENDENDO A JPQL 131


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

12.2 UTILIZANDO JPQL COM AND E OR NO FILTRO


Precisamos gerar alguns relatórios no nosso sistema. Para começar vamos disponibilizar uma tela
onde o usuário pode escolher o tipo de movimentação, e o valor da movimentação, que vão servir de
base para filtrar as movimentações do tipo e valor escolhido. Para realizar essa pesquisa poderíamos usar
um AND na nossa consulta. A JPQL ficaria assim:

String jpql = "select m from Movimentacao m "


+ "where m.valor = :valor and m.tipoMovimentacao = :tipo";

Agora, podemos adicionar o novo método na classe MovimentacaoDao . O código ficaria da


seguinte forma:
public List<Movimentacao> pesquisaMovimentacoes(
BigDecimal valor, TipoMovimentacao tipo) {
String jpql = "select m from Movimentacao m "
+ "where m.valor <= :valor and m.tipoMovimentacao = :tipo";
Query query = this.manager.createQuery(jpql);
query.setParameter("valor", valor);
query.setParameter("tipo", tipo);
return query.getResultList();
}

12.3 EXERCÍCIOS: BUSCANDO TODAS AS MOVIMENTAÇÕES DE


DETERMINADA CONTA
1. Queremos criar um método na MovimentacaoDao que liste todas as movimentações de
determinada conta. Para podermos fazer as consultas, vamos precisar do EntityManager que já
existe como atributo.

Crie um novo método na classe MovimentacaoDao que recebe uma Conta e busca as
movimentações associadas a ela. Escreva uma JPQL para isso, devolvendo as movimentações
ordenadas por valor.

Use named parameters ou positional parameters, como preferir.

Uma sugestão de implementação:


public List<Movimentacao> listaTodasMovimentacoes(Conta conta) {
String jpql = "select m from Movimentacao m " +
"where m.conta = :conta order by m.valor desc";
Query query = this.manager.createQuery(jpql);
query.setParameter("conta", conta);
return query.getResultList();
}

2. Vamos integrar essa nova pesquisa ao nosso sistema Web. Já há uma tela e um managed bean do JSF
preparados para exibir os dados, basta invocarmos nosso novo método.

Abra a classe MovimentacoesDaContaBean , nela é preciso injetar o DAO:

132 12.2 UTILIZANDO JPQL COM AND E OR NO FILTRO


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

@Inject
private MovimentacaoDao dao;

Depois procure na mesma classe o método lista e altere o método para que ele atribua à
movimentacoes o resultado de invocação ao novo método criado no DAO.

Reinicie o servidor e teste a nova pesquisa: http://localhost:8080/fj25-financas-


web/movimentacoesDaConta.xhtml

Não deixe de observar no console a query executada.

12.4 EXERCÍCIOS: BUSCANDO MOVIMENTAÇÕES SEGUINDO ALGUNS


CRITÉRIOS
1. Crie um método listaPorValorETipo na classe MovimentacaoDao que busca as movimentações
baseado no tipo e no valor. Para flexibilizar um pouco a busca, vamos filtrar as movimentações com
valor menor ou igual ao passado como parâmetro.
public List<Movimentacao> listaPorValorETipo(BigDecimal valor,
TipoMovimentacao tipo) {
}

Nosso JPQL será algo como:


select m from Movimentacao m
where m.valor <= :valor and m.tipoMovimentacao = :tipo

Implemente o método executando essa busca. Não esqueça de preencher os parâmetros na query.

2. Na classe MovimentacoesPorValorETipoBean injete o MovimentacaoDao e altere o método


lista para utilizar o método que acabamos de criar, passando tipoMovimentacao como segundo
argumento.

Reinicie o WildFly (ou faça Full publish). Teste acessando a URL: http://localhost:8080/fj25-
financas-web/movimentacoesPorValorETipo.xhtml

12.4 EXERCÍCIOS: BUSCANDO MOVIMENTAÇÕES SEGUINDO ALGUNS CRITÉRIOS 133


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

12.5 EXECUTANDO BUSCAS COM FUNÇÕES


Agora, precisamos disponibilizar uma tela onde o usuário consegue ver quanto ele movimentou
dentro de uma determinada conta. Um detalhe importante é que precisamos especificar o tipo de
movimentação que ele está procurando, se foi de entrada ou saída. Primeiramente, vamos montar a
query que seleciona todas as movimentações dado uma conta e um tipo de movimentação:
String jpql = "select m from Movimentacao m"
+ "where m.conta=:conta and m.tipoMovimentacao=:tipo"

Dessa maneira estaríamos retornando a lista com as movimentações, não é bem isso que queremos.
Queremos apenas a soma dos valores e, para isso, vamos usar a função de agregação sum para realizar a
soma de todos valores do conjunto de movimentações retornado:

String jpql = "select sum(m.valor) from Movimentacao m "


+ "where m.conta=:conta and m.tipoMovimentacao=:tipo"

Vamos criar um método na classe MovimentacaoDao para contemplar essa funcionalidade.


Anteriormente, usamos o método getResultList do EntityManager para retornar uma lista com os
objetos selecionados pela nossa consulta, mas nesse momento ele não nos serve. Queremos apenas
retornar um único valor, no caso o somatório dos valores das movimentações de determinado tipo. Para
isso, vamos usar o getSingleResult , o código ficaria da seguinte forma:
public BigDecimal calculaTotalMovimentado(Conta conta, TipoMovimentacao tipo) {
String jpql = "select sum(m.valor) from Movimentacao m "
+ "where m.conta=:conta and m.tipoMovimentacao=:tipo";
Query query = this.manager.createQuery(jpql);
query.setParameter("conta", conta);
query.setParameter("tipo", tipo);
return query.getSingleResult();
}

O problema é que os métodos getSingleResult e getResultList da interface Query sempre

134 12.5 EXECUTANDO BUSCAS COM FUNÇÕES


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

retornam respectivamente Object e List . O método calculaTotalMovimentado precisa retornar


um BigDecimal , consequentemente precisamos fazer um cast:
public BigDecimal calculaTotalMovimentado(Conta conta, TipoMovimentacao tipo) {
String jpql = "select sum(m.valor) from Movimentacao m "
+ "where m.conta=:conta and m.tipoMovimentacao=:tipo";
Query query = this.manager.createQuery(jpql);
query.setParameter("conta", conta);
query.setParameter("tipo", tipo);
return (BigDecimal) query.getSingleResult();
}

Se temos muitas consultas que retornam determinado tipo de objeto várias vezes no sistema, esse
código para fazer cast começa a ficar totalmente espalhado. Visando resolver esse problema, a JPA2
trouxe um novo tipo de objeto para realizarmos queries, que é 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:

public BigDecimal calculaTotalMovimentado(Conta conta, TipoMovimentacao tipo) {


String jpql = "select sum(m.valor) from Movimentacao m "
+ "where m.conta=:conta and m.tipoMovimentacao=:tipo";
TypedQuery<BigDecimal> query =
this.manager.createQuery(jpql, BigDecimal.class);
query.setParameter("conta", conta);
query.setParameter("tipo", tipo);
return query.getSingleResult();
}

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

12.6 PESQUISANDO COM FILTROS EM RELACIONAMENTOS


Os nossos usuários precisam pesquisar as movimentações de uma conta de determinado titular. Para
realizar essa consulta, vamos novamente usar a JPQL:

String jpql = "select m from Movimentacao m where m.conta.titular=:titular";

Repare que fazer uma consulta que envolve duas entidades com JPQL simplesmente resume-se a
navegar pelos relacionamentos. Mais uma vez estamos trabalhando voltado aos nossos objetos. Onde
está o join? Não precisamos nos preocupar com as chaves. Se fossemos fazer a mesma consulta usando
SQL, teríamos algo como:
select
movimentac0_.id as id1_,
movimentac0_.conta_id as conta6_1_,
movimentac0_.data as data1_,
movimentac0_.descricao as descricao1_,
movimentac0_.tipo as tipo1_,
movimentac0_.valor as valor1_
from
Movimentacao movimentac0_ cross

12.6 PESQUISANDO COM FILTROS EM RELACIONAMENTOS 135


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

join
Conta conta1_
where
movimentac0_.conta_id=conta1_.id
and conta1_.titular=?

Vamos agora adicionar o método que realiza a busca de todas movimentações de determinado titular
na MovimentacaoDao . Vamos usar a TypedQuery para dizer que queremos que o resultado seja uma
lista de movimentações:
public List<Movimentacao> buscaTodasMovimentacoesDaConta(String titular) {
String jpql = "select m from Movimentacao m where m.conta.titular=:titular";
TypedQuery<Movimentacao> query =
this.manager.createQuery(jpql, Movimentacao.class);
query.setParameter("titular", titular);
return query.getResultList();
}

12.7 EXERCÍCIOS: CALCULANDO O TOTAL MOVIMENTADO EM UMA


CONTA
1. Crie um método calculaTotalMovimentado na MovimentacaoDao para calcular o total
movimentado em uma conta para um certo tipo de movimentação.
public BigDecimal calculaTotalMovimentado(Conta conta, TipoMovimentacao tipo) {
}

Nossa query vai usar a função soma e filtrar por conta e tipo de movimentação:
select sum(m.valor) from Movimentacao m
where m.conta=:conta and m.tipoMovimentacao=:tipo

Para executar o JPQL, você pode usar uma TypedQuery recebendo BigDecimal para evitar o cast.
Como a consulta devolve apenas um valor simples, chame getSingleResult ao final. Um esboço:

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


// preencha os parâmetros
return query.getSingleResult();

2. Altere a classe TotalMovimentadoBean . Injete o MovimentacaoDAO usando a anotação @Inject :

@Inject
private MovimentacaoDao dao;

Procure o método calcula e altere para que ele invoque o calculaTotalMovimentado passando
conta e tipoMovimentacao como argumento e armazenando o resultado no atributo total .

3. Acesse a página http://localhost:8080/fj25-financas-web/totalMovimentado.xhtml e


escolha uma Conta e um Tipo de Movimentação para observar o valor total. Aproveite e olhe no
console a consulta que foi disparada no banco de dados.

136 12.7 EXERCÍCIOS: CALCULANDO O TOTAL MOVIMENTADO EM UMA CONTA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

12.8 EXERCÍCIOS: PESQUISANDO NO RELACIONAMENTO


1. Nossa próxima consulta deve devolver todas as movimentações da conta de um determinado titular.
A busca deve ser feita pelo nome do titular da conta, o que implica no uso de join. Além disso, use
like para buscar o nome.

Uma sugestão de implementação:


public List<Movimentacao> buscaTodasMovimentacoesDaConta(String titular) {
String jpql = "select m from Movimentacao m "
+ "where m.conta.titular like :titular";
TypedQuery<Movimentacao> query =
this.manager.createQuery(jpql, Movimentacao.class);
query.setParameter("titular", "%"+titular+"%");
return query.getResultList();
}

2. Altere a classe MovimentacoesPorTitularBean , injete o MovimentacaoDao e altere o método


lista para que o método acesse nosso novo método buscaTodasMovimentacoesDaConta do
DAO passando o titular como argumento.
3. Acesse a página http://localhost:8080/fj25-financas-
web/movimentacoesPorTitular.xhtml e digite um titular de uma conta para pesquisar. Aproveite
e olhe no console a consulta que foi disparada no banco de dados.

12.9 IMPLEMENTANDO PESQUISAS COMPLEXAS COM JPQL


Um relatório que precisamos fornecer ao nosso usuário deve possibilitar saber o valor total
movimentado por mês, podendo ser movimentação de entrada ou saída. Com isso, é possível saber quais
foram os meses em que mais dinheiro entrou ou saiu da conta.

Precisamos agrupar as movimentações de alguma forma para que possamos somar os valores de um

12.8 EXERCÍCIOS: PESQUISANDO NO RELACIONAMENTO 137


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

grupo de movimentações. Seguindo a semelhança de sintaxe com o SQL, vamos usar a cláusula group by
para poder reunir todas movimentações do mesmo tipo:
String jpql = "select m from Movimentacao m group by m.data";

O problema é que a data de uma Movimentacao é Timestamp portanto agruparíamos apenas as


movimentações que aconteceram no mesmo instante. Precisamos agrupar as movimentações por mês:
para isso, vamos usar a função month que nos retorna um inteiro relativo ao mês:
String jpql = "select m from Movimentacao m group by month(m.data)";

Nós agrupamos as movimentações apenas por mês, portanto as movimentações de janeiro de 2009 e
janeiro de 2010 farão parte do mesmo grupo. Para diferenciar as movimentações também por ano vamos
agrupá-las usando a função year junto com a função month:

String jpql = "select m from Movimentacao m group by year(m.data), month(m.data)";

AS FUNÇÕES MONTH E YEAR

É importante ressaltar que as funções month e year não estão definidas nem no Hibernate
nem na JPA. Quando o Hibernate encontra uma função desconhecida no meio do JPQL ele
simplesmente delega para o Banco de Dados na esperança de ele entender o comando. No caso de
month e year , muitos bancos (incluindo o MySQL) suportam essa sintaxe. Mas cuidado que ela
não faz parte da especificação e pode eventualmente não funcionar em certos bancos de dados.

Vamos agora adicionar os filtros necessários para especificar de qual conta estamos buscando as
movimentações, além de informar o tipo de movimentação. Para isso, vamos usar o where antes do
group by.
String jpql = "select m from Movimentacao m "
+ "where m.conta = :conta and m.tipoMovimentacao = :tipo "
+ "group by year(m.data), month(m.data) ";

O resultado da consulta deve trazer o mês, o ano e o total movimentado dentro daquele mês e ano.
Usando o month para pegar o mês, year para pegar o ano e a função sum para somar os valores, fazemos
nossa projeção dentro do SELECT:
String jpql = "select month(m.data), year(m.data), sum(m.valor) "
+ "from Movimentacao m "
+ "where m.conta = :conta and m.tipoMovimentacao = :tipo "
+ "group by year(m.data), month(m.data) ";

Com a consulta criada, basta usarmos o EntityManager para realizar a listagem. Mas o que o
getResultList() devolve neste caso? O Hibernate não tem como saber o tipo de objeto que a lista vai
conter, não há uma classe ou entidade que represente o retorno de meses e valores somados. De alguma
maneira, ele precisa poder guardar um número variável de objetos em alguma estrutura flexível. Para

138 12.9 IMPLEMENTANDO PESQUISAS COMPLEXAS COM JPQL


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

isso, ele usa um array de Objects para cada linha devolvida pelo resultado. Como estamos esperando
uma lista como retorno, temos uma estranha List<Object[]> :
public List<Object[]> listaMesesComMovimentacoes(Conta c, TipoMovimentacao tipo) {
String jpql = "select month(m.data), year(m.data), sum(m.valor) "
+ "from Movimentacao m "
+ "where m.conta = :conta and m.tipoMovimentacao = :tipo "
+ "group by year(m.data), month(m.data) ";

Query query = this.manager.createQuery(jpql);


query.setParameter("conta", c);
query.setParameter("tipo", tipo);

return query.getResultList();
}

Essa abordagem nos leva a um problema: quem invoca esse método precisa saber a ordem que os
valores estão posicionados em cada array dentro da lista, levando a um alto acoplamento do código com
a consulta.
Conta conta = new Conta();
conta.setId(1);

List<Object[]> resultado = movimentacaoDAO.listaMesesComMovimentacoes(


conta, TipoMovimentacao.ENTRADA);

for (Object[] info : resultado) {


System.out.println(info[0] + "--" + info[1]);
}

Se mudarmos a ordem do select da nossa consulta, vamos afetar quem invoca nosso método, fazendo
com que, possivelmente, alguma informação apareça errada para o usuário. Para diminuir esse
acoplamento, podemos criar uma nova classe para representar o retorno da consulta, ou seja, representar
o agrupamento de meses e valores. Não é uma nova entidade, é apenas uma classe simples que vai servir
simplesmente para guardar os valores e expor getters para acessá-los:

package br.com.caelum.financas.modelo;

public class ValorPorMesEAno {

private int mes;


private int ano;
private BigDecimal valor;

public ValorPorMesEAno(int mes, int ano, BigDecimal valor) {


this.mes = mes;
this.ano = ano;
this.valor = valor;
}

public BigDecimal getValor() {


return this.valor;
}

public int getMes() {


return this.mes;
}

12.9 IMPLEMENTANDO PESQUISAS COMPLEXAS COM JPQL 139


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public int getAno() {


return this.ano;
}
}

Precisamos agora transformar cada um dos Object[] em um objeto ValorPorMesEAno para, no


final, devolver uma List<ValorPorMesEAno> . Mas como fazer isso? Percorrer a lista de arrays na mão
e criar os novos objetos? Um recurso interessante da JPQL permite que a própria query instancie esses
objetos para nós no momento do select , assim conseguimos nossa List<ValorPorMesEAno> sem
muito esforço:
String jpql =
"select new br.com.caelum.financas.modelo.ValorPorMesEAno"
+ "(month(m.data), year(m.data), sum(m.valor)) "
+ "from Movimentacao m "
+ "where m.conta = :conta and m.tipoMovimentacao = :tipo "
+ "group by year(m.data), month(m.data) ";

Repare que temos que colocar o nome completo da nossa classe, ou seja, o nome do pacote mais o
nome da classe. Essa maneira de instanciar objetos dentro da JPQL é chamado de Constructor
Expressions. Esse objeto, que não é uma entidade, pode ser considerado como um Data Transfer Object,
pois serve apenas para transportar dados de uma maneira mais organizada, e pode ajudar na
granularidade, trazendo apenas os dados necessários.

Além disso, para organizar melhor o relatório, vamos ordená-lo de acordo com o total gasto no mês
de maneira decrescente:
public List<ValorPorMesEAno> listaMesesComMovimentacoes(Conta conta,
TipoMovimentacao tipoMovimentacao) {

String jpql =
"select new br.com.caelum.financas.modelo.ValorPorMesEAno"
+ "(month(m.data), year(m.data), sum(m.valor)) "
+ "from Movimentacao m "
+ "where m.conta = :conta and m.tipoMovimentacao = :tipo "
+ "group by year(m.data), month(m.data) "
+ "order by sum(m.valor) desc";

Query query = this.manager.createQuery(jpql);


query.setParameter("conta", conta);
query.setParameter("tipo", tipoMovimentacao);

return query.getResultList();
}

Com a transformação do ValorPorMesEAno , nosso código de acesso fica muito mais simples e
claro:
List<ValorPorMesEAno> resultado =
movimentacaoDAO.listaMesesComMovimentacoes(conta, TipoMovimentacao.ENTRADA);

for (ValorPorMesEAno info : resultado) {


System.out.println(info.getValor() + "--" + info.getMes() + "/"
+ info.getAno());

140 12.9 IMPLEMENTANDO PESQUISAS COMPLEXAS COM JPQL


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Não precisamos mais saber detalhes de implementação, como quantas posições tinham cada vetor da
lista e em quais posições estavam as informações. Podemos trabalhar com um objeto que faz muito mais
sentido para nosso domínio.

12.10 EXERCÍCIOS: RELATÓRIO AVANÇADO


1. Crie a classe ValorPorMesEAno no pacote br.com.caelum.financas.modelo . Ela deve ter um
atributo mes , outro ano ambos do tipo int e um atributo valor do tipo BigDecimal . Escreva
um construtor que receba esses argumentos e depois métodos getter para os atributos.

Dica: Use o Ctrl+3 para gerar o construtor e os getters. Para o construtor, digite o atalho gcuf e, para
os getters, ggas.

2. Crie o método listaMesesComMovimentacoes na classe MovimentacaoDao . Ele deve receber uma


Conta como argumento e um TipoMovimentacao . Seu retorno deve ser a
List<ValorPorMesEAno> .

public List<ValorPorMesEAno> listaMesesComMovimentacoes(Conta conta,


TipoMovimentacao tipo) {
}

Execute nossa query avançada no EntityManager . Não esqueça de preencher os parâmetros na


Query.
select
new br.com.caelum.financas.modelo.ValorPorMesEAno(month(m.data),
year(m.data), sum(m.valor))
from Movimentacao m
where m.conta = :conta and m.tipoMovimentacao = :tipo
group by year(m.data), month(m.data)
order by sum(m.valor) desc

3. Nosso sistema possui uma tela e um managed bean do JSF já preparados para exibir nosso relatório.
Vá na classe MesesComMovimentacaoBean e crie um novo atributo chamado valoresPorMesEAno
do tipo List<ValorPorMesEAno> . Esse novo atributo armazenará a lista que vai ser devolvida pela
busca que criamos antes no DAO.

Além do atributo, gere o método getValoresPorMesEAno . Tome cuidado com os nomes do


atributo e do getter para que a view consiga achar a listagem para exibir.

Depois injete o MovimentacaoDao , igual aos exercícios anteriores:


@Inject
MovimentacaoDao dao;

Por último, altere o método lista desse managed bean para que ele invoque nossa consulta
listaMesesComMovimentacoes no MovimentacaoDao e salve seu resultado no atributo

12.10 EXERCÍCIOS: RELATÓRIO AVANÇADO 141


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

valoresPorMesEAno que criamos antes.

4. Teste acessando a página http://localhost:8080/fj25-financas-


web/mesesComMovimentacao.xhtml

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

12.11 PARA SABER MAIS: HAVING


Poderíamos estar interessados apenas nos meses nos quais o total movimentado foi superior a um
certo valor. Para restringirmos nossa busca podemos aplicar uma restrição depois de ter agrupado as
movimentações por mês e ano. Com esse objetivo podemos utilizar a cláusula having .

select
new br.com.caelum.financas.modelo.ValorPorMesEAno
(month(m.data), year(m.data),sum(m.valor))
from Movimentacao m
where m.conta = :conta and m.tipoMovimentacao = :tipo
group by year(m.data), month(m.data) having sum(m.valor) > :minimo
order by sum(m.valor) desc

12.12 PARA SABER MAIS: NAMED QUERIES


Quando temos DAO s que começam a fazer muitas consultas, por exemplo aqueles que concentram
métodos para relatórios como é o caso do MovimentacaoDao , é comum ficarmos com muitas queries
espalhadas pelos métodos dentro da classe. Uma prática que já foi muito usada, consistia em externar
essas queries em arquivos properties do Java para que pudessem ser lidas pelo programa. O Hibernate já
vem com suporte nativo para este tipo de abordagem. Quando queremos centralizar as queries relativas
a alguma entidade podemos fazer uso das Named Queries. Para configurar essas consultas, usamos a
anotação NamedQuery . Por exemplo, imagine que queremos centralizar as consultas relativas aos
relatórios, poderíamos anotar nossa entidade Movimentacao . O código ficaria assim:
package br.com.caelum.financas.modelo;

@NamedQuery(name = "Movimentacao.buscaTodasMovimentacoesDaConta",
query = "select m from Movimentacao m
where m.conta.titular=:titular")
@Entity

142 12.11 PARA SABER MAIS: HAVING


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public class Movimentacao {


}

Perceba que segue o mesmo padrão de um arquivo properties. Associamos uma chave através do
atributo name à uma query, esta especificada pelo atributo query . Agora no nosso método teríamos:
public List<Movimentacao> buscaTodasMovimentacoesDaConta(String titular) {
Query query = this.manager.createNamedQuery("Movimentacao.
buscaTodasMovimentacoesDaConta");
query.setParameter("titular", titular);
return query.getResultList();
}

Uma outra característica imposta pelo uso das Named Queries é o uso dos Named Parameter
Notation ou dos Positional Parameter Notation. Como não temos como concatenar a query com algum
valor dinamicamente, somos obrigados a usar uma das duas maneiras de setar os parâmetros, o que
impõe logo de início a boa prática. Além destes ganhos, no momento da criação da
EntityManagerFactory , as Named Queries já são parseadas para o SQL específico, fazendo com que a
aplicação possa vir a ter ganhos de performance quando muito utilizada.

12.12 PARA SABER MAIS: NAMED QUERIES 143


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 13

RELACIONAMENTO BIDIRECIONAL E
LAZYNESS

"O pensamento é a grandeza do homem." -- Blaise Pascal

Ao término desse capítulo, você será capaz de:

Como mapear relacionamentos de forma bidirecional


Compreender os problemas envolvidos quando se trabalha com relacionamento de objetos
Entender Lazy Loading da JPA e como mudar essa estratégia

13.1 E QUANDO EU PRECISO SABER QUAIS SÃO AS MOVIMENTAÇÕES


DA CONTA?
Quando estávamos configurando a Movimentacao para ser gerenciada pela JPA, vimos que bastou
anotarmos o atributo conta com ManyToOne para estabelecer a relação de muitos-para-um. Com isso
conseguimos recuperar a conta associada à movimentação simplesmente invocando o getConta . No
entanto, no capítulo anterior, tivemos que fazer uma query para retornar as movimentações de
determinada conta via JPQL. Imagine, agora, que queremos exibir na listagem das contas quantas
movimentações cada uma tem. Teríamos um código mais ou menos assim:

Conta conta = // recuperamos uma conta

List<Movimentacao> movimentacoes = manager.createQuery(


"select m from Movimentacao m where m.conta = :conta").
setParamenter("conta", conta).
getResultList();

System.out.println(movimentacoes.size());

Pensando de uma maneira mais orientada a objetos, seria mais natural pedirmos a conta todas as
suas movimentações, ao invés de invocar um método em outro lugar. Se o código seguisse essa ideia
ficaria assim:
Conta conta = // recuperamos uma conta
System.out.println(conta.getMovimentacoes().size());

Seguindo essa linha, precisamos modificar nossa classe Conta para ter um atributo que possa
guardar todas as movimentações e disponibilizar os métodos de acesso para esse atributo. Modificando

144 13 RELACIONAMENTO BIDIRECIONAL E LAZYNESS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

nossa classe teríamos o seguinte código:


@Entity
public class Conta {

// ...
private List<Movimentacao> movimentacoes;
}

Deixando dessa maneira, ainda não conseguimos falar para a JPA trazer todas as movimentações
associadas a nossa conta. Perceba que simplesmente criamos um atributo, nada de mais. Assim como
anotamos com ManyToOne quando queremos indicar um relacionamento de muitos-para-um, temos
uma anotação para indicar um relacionamento de um-para-muitos, ela se chama @OneToMany . Para
completarmos a configuração da nossa classe Conta vamos mapear o atributo. O código ficaria:
@Entity
public class Conta {
// ...
@OneToMany
private List<Movimentacao> movimentacoes;
}

Dessa forma, estamos configurando a JPA para que, quando carregarmos um objeto do tipo Conta ,
ele consiga as movimentações associadas.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

13.2 EXERCÍCIOS: TESTANDO O NOVO MAPEAMENTO


1. Vamos alterar a classe Conta para adicionar o mapeamento OneToMany .

@Entity
public class Conta {
// outra atributos aqui
@OneToMany
private List<Movimentacao> movimentacoes;
}

Crie também seu getter e setter.

13.2 EXERCÍCIOS: TESTANDO O NOVO MAPEAMENTO 145


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

2. Pensando pelo lado relacional, adicionar esse novo mapeamento não deveria mudar nada nas tabelas
do banco. A tabela Movimentacao já tem a chave estrangeira para a tabela que guarda as contas.
Para verificar se tudo está correto, reinicie o WildFly agora. Ao subir o WildFly automaticamente
inicializa o JPA.

3. Acesse o MYSQL para verificar se as nossas tabelas sofreram alguma alteração ou se algo foi criado.
mysql -u root
use fj25;
show tables;

Perceba que uma tabela com nome Conta_Movimentacao foi criada. Por quê?

13.3 RELACIONAMENTO BIDIRECIONAL


Aqui temos mais uma clara diferença entre o mundo relacional e o orientado a objetos. Para o nosso
banco de dados, quando mapeamos a chave estrangeira na tabela de movimentações, automaticamente
já está estabelecida, além da relação de muitos para um da movimentação para a conta, a relação de um
para muitos da conta para as movimentações.

Para os nossos objetos, essa relação não é implícita. Não é porque temos um atributo do tipo Conta
na classe Movimentacao , que automaticamente temos um atributo do tipo List<Movimentacao> na
classe Conta , tivemos que fazer isso manualmente. Perceba que o inverso também não é obrigatório,
não é porque temos um List<Movimentacao> na Conta que toda movimentação tem um atributo do
tipo Conta .

É exatamente dessa maneira que a JPA entende. Para ele, existem duas relações, uma de muitos para
um da movimentação para a conta e outra de um para muitos da conta para as movimentações. Para
representar esse novo mapeamento, o Hibernate criou essa nova tabela Conta_Movimentacao .

146 13.3 RELACIONAMENTO BIDIRECIONAL


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

O que queremos então é configurar a JPA para que ela entenda que esse novo relacionamento
representa o mesmo que já temos configurado com a anotação ManyToOne na classe Movimentacao .
Para atingir esse objetivo, devemos usar um atributo da anotação OneToMany , cujo nome é mappedBy ,
que indica que essa relação é a mesma representada pelo atributo conta na classe Movimentacao :

@Entity
public class Conta {

// outros atributos aqui

@OneToMany(mappedBy="conta")
private List<Movimentacao> movimentacoes;

// getters e setters
}

Perceba que a classe que não possui o mappedBy mapeado no atributo é a que determina o modelo
de banco dados, por isso ele é chamado de owning entity ou lado forte da relação. A outra, que utiliza o
mappedBy para informar que aquele relacionamento já está mapeado na outra ponta, é chamada de
inverse entity ou lado fraco da relação.

Devemos evitar o uso impensado de getters e setters. Nossas entidades podem sim, e devem, possuir
regra de negócio que sejam de sua responsabilidade. Veremos no decorrer do curso como enriquecer
essas entidades, a fim de evitar o modelo anêmico e expor menos nossos atributos internos.

13.4 EXERCÍCIOS: UTILIZANDO O MAPPEDBY NO RELACIONAMENTO


BIDIRECIONAL
1. Vamos alterar nossa configuração de OneToMany no atributo movimentacoes da classe Conta
para utilizar o mapppedBy e indicar ao Hibernate que estamos falando do mesmo relacionamento.
Para isso, faça a anotação ficar @OneToMany(mappedBy="conta") .

2. Vá até o terminal e acesse o MySQL novamente. Delete a tabela de relacionamento criada pelo
Hibernate:
drop table Conta_Movimentacao;

3. Agora, reinicie o WildFly pelo Eclipse. Acesse novamente o MySQL e veja que nada de novo foi
criado. Ensinamos à JPA que o nosso relacionamento um para muitos da conta para as
movimentações, representa o mesmo do atributo conta na classe Movimentacao .

13.4 EXERCÍCIOS: UTILIZANDO O MAPPEDBY NO RELACIONAMENTO BIDIRECIONAL 147


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

13.5 DETALHES DO RELACIONAMENTO BIDIRECIONAL


Com o nosso OneToMany configurado podemos agora buscar as movimentações de uma conta
como comentamos anteriormente. Vamos ver o código para analisar o quão simples ficou:

Conta conta = // recupera uma conta;

for (Movimentacao movimentacao : conta.getMovimentacoes()) {


System.out.println(movimentacao.getDescricao());
}

Simplesmente invocamos o getMovimentacoes e o Hibernate cuida para que as movimentações


sejam retornadas.

Quando estamos utilizando um mapeamento bidirecional, ou seja a mesma relação mapeada nas
duas pontas, temos que tomar alguns cuidados. Por exemplo, imagine o seguinte o código:
Conta conta = // recupera uma conta sem movimentacoes

Movimentacao movimentacao = new Movimentacao();


movimentacao.setConta(conta);

// imprimindo o numero de movimentacoes da conta


System.out.println(conta.getMovimentacoes().size());

Pensando naturalmente, quando mostramos quantas movimentações estão associadas à nossa conta,
nesse momento, deveria ser impresso um, mas aparece zero. Como não existe nenhuma relação
assumida automaticamente, temos que, manualmente, adicionar essa movimentação na lista de
movimentações da conta. Então teríamos que fazer o seguinte:

Conta conta = // recupera uma conta sem movimentacoes

Movimentacao movimentacao = new Movimentacao();

movimentacao.setConta(conta);
conta.getMovimentacoes().add(movimentacao);

148 13.5 DETALHES DO RELACIONAMENTO BIDIRECIONAL


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

// imprimindo o numero de movimentacoes da conta


System.out.println(conta.getMovimentacoes().size());

Agora apareceria um quando fosse mostrado o número de movimentações associadas a conta.


Quando optamos por usar o mapeamento bidirecional, temos que tomar esse tipo de cuidado para
mantermos sempre consistente o estado dos nossos objetos. Certamente podemos, e devemos, fazer esse
tipo de garantia dentro dos próprios métodos que adicionam e removem objetos dos relacionamentos.
Isto é, o setConta ficaria responsável por invocar conta.getMovimentacoes().add . E criaríamos em
Conta um método adiciona que faria o mesmo trabalho na outra ponta.

13.6 RELACIONAMENTOS MANY TO MANY


Os nossos usuários estão sentindo a necessidade de fazer uma separação mais fina das
movimentações, ir além de indicar se ela foi de entrada ou saída. Vamos usar o recurso comum e criar
categorias para as nossas movimentações, por exemplo: moradia, alimentação, diversão e etc... Dessa
maneira o usuário sempre deve, antes de inserir uma movimentação de outro tipo de categoria, ir na
seção de cadastros de categorias e criar um nova.

Para representar essa possibilidade no nosso sistema, primeiramente precisamos de uma classe que
represente a categoria. Queremos gravar essas categorias criadas no banco de dados, para isso nossa
classe também precisa ser mapeada como uma entidade e gerenciada pelo JPA. Dessa maneira temos a
seguinte classe:
@Entity
public class Categoria implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String nome;

public Integer getId() {


return id;
}

public void setId(Integer id) {


this.id = id;
}

public String getNome() {


return nome;
}

public void setNome(String nome) {


this.nome = nome;
}

public String toString() {


return this.nome;
}
}

13.6 RELACIONAMENTOS MANY TO MANY 149


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Agora, precisamos informar a JPA que a movimentação pode possuir diversas categorias
relacionadas. Poderíamos pensar num relacionamento do tipo um para muitos, o problema dessa
abordagem é que cada categoria seria de apenas uma movimentação e, como provavelmente teríamos
duas movimentações associadas a mercado por exemplo, acabaríamos com várias categorias com nomes
repetidos cadastradas. Precisamos informar que uma movimentação pode ter várias categorias
associadas mas, ao mesmo tempo, essas mesmas categorias podem estar associadas a outras
movimentações, sem duplicar esses cadastros. Isso caracteriza um relacionamento do tipo muitos para
muitos. Primeiro precisamos alterar a classe Movimentacao para indicar que ela contém uma lista de
categorias associadas. Ficamos com o código da seguinte maneira:
@Entity
public class Movimentacao {

private List<Categoria> categorias = new ArrayList<Categoria>();

// outros atributos
}

Temos também que informar a JPA que essa lista representa um relacionamento muitos para
muitos. Para isso, vamos utilizar a anotação ManyToMany :
@Entity
public class Movimentacao {

@ManyToMany
private List<Categoria> categorias = new ArrayList<Categoria>();
// ...
}

Pensando no banco de dados, não temos como representar esse tipo de relacionamento apenas com
duas tabelas. Temos apenas um cadastro da movimentação e um cadastro da categoria e precisamos
dizer que essa mesma movimentação está relacionada com várias categorias e vice-versa.

Para representar isso, é utilizada uma tabela que, geralmente, contém apenas duas colunas: uma para
o id de uma ponta da relação, no nosso caso a movimentação e outra para a outra ponta da relação, no
nosso caso a categoria. Essa tabela é chamada de Tabela de Relacionamento, ou tabela associativa. O
único objetivo é relacionar registros de uma tabela com os registros da outra. É justamente essa tabela
que vai ser gerada pelo Hibernate para representar nosso mapeamento de muitos para muitos. Pela
convenção, vai ser gerada uma tabela chamada Movimentacao_Categoria , ou seja, o nome da classe
que contém o relacionamento e depois o nome da classe que foi associada. É interessante notar que, mais
uma vez, em nenhum momento vamos precisar fazer referência a essas tabelas que foram criadas para
nós. Sempre trabalhamos baseado nos nossos objetos!

13.7 EXERCÍCIO: CRIANDO UM RELACIONAMENTO MUITOS-PARA-


MUITOS
1. Crie a classe Categoria no pacote br.com.caelum.financas.modelo e configure-a para ser uma

150 13.7 EXERCÍCIO: CRIANDO UM RELACIONAMENTO MUITOS-PARA-MUITOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

entidade gerenciada pela JPA (não esqueça de registrá-la no persistence.xml):


@Entity
public class Categoria implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
private String nome;

public Integer getId() {


return id;
}

public void setId(Integer id) {


this.id = id;
}

public String getNome() {


return nome;
}

public void setNome(String nome) {


this.nome = nome;
}
}

2. Sobrescreva também o método toString na classe Categoria para retornar o nome da categoria:
@Entity
public class Categoria implements Serializable {

//atributos, getters e setters

public String toString() {


return this.nome;
}
}

3. Agora vamos alterar a classe Movimentacao adicionando o atributo categorias do tipo


List<Categoria> . Para que possamos indicar que este atributo representa um relacionamento de
muitos para muitos com a classe Categoria vamos também adicionar a anotação @ManyToMany .
@Entity
public class Movimentacao {
//outros atributos...

@ManyToMany
private List<Categoria> categorias = new ArrayList<Categoria>();

Não esqueça dos getters e setters do novo atributo.

4. Reinicie o WildFly. Após subir, o banco de dados deve ser atualizado. (Caso precise, faça um Full
Publish do projeto fj25-financas-web )

13.7 EXERCÍCIO: CRIANDO UM RELACIONAMENTO MUITOS-PARA-MUITOS 151


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Acesse o MySQL para verificar o resultado. As tabelas Categoria e Movimentacao_Categoria


devem ter sido criadas.

5. Antes de cadastrar as categorias associadas a cada movimentação, é preciso preparar os dados para a
tela. Precisamos listar todas as categorias e buscar uma categoria pelo id. Para isolar essa operação,
vamos criar um DAO para a categoria. No pacote br.com.caelum.financas.dao crie a classe
CategoriaDao , use a anotação @Stateless para indicar um Session Bean e injete o
EntityManager com a anotação @PersistenceContext :

package br.com.caelum.financas.dao;

@Stateless
public class CategoriaDao implements Serializable {

@PersistenceContext
private EntityManager manager;

public Categoria procura(Integer id) {


//busca pela id
}

public List<Categoria> lista() {


//retorna todas as categorias
}

6. Vamos utilizar este DAO na classe MovimentacoesBean . Abra a classe e injete o CategoriaDao :

@Inject
private CategoriaDao categoriaDao;

7. A nossa tela vai chamar na classe MovimentacoesBean uma ação para associar uma categoria com a
movimentação a cadastrar.

Para isso funcionar, vamos criar um método adicionaCategoria que busca a categoria pelo id e
passa para a movimentação (o id da categoria vem do formulário):
public void adicionaCategoria() {
if(this.categoriaId != null && this.categoriaId > 0){
Categoria categoria = categoriaDao.procura(this.categoriaId);
this.movimentacao.getCategorias().add(categoria);
}
}

8. O formulário da movimentação terá uma combo box para mostrar todas as categorias disponíveis.
Para tal vamos adicionar um novo atributo na classe MovimentacoesBean que representa todas as
categorias.

Adicione o atributo:

private List<Categoria> categorias;

Também gere o getter para o atributo. No getter vamos utilizar o DAO para buscar as categorias:

152 13.7 EXERCÍCIO: CRIANDO UM RELACIONAMENTO MUITOS-PARA-MUITOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public List<Categoria> getCategorias() {


if(this.categorias == null) {
System.out.println("Listando as categorias");
this.categorias = this.categoriaDao.lista();
}
return this.categorias;
}

9. Vá até a página movimentacoes.xhtml e altere o atributo rendered="false" no formulário de


movimentação. Altere apenas relativo à exibição do campo de preenchimento das categorias
associadas a cada movimentação. O código que era assim:
<h:outputText value="Categorias" rendered="false"/>
<h:panelGroup rendered="false">

Deve ficar da seguinte maneira:

<h:outputText value="Categorias" rendered="true"/>


<h:panelGroup rendered="true">

Isso deve habilitar o combobox para as categorias.

10. Não temos categorias cadastradas no banco de dados. Para isso abra o terminal e insira as seguintes
categorias no MySQL:
insert into Categoria(nome) values('gasto fixo');
insert into Categoria(nome) values('gasto variavel');

11. Reinicie o servidor e acesse o endereço http://localhost:8080/fj25-financas-


web/movimentacoes.xhtml .

Anteriormente nossas movimentações não estavam relacionadas a nenhuma categoria. Cadastre


novas movimentações associando-as as novas categorias.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

13.8 PARA SABER MAIS: @JOINTABLE

13.8 PARA SABER MAIS: @JOINTABLE 153


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Pela convenção, o nome da tabela gerada pelo relacionamento @ManyToMany é


Movimentacao_Categoria , mas existe a possibilidade de alterar esse nome com o uso da anotação
@JoinTable :

@ManyToMany
@JoinTable(name = "categorias_da_movimentacao")
private List<Categoria> categorias = new ArrayList<Categoria>();

13.9 COMPORTAMENTO LAZY


Precisamos mostrar para o usuário quais são as categorias relacionadas com cada movimentação.
Análogo à exibição das movimentações da conta, basta invocarmos o getCategorias() e iterar sobre o
resultado. Para isso temos:

EntityManager em = // recupera entityManager;


Movimentacao movimentacao = em.find(Movimentacao.class,1);
for (Categoria categoria : movimentacao.getCategorias()) {
System.out.println(categoria.getNome());
}

É interessante notar que dois selects foram realizados, um primeiro para simplesmente carregar a
movimentação com id especificado e um segundo, no momento que invocamos pela primeira vez o
método getNome() da primeira categoria iterada pelo for . Para tentar otimizar ao máximo o
desempenho da aplicação, o Hibernate só vai realizar as queries quando realmente for necessário. Esse
comportamento é conhecido como lazy load ou carregamento preguiçoso.

Usando essa abordagem, sempre que buscarmos uma movimentação traremos também suas
categorias. Se existirem muitos locais no sistema nos quais precisamos de todos os dados da
movimentação exceto as categorias, adotar a estratégia EAGER para categorias será uma boa alternativa?

13.10 PROBLEMA COMUM NA WEB: LAZYINITIALIZATIONEXCEPTION


Agora, precisamos exibir as categorias associadas a cada movimentação na nossa tela de listagem de
movimentações. Baseado no exemplo anterior, basta, para cada movimentação, colocar as tags
relacionadas em mais uma coluna da tabela:
<h:column>
<f:facet name="header">
<h:outputText value="Categorias" />
</f:facet>
#{movimentacao.categorias}
</h:column>

Como vimos anteriormente, o Hibernate adota aqui o lazy load como comportamento default, então
no momento que invocamos o getCategorias ele vai tentar disparar a query para realizar a busca. O
problema que temos aqui, é que nesse momento o EntityManager já foi fechado. Repare na imagem
abaixo que o EntityManager está sendo gerenciado pelo container EJB. A movimentação na view não

154 13.9 COMPORTAMENTO LAZY


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

está mais gerenciadada e e sim no estado Detached. Não sendo mais gerenciado fica impossível a
realização da query.

Consequentemente, quando tentarmos exibir as categorias na nossa view, uma exception vai ser
disparada informando que não foi possível a realização do lazy load. No caso do Hibernate, essa
exception tem o nome de LazyInitializationException .

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

13.11 EXERCÍCIO: RELACIONAMENTOS LAZY


1. Vá até a página movimentacoes.xhtml e retire o atributo rendered="false" da coluna para exibição
das categorias associadas a cada movimentação (o código está por volta da linha 110).
2. Reinicie o servidor e acesse o endereço http://localhost:8080/fj25-financas-
web/movimentacoes.xhtml . Note que foi lançada a exception do tipo
org.hibernate.LazyInitializationException . Essa exception depende exclusivamente da
implementação que estamos utilizando da JPA2, se trocarmos, a exception vai mudar mas a causa é a
mesma.

13.11 EXERCÍCIO: RELACIONAMENTOS LAZY 155


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

13.12 INICIALIZANDO AS ENTIDADES COM QUERIES PLANEJADAS


Como vimos o container EJB gerencia o pool de conexões, a transação e a JPA, seguindo o princípio
de inversão de controle. Para receber o EntityManager usamos simplesmente a anotação
@PersistenceContext , por exemplo na classe MovimentacaoDao :

@Stateless
public class MovimentacaoDao {

@PersistenceContext
private EntityManager manager;

//método omitidos
}

Enquanto trabalhamos com esse EntityManager dentro de um EJB Session Bean temos a garantia
de ter uma conexão e transação aberta.

Para fugir do problema do lazy load uma estratégica adotada do mercado é inicializar todos os dados
antes de passar para a tela (view), ou seja inicializar todos os dados dentro do Session Bean. A ideia é
planejar antecipadamente quais entidades e relacionamentos serão utilizados, e assim só devolver dados
já carregados sem causar o problema do lazy load.

Para o resolver o nosso problema o JPQL oferece uma forma de pedir explicitamente o carregamento
dos relacionamentos. Isto é feito através de um join explicito junto com a clausula fetch . Por
exemplo podemos carregar todas as movimentações com as categorias:
public List<Movimentacao> lista() {
return manager.createQuery("select m from Movimentacao m "
+ " join fetch m.categorias", Movimentacao.class).
getResultList();
}

Porém ao executarmos a query vemos que nossos registros estão repetidos. Isto acontece porque para
cada linha em categorias serão trazidas todas as movimentações onde aquela categoria está relacionada.
Mas se uma movimentação tem mais de uma categoria, isso acaba causando a repetição desses registros
em nossa consulta. Para trazer apenas uma vez cada uma das movimentações encontradas, usamos a
cláusula distinct :
select distinct m from Movimentacao m join fetch m.categorias

Dessa maneira, estaríamos trazendo apenas as movimentações que possuem categorias, se quizermos
trazer todas, inclusive as que não tem categorias, poderíamos usar um LEFT JOIN. Então ficaria da
seguinte forma:
select distinct m from Movimentacao m left join fetch m.categorias

Assim que executamos a query, automaticamente serão carregadas as categorias. No entanto nem
sempre queremos todas as movimentações juntas com as categorias e por isso faz sentido criar um outro
método na classe MovimentacaoDao com essa responsabilidade e nome específico:

156 13.12 INICIALIZANDO AS ENTIDADES COM QUERIES PLANEJADAS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public List<Movimentacao> listaComCategorias() {


return manager.createQuery("select distinct m from Movimentacao m "
+ " join fetch m.categorias", Movimentacao.class).
getResultList();
}

Aqui fica visível a desvantagem dessa abordagem. Naturalmente vamos escrever mais queries e
menos utilizar o nosso modelo OO. O código se torna mais procedural o que pode prejudicar a
manutenção ao longo prazo.

13.13 EXERCÍCIOS: QUERIES PLANEJADAS


1. Vamos planejar melhor quais dados são precisos na tela para fugir da
LazyInitializationException .

Abra a classe MovimentacaoDao e adicione o método listaComCategorias, parecido com o método


lista . Nele carregue as movimentações junto com as categorias usando left join fetch:

select distinct m from Movimentacao m left join fetch m.categorias

2. Abra a classe MovimentacoesBean . Nela substitua todas as chamadas do método lista do


MovimentacaoDao com o método listaComCategorias .

3. Reinicie o servidor e acesse o endereço http://localhost:8080/fj25-financas-


web/movimentacoes.xhtml .

Na tabela deve aparecer as categorias de cada movimentação.

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

13.14 PARA SABER MAIS: FETCH PROFILE


Outra maneira de fazer o carregamento da coleção logo no momento da execução do select que
busca o objeto que a contém, no nosso caso a Movimentacao , seria usar o atributo fetch mudando o
comportamento default de lazy, para eager. Este atributo está presente em qualquer anotação que
represente um relacionamento. No nosso caso, ficaríamos com o seguinte código:

13.13 EXERCÍCIOS: QUERIES PLANEJADAS 157


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

@ManyToMany(fetch=FetchType.EAGER)
private List<Categoria> categorias = new ArrayList<Categoria>();

Dessa maneira, sempre que buscarmos por uma movimentação, automaticamente já vai ser
carregada também a lista com todas as categorias. Nesse caso, devemos tomar cuidado com exageros.
Muitas pessoas acabam mudando todos seus relacionamentos para EAGER , gerando um excesso de
carregamentos de entidades totalmente desnecessário.

13.15 RESOLVENDO O PROBLEMA DO LAZY-LOADING


Apesar das queries planejadas terem resolvidos os problemas do lazy loading vimos que
automaticamente escreveremos mais queries usando um estilo mais procedural. O ideal seria continuar
usando o nosso modelo OO quando podermos. Mas qual seria a forma de abrir e fechar o EntityManager
dentro da nossa aplicação?

Uma boa política de aquisição de entity managers dentro de uma aplicação web é uma por requisição.
Uma ideia é interceptar toda requisição ao nosso servidor e, antes de mais nada, criar um novo entity
manager, juntamente com uma transação. Depois que todo fluxo é executado devemos fazer o commit e
fechar o entity manager corrente. Deixaremos o entity manager aberto durante toda a requisição,
inclusive durante a renderização da nossa view, para o caso da utilização de coleções lazy. Assim
podemos resolver o problema do lazy load!

Para atingirmos esse objetivo, poderiamos utilizar servlet filters que abrirão e fecharão nossos _entity
manager_s mas isso só funcionaria em uma aplicação sem EJB. Nesse caso gerenciaríamos o entity
manager dentro do Servlet Container sem um segundo container. Isso também significa que estamos
responsáveis pela abertura e commit das transações. Ou seja, perderíamos a inversão de controle que o
container EJB oferece.

Vamos continuar com a ajuda do EJB mas usando o EntityManager per request pattern ou seja, um
EntityManager por requisição. Para implementar este padrão podemos aproveitar o CDI. Ele vai

158 13.15 RESOLVENDO O PROBLEMA DO LAZY-LOADING


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

gerenciar a abertura e fechamento dos EntityManager s. A JPA e a transação continuarão nas mãos do
container EJB, mas o CDI é quem vai disponibilizar o EntityManager para a aplicação.

13.16 OPEN ENTITYMANAGER IN VIEW COM CDI


Primeiramente vamos criar uma classe que será responsável pela abertura e fechamento do
EntityManager . No CDI essas classes que produzem algo são chamadas de Producer :

public class EntityManagerProducer {

Nela vamos colocar dois métodos, um para abrir ( getEntityManager ) outro para fechar ( close ).
No getEntityManager vamos abrir o manager pela EntityManagerFactory como já foi visto.
Repare também que o método close recebe o EntityManager :

public class EntityManagerProducer {

private EntityManagerFactory factory;

public EntityManager getEntityManager(){


EntityManager manager = factory.createEntityManager();
return manager;
}

public void close(EntityManager manager){


manager.close();
}

A EntityManagerFactory será injetada pelo CDI. Para isso existe uma anotação específica.
Analogo ao @PersistenceContext para injetar um EntityManager , existe a anotação
@PersistenceUnit para uma EntityManagerFactory :

public class EntityManagerProducer {

@PersistenceUnit
private EntityManagerFactory factory;

13.16 OPEN ENTITYMANAGER IN VIEW COM CDI 159


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Agora só falta ensinar ao CDI produzir (@Produces) o EntityManager no início da requisição


(@RequestScoped) pelo método getEntityManager , novamente feito através de anotações:
@Produces
@RequestScoped
public EntityManager getEntityManager(){
EntityManager manager = factory.createEntityManager();
return manager;
}

E também declarar que o método close é responsável por fechar o EntityManager :


public void close(@Disposes EntityManager manager){
manager.close();
}

Por fim, só precisamos de um EntityManagerProducer na aplicação. Vamos configurar isso pela


anotação @ApplicationScoped e o CDI criará apenas um objeto do tipo EntityManagerProducer .
Veja a classe completa:
@ApplicationScoped
public class EntityManagerProducer {

@PersistenceUnit
private EntityManagerFactory factory;

@Produces @RequestScoped
public EntityManager getEntityManager(){
return factory.createEntityManager();
}

public void close(@Disposes EntityManager manager){


manager.close();
}
}

Repare que a EntityManagerFactory continua sendo gerenciada pelo container EJB porém
abrimos o EntityManager pelo CDI. Este tipo de EntityManager também é chamado de Application
Managed Entity Manager.

Nos DAOs devemos usar o novo EntityManager que está sendo produzido com a ajuda do CDI.
Para isso basta trocar a anotação @PersistenceContext por @Inject :
@Stateless
public class ContaDao {

@Inject //@PersistenceContext
private EntityManager manager;

Isso já é suficiente para usar um EntityManager que vive apenas por uma requisição, ou seja, estará
aberto enquanto a view está sendo renderizada, seguindo assim o padrão chamado
OpenEntityManagerInView, porém este EntityManager não participa automaticamente da transação
gerenciada pelo container EJB. Nos métodos que precisam de transação devemos chamar
manager.joinTransaction(), por exemplo:

160 13.16 OPEN ENTITYMANAGER IN VIEW COM CDI


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public void adiciona(Movimentacao movimentacao) {


manager.joinTransaction();
manager.persist(movimentacao);
}

OPEN SESSION IN VIEW

Há um tópico na documentação do Hibernate que comenta justamente sobre o EntityManager


per request pattern, para mostrar que devemos usar o mesmo entity manager dentro de uma unit-
of-work, não mais nem menos. Confira mais detalhes em:

http://docs.jboss.org/hibernate/stable/entitymanager/reference/en/html_single/#transactions-
basics-uow

Esse padrão no Hibernate era conhecido analogamente como Open Session In View.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

13.17 EXERCÍCIOS: IMPLEMENTANDO O PATTERN OPEN


ENTITYMANAGER IN VIEW
1. O principal elemento da nossa implementação do Open EM in View é o producer que abre e fecha o
EntityManager .

Crie uma classe chamada EntityManagerProducer no pacote br.com.caelum.financas.util e


marque-a com a anotação ApplicationScoped . Nela injete a EntityManagerFactory :
@PersistenceUnit
private EntityManagerFactory factory;

Vamos implementar dois métodos getEntityManager e close que devem criar e fechar um
EntityManager :

13.17 EXERCÍCIOS: IMPLEMENTANDO O PATTERN OPEN ENTITYMANAGER IN VIEW 161


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

@Produces @RequestScoped
public EntityManager getEntityManager(){
return factory.createEntityManager();
}

public void close(@Disposes EntityManager manager){


manager.close();
}

2. Precisamos agora usar o EntityManager dentro dos DAOs.

Abra as classes Dao e substitua a anotação @PersistenceContext pela @Inject , por exemplo:
@Stateless
public class ContaDao {

@Inject //@PersistenceContext
private EntityManager manager;

3. Abra a classe MovimentacoesBean e procure o método getMovimentacoes. Nele volte a chamar o


método lista ao invés de listaComCategorias .

4. Abra a classe MovimentacaoDao . Nos métodos que precisam que o EntityManager participe de
uma transação, chame o método manager.joinTransaction() .

Faça o mesmo para os outros Dao .

5. Teste o cadastro da movimentação:

http://localhost:8080/fj25-financas-web/movimentacoes.xhtml

Teste também o cadastro de uma conta.

13.18 @BATCHSIZE E QUERIES N+1


Há alguns perigos não só com lazy, mas com o eager também. Pois algumas queries não vão buscar
todos os dados, mesmo que esteja anotado assim. É o caso que vimos de buscar todas as movimentações
dado o nome de uma pessoa, no capítulo anterior, em nosso MovicamentacaoDao :
public List<Movimentacao> buscaTodasMovimentacoesDaConta(String titular) {
String jpql = "select m from Movimentacao m where m.conta.titular = :titular";
TypedQuery<Movimentacao> query = this.em.createQuery(jpql, Movimentacao.class);
query.setParameter("titular", titular);
return query.getResultList();
}

Apesar de select m from Movimentacao m where m.conta.titular = :titular gerar um join


para buscar os dados de Movimentacao , essas movimentações não terão suas Contas populadas. Ao
mostrar todos os nomes dos titulares, cada invocação de getConta de uma conta diferente gerará mais
uma query, totalizando n , sendo que n pode chegar ao número de resultados de movimentações. É
um problema conhecido como n+1 queries.

162 13.18 @BATCHSIZE E QUERIES N+1


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Uma forma de minimizar o problema é através da anotação @BatchSize na classe Conta ou no


relacionamento de Movimentacao.conta , passando um size=30 por exemplo. Quando o Hibernate
perceber que está buscando uma Conta no banco por que encontrou um id de conta, ele verificará se
há mais alguma outra Movimentacao sem ter sua conta populada, e aproveitará para buscá-la também,
na mesma query. Isso até um máximo de 30 contas, nesse caso.

A outra forma é fazer o join fetch , forçando essa query a já retornar tudo de conta, como aqui:

select distinct m from Movimentacao m join fetch m.conta as c where


c.titular=:titular

Veremos, mais para a frente, o cache de segundo nível. Ele também ajudaria nesse caso, pois cada
Conta poderia já ter sido cacheada durante o uso de outro EntityManager .

13.18 @BATCHSIZE E QUERIES N+1 163


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 14

RECURSOS AVANÇADOS: CACHE,


ESTATÍSTICAS E LOCKS

"Fazer troça da filosofia é, na verdade, filosofar" -- Blaise Pascal

Ao término desse capítulo, você será capaz de:

Melhorar a performance da sua aplicação através das estratégias de cache


Compreender as diferentes estratégias de cache do Hibernate
Visualizar estatísticas de uso do Hibernate para descobrir eventuais problemas

14.1 CACHE DE PRIMEIRO NÍVEL


Um equívoco comum cometido por quem está começando a aprender a JPA é o de pensar que, por
ser uma camada a mais em nossa aplicação que está abstraindo o JDBC, a aplicação será mais lenta.
Claro que há uma camada a mais executando tarefas antes inexistentes ao acessar o banco diretamente
por JDBC. Mas, tanto a JPA quanto o Hibernate trazem, prontas, estratégias de otimização
extremamente avançadas, a ponto de ser muitas vezes mais fácil escrever código performático usando
essas tecnologias do que usando JDBC.

Uma das formas mais efetivas da JPA para otimizar a performance da nossa aplicação é através de
caches dos dados que trazemos, evitando assim muitas chamadas ao banco de dados.

A maneira mais simples de cache que existe na JPA é a que os EntityManager s possuem
internamente. Nesse caso, o EntityManager evita que carreguemos duas vezes a mesma informação do
banco de dados.

Com isso, ao invocarmos um find no EntityManager duas vezes para trazer o mesmo objeto,
este só vai até o banco de dados na primeira vez. Quando o find é invocado pela segunda vez no
mesmo EntityManager ele sabe que aquele objeto já foi carregado anteriormente e reaproveita os
dados já carregados sem fazer a segunda comunicação com o banco de dados.

Essa camada, entre cada EntityManager e o banco de dados, é o chamado cache de primeiro nível.

164 14 RECURSOS AVANÇADOS: CACHE, ESTATÍSTICAS E LOCKS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

14.2 EXERCÍCIOS: TESTANDO O CACHE DE PRIMEIRO NÍVEL


1. Podemos testar o Cache de primeiro nível utilizando o mesmo EntityManager e tentando buscar o
mesmo objeto por duas vezes no banco de dados.

Abra a classe CacheBean . Nela injete diretamente o EntityManager , ou seja sem o DAO:
@Inject
private EntityManager manager;

2. Ainda na classe CacheBean já preparamos um atributo conta e id . Vamos usar ambos no


método pesquisar para carregar uma conta pelo id. Nesse método use duas vezes o find do
EntityManager para testar o cache de primeiro nível:

public void pesquisar() {


this.conta = this.manager.find(Conta.class, id);
this.conta = this.manager.find(Conta.class, id);
}

3. Reinicie o servidor e acesse o endereço http://localhost:8080/fj25-financas-


web/cache.xhtml .

14.2 EXERCÍCIOS: TESTANDO O CACHE DE PRIMEIRO NÍVEL 165


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

No formulário digite um id de uma conta que exista no banco de dados. Veja a saída no console.
Quantos selects foram feitos? Era para acontecer isso mesmo?

14.3 CACHE DE SEGUNDO NÍVEL


Quando utilizamos o cache de primeiro nível ganhamos um pouco em termos de performance. No
entanto, em uma aplicação web, geralmente não mantemos a EntityManager aberta durante mais
tempo do que uma requisição, e dessa forma o cache de primeiro nível é rapidamente descartado.

Nesse caso, o mais vantajoso seria um cache que mantivesse as informações entre vários
EntityManagers . Dessa forma, usuários diferentes que enviassem requisições distintas
compartilhariam o mesmo cache. Se ambos precisassem ler a mesma informação, ela só seria buscada no
banco de dados uma única vez. E se depois disso a informação precisasse ser lida por outros usuários
novamente, não haveria uma nova comunicação com o banco de dados.

Mas se a informação não é buscada do banco de dados, de onde ela vem então? Precisamos de um
espaço de cache que seja compartilhado entre os EntityManager e que seja utilizado quando o cache
de primeiro nível não tiver a informação desejada. Esse é o cache de segundo nível.

O cache de segundo nível é bem mais crítico (e complexo) que o de primeiro nível, já que a
possibilidade de lidar com dados desatualizados (stale) é bem maior. Caso haja algum outro sistema
atualizando os dados sem passar pelo Hibernate, pode inviabilizar completamente o seu uso. Esse cache
vem desabilitado por padrão, mas costuma ser essencial para o bom andamento de uma aplicação.

14.4 CACHE NO WILDFLY: INFINISPAN

166 14.3 CACHE DE SEGUNDO NÍVEL


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Há diversas outras implementações de cache que se encaixam bem com a JPA e com o Hibernate,
seguindo suas APIs. Uma que tem ganhado bastante destaque é o Infinispan, disponível no WildFly.

O Infinispan é um banco NoSQL com um foco grande em escalabilidade. Veja mais no site do
projeto:

http://infinispan.org

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

14.5 HABILITANDO O INFINISPAN EM SUA APLICAÇÃO


Independente se estamos usando Infinispan ou não, ainda é necessário indicar para o Hibernate que
ele fará uso de um cache de segundo nível:
<!-- no persistence.xml, para um persistence-unit -->

<property name="hibernate.cache.use_second_level_cache" value="true" />

O WildFly já vem por padrão com o Infinispan, sendo assim, não precisamos configurá-lo como
provider de cache para uma persistence-unit dentro do persistence.xml .

A única configuração necessária do Infinispan no persistence.xml aparece somente quando


queremos alterar o padrão do modo de cache compartilhado, o que é feito através da configuração
shared-cache-mode:
<!-- no persistence.xml, para um persistence-unit -->
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>

Os valores possíveis para esta configuração são:

ALL: força o cache para todas as entidades.


NONE: desabilita o cache para todas as entidades. Útil quando queremos desabilitar o cache
de segundo nível.
ENABLE_SELECTIVE: habilita o cache quando as entidades estão explicitamentes marcadas

14.5 HABILITANDO O INFINISPAN EM SUA APLICAÇÃO 167


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

com a anotação @Cacheable .


DISABLE_SELECTIVE: habilita o cache para todas as entidades, exceto as marcadas
explicitamente com @Cacheable(false)
UNSPECIFIED (padrão) as configurações não estão definidas, cada provider pode tratar de
uma maneira diferente.

A configuração padrão ENABLE_SELECTIVE permite controlar quais entidades entrarão no cache,


o que pode ser interessante dependendo da estratégia utilizada pela aplicação. Se quisermos que a
entidade Movimentacao entre no cache de segundo nível com o Infinispan basta anotá-la
@Cacheable .

// outros imports omitidos


import javax.persistence.Cacheable;

@Entity
@Cacheable
public class Movimentacao implements Serializable {
// código omitido
}

Precisamos ainda fazer uma configuração fora do persistence.xml para usar o Infinispan do
WildFly: adicionar a dependência do mesmo no arquivo MANIFEST.MF, que está na pasta
WebContent/META-INF. Assim, o Hibernate consegue encontrar as classes necessárias para o
funcionamento do cache de segundo nível.
Dependencies: ... org.infinispan

14.6 EXERCÍCIO: CONFIGURANDO E TESTANDO O INFINISPAN


1. Em nosso projeto web, vamos habilitar o cache de segundo nível para o Hibernate.

Abra o persistence.xml e adicione logo abaixo da declaração das classes:


<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>

Ainda no persistence.xml adicione a propriedade abaixo dentro do elemento properties:

<property name="hibernate.cache.use_second_level_cache" value="true" />

Vamos informar ao Hibernate que queremos que os objetos da classe Conta sejam mantidos no
cache de segundo nível. Para isso vamos anotar a classe com @Cacheable .
@Entity
@Cacheable
public class Conta {
//atributos e métodos omitidos
}

Como estamos usando o WildFly, ao invés de colocar um jar em nosso projeto, podemos apenas
dizer para o WildFly que queremos usar o Infinispan interno do servidor.

168 14.6 EXERCÍCIO: CONFIGURANDO E TESTANDO O INFINISPAN


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Isso deve ser feito adicionando a dependência do Infinispan no arquivo MANIFEST.MF, que está
na pasta WebContent/META-INF. (No nosso projeto este arquivo já está com a dependência
configurada)

Abra o arquivo MANIFEST.MF e verifique se nele existe a seguinte linha:

Dependencies: ... org.infinispan

Com isso, o infinispan deve aparecer entre as bibliotecas do servidor.

Reinicie o servidor e acesse o endereço http://localhost:8080/fj25-financas-


web/cache.xhtml .

Teste novamente o formulário digitando o id de uma conta que exista no banco de dados. Busque
algumas vezes a mesma conta. Veja a saída no console. Dessa vez, quantos selects foram feitos?

2. Qual é a diferença entre o cache de primeiro nível e o de segundo nível?


Configure para não usar o cache de segundo nível (comentando a annotation @Cacheable da
classe Conta ), rode e verifique a diferença.
Quando será que o cache é invalidado? Tente alterar algum dado da Conta entre as duas
pesquisas.

14.7 CACHE DE COLLECTIONS


Quando habilitamos o cache de segundo nível em nossa aplicação, podemos cachear também os
resultados das associações de nossas entidades. Com isso, os relacionamentos ToMany podem ter seus
resultados cacheados, e quando for necessário buscá-los, em nosso caso na invocação de
conta.getMovimentacoes , a query só seria disparada uma única vez. Nas vezes sucessivas ela não seria
mais disparada.

Para habilitarmos o cache de collections, basta adicionarmos a anotação @Cache sobre a


Collection da relação dos objetos e definirmos a estratégia de cache da mesma forma que fizemos no
cache de segundo nível. Diferente da anotação @Cacheble, que faz parte da especificação JPA, @Cache é
específica do Hibernate e sua utilização pode impedir a portabilidade de sua aplicação para outros
providers.

Dessa forma, para cachear a lista de movimentações da Conta podemos fazer:

@Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
@OneToMany(mappedBy = "conta")
private List<Movimentacao> movimentacoes = new ArrayList<Movimentacao>();

14.7 CACHE DE COLLECTIONS 169


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

INFINISPAN E CACHECONCURRENCYSTRATEGY

O Infinispan suporta apenas como CacheConcurrencyStrategy os tipos READ_ONLY e


TRANSACTIONAL .

Há alguns detalhes para a invalidação do cache de uma coleção. Você precisa remover a entidade da
coleção cacheada, caso contrário essa coleção não será invalidada. Veja mais em:

http://planet.jboss.org/post/collection_caching_in_the_hibernate_second_level_cache

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

14.8 EXERCÍCIOS: UTILIZANDO CACHE PARA AS COLLECTIONS


1. Vamos dar a possibilidade do usuário saber quantas movimentações já foram realizadas numa
determinada conta. Para isso, simplesmente vamos invocar o getMovimentacoes da conta
carregada a partir do EntityManager . Como o comportamento das coleções por padrão é lazy, uma
segunda query será disparada para recuperar essas movimentações. Vamos então alterar a classe
QtdeMovimentacoesDaContaBean para adicionar esse comportamento.

Injete o DAO de contas:

@Inject
private ContaDao dao;

Altere o método lista .

public void lista() {


conta = dao.busca(conta.getId());
quantidade = conta.getMovimentacoes().size();
}

170 14.8 EXERCÍCIOS: UTILIZANDO CACHE PARA AS COLLECTIONS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

2. Agora, acesse a URL http://localhost:8080/fj25-financas-


web/qtdeMovimentacoesDaConta.xhtml .
3. Pesquise a quantidade de movimentações de Conta e observe no console quantas queries foram
feitas.
4. Clique novamente no botão para refazer a mesma pesquisa (utilizando a mesma Conta ). Repara
que a query para buscar as movimentações foi novamente realizada. Será que é necessária? São as
mesmas informações de novo.

5. Vamos melhorar isso adicionando cache na lista de movimentações que temos dentro da classe
Conta . Como esta é uma anotação específica do Hibernate, precisamos colocá-lo no classpath.

Como estamos usando o WildFly, ao invés de colocar um jar em nosso projeto, podemos apenas
dizer para o WildFly que queremos usar o Hibernate interno do servidor. Assim evitamos conflitos
com possíveis versões diferentes do hibernate sendo carregadas em nosso projeto.

Isso deve ser feito adicionando a dependência do Hibernate no arquivo MANIFEST.MF, que está na
pasta WebContent/META-INF. (No nosso projeto este arquivo já está com a dependência
configurada)

Abra o arquivo MANIFEST.MF e verifique se nele existe a seguinte linha:


Dependencies: ... org.hibernate

Com isso o hibernate deve aparecer entre as bibliotecas do servidor.

Abra a classe Conta e anote o atributo movimentacoes com a anotação @Cache


@Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
@OneToMany(mappedBy = "conta")
private List<Movimentacao> movimentacoes = new ArrayList<Movimentacao>();

Vamos anotar também a classe Movimentacao para informar que os objetos deste tipo devem ser
cacheados. Para isso, simplesmente adicione a anotação @Cacheable da mesma forma que
fizemos na Conta .
Reinicie o servidor.
Acesse novamente a página para verificar a quantidade de movimentações de uma Conta e
pesquise várias vezes pela mesma Conta . Observe quantas queries são realizadas. Mudou algo?
6. Quando será que o cache é invalidado? Adicione mais uma movimentação na conta que você está
pesquisando.

14.9 INVALIDADAÇÃO PROGRAMÁTICA DO CACHE -


JAVAX.PERSISTENCE.CACHE
Na versão 2 do JPA entrou uma api para interagir com o cache programaticamente, um recurso que
antes era especifico do provider de persistência. A EntityManagerFactory possui um método para

14.9 INVALIDADAÇÃO PROGRAMÁTICA DO CACHE - JAVAX.PERSISTENCE.CACHE 171


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

pegar a referência do cache do segundo nível:

javax.persistence.Cache cache = entityManagerFactory.getCache();

A api é bem simples e possui apenas métodos para tirar (evict) objetos do cache. Por exemplo, para
invalidar uma conta com o ID 3:
cache.evict(Conta.class, 3);

Também podemos tirar todas as contas:

cache.evict(Conta.class);

ou invalidar o cache inteiro:


cache.evictAll();

Vale lembrar que o ideal é sempre deixar o gerenciamento para o seu provedor de cache. Quanto
menos você precisar fazer controle fino da invalidação, melhor. Para isso, uma boa configuração do
tempo de vida do objeto e política de invalidação (como o LRU) podem ajudar bastante.

14.10 EXERCÍCIO OPCIONAL: INVALIDAR PROGRAMATICAMENTE


1. Para invalidarmos nosso cache programaticamente, precisamos ter uma referência para o
javax.persistence.Cache gerenciado pela EntityManagerFactory .

Podemos tornar o Cache disponível para a nossa aplicação criando um Producer do CDI, similar
ao producer de EntityManager que criamos anteriormente.

Crie a classe CacheProducer no pacote br.com.caelum.financas.util :


@ApplicationScoped
public class CacheProducer {

@PersistenceUnit
private EntityManagerFactory factory;

@Produces @ApplicationScoped
public Cache getCache() {
return factory.getCache();
}
}

2. Na classe CacheInvalidaBean injete o Cache


@Inject
private Cache cache;

Altere o método invalidar para tirar uma entidade do cache do segundo nível:
public void invalidar() {
cache.evict(Conta.class, id);
}

172 14.10 EXERCÍCIO OPCIONAL: INVALIDAR PROGRAMATICAMENTE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

3. Vamos executar um select de uma conta, depois invalidar o cache e em seguida fazer um select para a
mesma conta.

Acesse a URL para cachear um conta de uma id existente: http://localhost:8080/fj25-


financas-web/cache.xhtml .
Agora invalide a mesma conta pelo formulário: http://localhost:8080/fj25-financas-
web/cacheInvalida.xhtml .

Torne a acessar a URL e tente novamente com a mesma id: http://localhost:8080/fj25-


financas-web/cache.xhtml

Quantos selects deveriam aparecer?

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

14.11 CACHE DE QUERIES


Uma outra otimização extremamente importante que podemos realizar em nossa aplicação é a Cache
de queries. O intuito é que indiquemos quais queries desejamos cachear. Quando essa query for
executada, será armazenado qual é a query que foi executada (com seus parâmetros) e quais foram os
ids dos objetos retornados pela query.

Dessa forma, se executássemos a query from Conta conta where conta.titular like
:pTitular onde o parâmetro pTitular fosse "João" e ela retornasse 3 objetos, com ids 1, 7 e 10,
seria armazenado no cache a query executada, o parâmetro passado e também os ids dos objetos
retornados.

Esse cache sempre é invalidado quando acontece alguma operação em alguma das entidades
relacionadas, como um insert, update ou delete, portanto indicado para queries que envolvem tabelas
com muito mais leitura do que escrita.

14.11 CACHE DE QUERIES 173


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Habilitamos o cache de queries através da invocação do método setHint , no objeto Query que
utilizamos dentro dos nossos DAOs para realizar as pesquisas. O método setHint recebe dois
parâmetros, o primeiro é uma chave que indica que estamos configurando o cache da consulta e o
segundo indicando que realizaremos o cache. Com isso, podemos aplicar o cache de query como no
código abaixo:
public List<Movimentacao> listaMovimentacoesPorValorETipo(
BigDecimal valor, TipoMovimentacao tipo) {
String jpql = "select m from Movimentacao m where m.valor <= :valor "
+" and m.tipoMovimentacao = :tipo";
Query query = this.em.createQuery(jpql);
query.setParameter("valor", valor);
query.setParameter("tipo", tipo);

query.setHint("org.hibernate.cacheable", "true");

return query.getResultList();
}

14.12 EXERCÍCIOS: ADICIONANDO CACHE EM CONSULTAS


1. Precisamos habilitar o cache de queries do Hibernate. Para isso adicione no persistence.xml do
seu projeto dentro da Tag <properties> a configuração para isso:
<property name="hibernate.cache.use_query_cache" value="true"/>

2. Abra o MovimentacaoDao e altere o método listaPorValorETipo para definir o Hint para que
a consulta seja cacheada. Para isso, adicione a chamada
query.setHint("org.hibernate.cacheable", "true"); .

Dessa forma, o método ficará da seguinte forma:

public List<Movimentacao> listaPorValorETipo(


BigDecimal valor, TipoMovimentacao tipo) {
String jpql = "select m from Movimentacao m "
+ " where m.valor <= :valor and m.tipoMovimentacao = :tipo";
Query query = this.manager.createQuery(jpql);
query.setParameter("valor", valor);
query.setParameter("tipo", tipo);

query.setHint("org.hibernate.cacheable", "true");

return query.getResultList();
}

3. Reinicie o servidor e acesse a URL http://localhost:8080/fj25-financas-


web/movimentacoesPorValorETipo.xhtml . Faça a query pela primeira vez e verifique no console se
a query foi realizada.
4. Clique com o botão direito no console e clique em Clear para limpar o console.
5. Faça novamente a consulta com os mesmos parâmetros e olhe no console para ver se a query foi
realizada novamente. O que aconteceu?

174 14.12 EXERCÍCIOS: ADICIONANDO CACHE EM CONSULTAS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

6. Insira uma nova movimentação, e refaça a query. Ela foi disparada novamente? Por quê?

14.13 TRABALHANDO COM CACHE FORA DE UM SERVIDOR DE


APLICAÇÃO
Uma das bibliotecas mais conhecidas para se trabalhar com esse tipo de cache é o EhCache, um
provider de cache para o Hibernate. Para ser habilitado, basta adicionar duas propriedades ao arquivo
persistence.xml :

<property name="hibernate.cache.use_second_level_cache"
value="true"/>
<property name="hibernate.cache.provider_class"
value="org.hibernate.cache.EhCacheProvider"/>

A primeira linha diz que queremos habilitar o cache de segundo nível, que por padrão vem
desabilitado, e a segunda linha indica qual é o provider que será utilizado, que no nosso caso é o
EhCache.

Mas uma das questões mais difíceis ao se trabalhar com Cache de segundo nível é determinar
quando o dado que está no cache deve ser invalidado. Para isso, as bibliotecas de Cache precisam saber
qual estratégia utilizar para invalidar o Cache. Basicamente existem quatro formas: READ_ONLY ,
NONSTRICT_READ_WRITE , READ_WRITE e TRANSACTIONAL .

A estratégia mais restritiva é a READ_ONLY , indicada para entidades que são apenas lidas e nunca
modificadas. Este é o modo mais simples, pois nunca irá se preocupar com sincronização e locks para os
objetos, já que não há escritas.

NONSTRICT_READ_WRITE é a mais comum, indicada para objetos que são raramente atualizados e
que têm probabilidade de atualizações simultâneas muito baixa. Caso muitas atualizações ocorram
"simultaneamente" pode ser que a última versão comitada no banco não seja a mesma que acabou
ficando no seu cache.

READ_WRITE é mais abrangente e suporta escrita mais frequente, garantindo que a última versão do
cache é a mesma do banco.

TRANSACTIONAL é totalmente transacional, com suporte a caches distribuídos. O Infinispan do


WildFly, por exemplo, oferece esse suporte.

Você pode ainda configurar detalhes sobre o cache, utilizando o ehcache.xml no classpath do seu
projeto:

<ehcache>

<diskStore path="java.io.tmpdir"/>

<defaultCache
maxElementsInMemory="1000"

14.13 TRABALHANDO COM CACHE FORA DE UM SERVIDOR DE APLICAÇÃO 175


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

eternal="true"
overflowToDisk="false"
/>

<cache name="br.com.caelum.financas.modelo.Conta"
maxElementsInMemory="2000"
eternal="true"
overflowToDisk="false"
/>

</ehcache>

Existem opções para você configurar a política de cache, timeout e outros, através do arquivo de
configuração ehcache.xml . Veja mais no site do projeto:

http://ehcache.org

Caso você não defina nada no ehcache.xml por padrão serão criados caches que cabem até 10 mil
elementos, e esses elementos serão invalidados após dois minutos de existência. Esses defaults podem
mudar de versão em versão do ehcache.

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

14.14 EXERCÍCIO OPCIONAL: CONFIGURANDO E TESTANDO O


EHCACHE
1. Vamos testar o EhCache com nosso projeto standalone fj25-financas.

Primeiramente para conseguirmos trabalhar com o cache de segundo nível num ambiente
standalone (sem servidor de aplicação), temos que habilitar um provider. No curso utilizaremos o
EhCache , mas existem outros disponíveis que podem ser utilizados.

Abra o arquivo persistence.xml e dentro da Tag <properties> adicione duas novas


propriedades para habilitar o EhCache:
<property name="hibernate.cache.use_second_level_cache"
value="true"/>
<property name="hibernate.cache.region.factory_class"
value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"/>

176 14.14 EXERCÍCIO OPCIONAL: CONFIGURANDO E TESTANDO O EHCACHE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

O EhCache é uma biblioteca externa portanto precisamos trazer seu JAR para nossa aplicação.
Copie os JARs da pasta Atalho para arquivos dos cursos/25/ehcache para a pasta lib do projeto
fj25-financas e adicione no Build Path .

Vamos informar ao Hibernate que queremos que os objetos da classe Conta sejam mantidos no
cache de segundo nível. Para isso vamos anotar a classe dizendo qual a estratégia de cache que
vamos aplicar aos objetos.

@Entity
@Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Conta {
//atributos e métodos omitidos
}

Crie uma classe chamada TesteCacheSegundoNivel no pacote


br.com.caelum.financas.teste com o seguinte conteúdo:

public class TesteCacheSegundoNivel {


public static void main(String[] args) {

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

primeiraEM.getTransaction().begin();
Conta primeiraConta = primeiraEM.find(Conta.class, 1);
primeiraEM.getTransaction().commit();

primeiraEM.close();

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


Conta segundaConta = segundaEM.find(Conta.class, 1);
segundaEM.close();

System.out.println("Titular da primeira conta: " +


primeiraConta.getTitular());

System.out.println("Titular da segunda conta: " +


segundaConta.getTitular());
}
}

Execute a classe e veja o resultado. Qual é a diferença entre o cache de primeiro nível e o de
segundo nível?
Configure para não usar o cache de segundo nível (comentando a annotation @Cache da classe
Conta ), rode, e verifique a diferença.
2. Quando será que o cache é invalidado? Tente alterar algum dado da Conta entre as duas pesquisas.

14.15 PARA SABER MAIS: ESTRATÉGIAS PARA INVALIDAR O CACHE


O EhCache por padrão armazena até 10000 objetos. Esse número pode ser configurado através do
ehcache.xml , onde podemos dizer a quantidade que desejamos. Mas, quando esse limite é atingido, o
que acontece?

14.15 PARA SABER MAIS: ESTRATÉGIAS PARA INVALIDAR O CACHE 177


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Basicamente, o EhCache possui 3 formas de decidir como ele deve tratar os objetos que já estavam
no cache para abrir espaço para novos objetos:

Less Frequently Used - LFU


Least Recently Used - LRU
First in First Out - FIFO

O padrão do EhCache é a utilização do algoritmo LRU, onde o objeto menos utilizado recentemente
é descartado do cache para dar espaço ao novo objeto que entrará no espaço do cache.

A estratégia LFU verifica qual o objeto que tem menor frequência de uso para poder ser descartado
do cache, enquanto o FIFO simplesmente remove o mais antigo.

14.16 VENDO NOSSA PERFORMANCE: HIBERNATE STATISTICS


Um dos pontos críticos de uma aplicação que trabalha com banco de dados é sabermos se estamos
utilizando recursos de forma adequada, se não estamos abrindo muitas conexões e deixando-as abertas
ocasionando um eventual connection leak, se não estamos fazendo muitas pesquisas repetidas, ou
inclusive se configuramos mal nossa estratégia de cache. Todos esses atributos são difíceis de medir
simplesmente observando a aplicação em execução.

Muitos dos problemas só podem ser descobertos através de uma análise ou de uma auditoria sobre o
uso do banco de dados e também do Hibernate e suas ferramentas. Justamente para facilitar esse
trabalho, o Hibernate possui uma API que gera estatísticas de uso da aplicação enquanto ela está em
execução.

Por padrão as estatísticas não são colhidas, pois isso demanda mais recursos o que pode fazer com
que a aplicação tenha uma sensível perda de performance. No entanto, para habilitar as estatísticas, basta
adicionar uma nova propriedade no persistence.xml :

<property name="hibernate.generate_statistics" value="true"/>

Com isso conseguimos colher as estatísticas no momento que precisarmos. Para isso, basta
utilizarmos a EntityManager e invocarmos o método unwrap , que nos retornará um objeto interno
do Hibernate, que é a Session . Com esse objeto Session recuperado, podemos invocar o método
getSessionFactory que retornará a SessionFactory do hibernate, e então podemos invocar o
método getStatistics que retornará um objeto do tipo Statistics :
EntityManager em = // recuperando uma entitymanager
Session session = em.unwrap(Session.class);
Statistics stats = session.getSessionFactory().getStatistics();

A classe Statistics possui diversos métodos que podemos utilizar para recuperar informações
sobre o uso do Hibernate, como a quantidade de vezes que utilizamos o cache de segundo nível, quantas
vezes fizemos exclusões em uma determinada entidade, quantas queries foram cacheadas, qual a

178 14.16 VENDO NOSSA PERFORMANCE: HIBERNATE STATISTICS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

consulta que demora mais tempo para executar e assim por diante.

Há mais informações também neste post da Caelum: http://blog.caelum.com.br/cacando-seus-


gargalos-com-o-hibernate-statistics/

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

14.17 EXERCÍCIOS: VISUALIZANDO ESTATÍSTICAS DO HIBERNATE NA


APLICAÇÃO WEB
1. Abra o arquivo persistence.xml e dentro da Tag <properties> adicione a nova propriedade
para habilitar a geração das estatísticas.
<property name="hibernate.generate_statistics" value="true"/>

2. Abra a classe EstatisticasBean no pacote br.com.caelum.financas.mb .

Nela injete o EntityManager e crie um atributo chamado estatisticas, do tipo


org.hibernate.stat.Statistics :

@Inject
private EntityManager manager;

private Statistics estatisticas;

Lembre-se: precisamos criar os métodos getEstatisticas e setEstatisticas para que a view consiga
encontrar os dados para exibir.

3. Na mesma classe EstatisticasBean abra o método gera . Esse método é responsável por pegar
as estatísticas de uso do Hibernate e disponibilizar o objeto Statistics para ser exibido na interface.

Adicione no método gera :


Session session = this.manager.unwrap(Session.class);
this.estatisticas = session.getSessionFactory().getStatistics();

Cuidado: A classe Session vem do pacote org.hibernate.Session .

14.17 EXERCÍCIOS: VISUALIZANDO ESTATÍSTICAS DO HIBERNATE NA APLICAÇÃO WEB 179


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

4. Veja as estatísticas funcionando através da página: http://localhost:8080/fj25-financas-


web/estatisticas.xhtml .

Veja se algum número dentre os exibidos está estranho. Tente entender o por quê e como corrigir.

5. Qual seria um número bom para as conexões abertas e fechadas? Será que deveriam ser valores
parecidos ou diferentes? E os números de hits no cache de segundo nível? Um valor bom é um valor
alto? Ou baixo?

14.18 EXTENDED PERSISTENCE CONTEXT - UM ENTITYMANAGER


PARA VÁRIAS REQUISIÇÕES
Vimos no capítulo de pool de conexões que uma conexão será aberta quando o EntityManager
inicia uma transação. Antes disso o EntityManager pode ser usado, mas ele não possui uma conexão
associada. Depois que a transação for comitada, a conexão será devolvida para o pool, mas o
EntityManager continua ativo. Interessante aqui é que qualquer entidade Managed continua nesse
estado, com ou sem uma conexão/transação aberta. O estado Managed é relacionado com o
EntityManager . A transação é necessária para forçar a sincronização do Persistence Context com
o banco de dados.

Mas por que essa abordagem poderia ser útil? Imagina que no nosso sistema a criação de uma conta
é um processo que envolve não só uma página, e sim 3 páginas diferentes. Depois de cada ação das
páginas queremos gravar/atualizar os dados da conta no banco, ou seja, precisamos nos preocupar como
deixar a conta gerenciada entre requisições diferentes.

Na nossa aplicação implementamos o padrão Open EntityManager In View . Isso significa que
para cada requisição:

abre-se um EntityManager e uma transação


carrega-se a conta pelo ID e efetua-se uma alteração
consolida-se a transação e fecha-se o EntityManager

180 14.18 EXTENDED PERSISTENCE CONTEXT - UM ENTITYMANAGER PARA VÁRIAS REQUISIÇÕES


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Aplicando no código:

// em cada requisição
EntityManager manager = new JPAUtil().getEntityManager();
manager.getTransaction().begin();

Conta conta = manager.find(Conta.class, 1);


conta.setTitular("Maria");

manager.getTransaction().commit();
manager.close();

Sabendo que o mesmo EntityManager poderia ser aproveitado entre transações diferentes, seria
mais fácil abrir apenas um para todo o processo de criação/atualização da conta. Em cada requisição,
não queremos mais criar um novo EntityManager , mas abrir apenas uma nova transação e usar o
mesmo EntityManager para todo o processo. Queremos estender a vida do EntityManager
(Extended Persistence Context).

14.18 EXTENDED PERSISTENCE CONTEXT - UM ENTITYMANAGER PARA VÁRIAS REQUISIÇÕES 181


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

O próximo código mostra o conceito:

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

// inicio da primeira requisição


manager.getTransaction().begin();
Conta conta = manager.find(Conta.class, 1);
conta.setTitular("Maria");
manager.getTransaction().commit();
// fim da primeira requisição

// inicio da segunda requisição


manager.getTransaction().begin();
conta.setBanco("Itau");
manager.getTransaction().commit();
// fim da segunda requisição

// inicio da terceira requisição


manager.getTransaction().begin();
conta.setNumero("123-45");
manager.getTransaction().commit();
// fim da terceira requisição

manager.close();

Isso é apenas um exemplo simples para mostrar o conceito. Para usar um Persistence Context
Extended na nossa aplicação é necessário que nosso filtro saiba gerenciar EntityManager entre
requisições diferentes, muito mais complicado do que nos fizemos. Por isso, e outros motivos, é comum
usar para o gerenciamento do EntityManager e transações, um container mais sofisticado. Exemplos
de container são: JBoss Seam e EJB3 Container. Ambos são aptos para trabalhar com Extended
Persistence Context .

Voltando no nosso exemplo da aplicação, não queremos mais persistir os dados em cada requisição.
Na verdade, queremos persistir os dados quando finalizarmos o processo de alteração da conta ou seja
no fim do processo.

182 14.18 EXTENDED PERSISTENCE CONTEXT - UM ENTITYMANAGER PARA VÁRIAS REQUISIÇÕES


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Com apenas um EntityManager ativo fica simples de implementar. Vamos usar um Persistence
Context Extended , mas apenas na última requisição usarmos uma transação, conforme a figura
abaixo:

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

// inicio da primeira requisição


Conta conta = manager.find(Conta.class, 1);
conta.setTitular("Maria");
// fim da primeira requisição

// inicio da segunda requisição


conta.setBanco("Itau");
// fim da segunda requisição

// inicio da terceira requisição


manager.getTransaction().begin();
conta.setBanco("123-45");
manager.getTransaction().commit();
// fim da terceira requisição

manager.close();

14.19 LOCK OTIMISTA E PESSIMISTA


Fazer com que o EntityManager tenha vida curta reduz muito a probabilidade de problemas de
concorrência nos objetos. Mesmo assim, o problema não está eliminado. Vimos também que pode fazer
sentido usar um EntityManager com uma vida que dura mais do que uma requisição. Imagine que
dois usuários estão editando a mesma conta, ao mesmo tempo: qual dos dados vai valer? O que for salvo
primeiro, ou o que for salvo por último?

Há três opções para lidar com o problema. A primeira opção é não fazer nada e deixar sempre que o
último a ser salvo ganhe. As outras duas opções providas pelo Hibernate são: locks otimistas e locks

14.19 LOCK OTIMISTA E PESSIMISTA 183


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

pessimistas.

O lock pessimista parte do princípio de que frequentemente vai haver problema de concorrência.
Portanto, para se precaver do problema, o Hibernate pede uma trava (lock) do objeto ao banco de dados.
O método lock() do EntityManager serve justamente para pedir esta trava ao banco de dados. Veja
o exemplo abaixo que usa dois EntityMangager para a mesma conta:
EntityManager em1 = new JPAUtil().getEntityManager();
EntityManager em2 = new JPAUtil().getEntityManager();

em1.getTransaction().begin();
em2.getTransaction().begin();

Conta contaDoEM1 = em1.find(Conta.class, 1);


em1.lock(contaDoEM1,LockModeType.PESSIMISTIC_WRITE);
contaDoEM1.setTitular("Maria");

Conta contaDoEM2 = em2.find(Conta.class, 1);


em2.lock(contaDoEM2, LockModeType.PESSIMISTIC_WRITE); // Thread trava aqui

A grande desvantagem desta abordagem é que não escala, já que o registro/tabela fica travado e só
pode ser manipulado por um usuário. Além disso, o banco de dados utilizado precisa oferecer suporte ao
lock desejado (como select for update , por exemplo).

A segunda abordagem, o lock otimista, é a mais recomendada por não trazer problemas de
escalabilidade. O Hibernate parte do princípio de que não vai haver problema de concorrência algum,
portanto não faz nada para se precaver. SE ocorrer concorrência, o Hibernate apenas lança uma
exception para o segundo update, que estaria atropelando o update anterior.

Desta forma a aplicação pode tratar a exception lançada como desejar. Abordagens comuns são dizer
ao usuário para tentar novamente, ou mostrar na tela os dados da versão mais nova, para que ele mesmo
resolva os conflitos.

Fazer o Hibernate usar o lock otimista é muito simples. Basta haver um campo de versionamento na
entidade:
public class Conta {

// outros atributos...

@Version
private Integer versao;

//get e set para a versão.


// ...
}

Perceba o campo do tipo Integer anotado com @Version . A propriedade que guarda a versão
pode ser numérica ( Integer , Long ), ou um timestamp ( java.util.Date , java.util.Calendar ,
java.time.LocalDateTime ).

184 14.19 LOCK OTIMISTA E PESSIMISTA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Se a propriedade @Version existir na entidade, em qualquer update o Hibernate automaticamente


verifica se a versão do objeto a ser atualizada é antiga. Caso o objeto esteja com uma versão antiga (o que
quer dizer que alguém já o atualizou primeiro no banco, e esse número já esta maior do que o esperado),
o Hibernate lança StaleObjectStateException .

Caso o objeto já seja o mais atual, o Hibernate simplesmente incrementa a sua versão
automaticamente ao atualizá-lo.

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

14.20 EXERCÍCIOS OPCIONAIS: CONFIGURANDO E TESTANDO O


LOCK OTIMISTA
1. Vamos começar criando e anotando o atributo na classe Conta que vai guardar a versão.
//outras anotações aqui
@Entity
public class Conta {

// outros atributos...

@Version
private Integer versao; //getter e setter

// ...
}

2. Reinicie o WildFly e acesse o mysql para verificar o resultado. Deve ter sido criado uma coluna na
tabela Conta com o nome versao. Vamos deixar a versão das contas previamente criadas como zero.
Para isso, ainda no mysql, execute o seguinte comando:

update Conta set versao=0;

3. Agora vamos simular dois usuários tentando alterar a mesma conta ao mesmo tempo.

Para isso, acesse o sistema, vá no cadastro de contas e clique para alterar alguma conta.

14.20 EXERCÍCIOS OPCIONAIS: CONFIGURANDO E TESTANDO O LOCK OTIMISTA 185


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Com essa conta carregada, abra uma nova página, acesse o cadastro de contas e clique para alterar
a mesma conta.
Realize alguma alteração e clique no botão para confirmar a alteração.
Volte na primeira página e tente agora alterar a mesma conta carregada previamente, no caso com
uma versão anterior. Uma exception do tipo OptimisticLockException vai ser lançada
indicando que o mesmo objeto foi alterado em outra transação.
4. Volte o código do projeto pra como estava antes desse exercício, ou seja, retire o atributo criado na
classe Conta .

14.21 PARA SABER MAIS: OPERAÇÕES EM LOTE


Imagine que alguns dos usuários de nosso sistema de controle de movimentações sejam clientes de
um banco que sofreu uma fusão e mudou de nome. O resultado dessa mudança é que precisaríamos
alterar todas as contas associadas ao referido banco.

Poderíamos escrever uma query para buscar as contas, trazê-las para memória, iterar pela lista
alterando o atributo banco das instâncias e então comitar as alterações. O código seria algo como:

//entityManager já foi inicializado

String jpql = "select c from Conta c where c.banco = :banco";


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

List<Conta> contas = query.setParameter("banco", "Nome do banco").getResultList();

for (Conta conta : contas) {


conta.setBanco("Novo nome do banco");
}

A questão é: Precisamos mesmo fazer um select e trazer objetos para a memória do nosso
servidor, quando o que realmente desejamos fazer é um update ? Além disso, o first level cache pode
ficar grande demais, conforme o número de contas que você carregar para atualizá-las. Precisaria fazer
um controle fino para limpar esse cache através do entityManager.clear() .

Uma alternativa é fazer a mesma operação diretamente no banco, alterando todas as contas sem
precisar trazê-las para a memória. Trata-se de uma operação em lote ( bulk operation ) e para
executar precisamos utilizar um update por jpql:
//entityManager já foi inicializado

String jpql = "UPDATE Conta c SET c.banco = :novoNome WHERE c.banco = :antigoNome";

Query query = manager.createQuery(jpql);


query.setParameter("antigoNome", "Antigo nome do banco");
query.setParameter("novoNome", "Novo nome do banco");

int contasAlteradas = query.executeUpdate();

A abordagem anterior possui uma desvantagem. Toda operação em lote é traduzida para SQL pelo

186 14.21 PARA SABER MAIS: OPERAÇÕES EM LOTE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

provider da JPA e executado diretamente contra o banco de dados, de forma que o container
desconhecerá totalmente essa operação: O cache de primeiro e segundo nível não perceberão qualquer
alteração!

14.22 EXERCÍCIOS OPCIONAIS: OPERAÇÕES EM LOTE


1. Crie um novo método na classe ContaDao que implementa a operação em lote. Nesse método
vamos trocar o nome de um banco que adicionamos anteriormente:
public int trocaNomeDoBancoEmLote(String antigoNomeBanco, String novoNomeBanco) {

String jpql = "UPDATE Conta c SET c.banco = :novoNome "


+ "WHERE c.banco = :antigoNome";

Query query = manager .createQuery(jpql);


query.setParameter("antigoNome", antigoNomeBanco);
query.setParameter("novoNome", novoNomeBanco);

return query.executeUpdate();
}

2. Abra a classe OperacaoEmLoteBean e injete o DAO:


@Inject
private ContaDao contaDao;

3. Na classe já tem dois atributos para o nome antigo e nome novo do banco. Vamos passá-los para o
método do DAO que acabamos de criar. Procure o método atualizar na classe
OperacaoEmLoteBean . Esse método é responsável por chamar o método criado no ContaDao :

this.contasAlteradas =
contaDao.trocaNomeDoBancoEmLote(antigoNomeBanco, novoNomeBanco);

Repare que o resultado fica guardado na variável contasAlteradas que a tela vai mostrar.

4. Teste a funcionalidade através da página: http://localhost:8080/fj25-financas-


web/operacaoEmLote.xhtml .

Verifique depois na tela da Conta se o nome do banco realmente foi atualizado:

http://localhost:8080/fj25-financas-web/contas.xhtml

14.22 EXERCÍCIOS OPCIONAIS: OPERAÇÕES EM LOTE 187


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

14.23 AS NOVIDADES DO JPA 2.1 NO JAVA EE 7


O JPA 2 trouxe como principal novidade a API de criteria e o cache de segundo nível. No JPA 2.1,
que faz parte do Java EE 7, teremos o suporte a chamadas de stored procedures sem a necessidade de usar
código específico de nenhuma implementação:
//prepara a procedure
StoredProcedureQuery query =
EntityManager.createStoredProcedureQuery("processaMovimentacoes");

//registra os parametros da procedure


query.registerStoredProcedureParameter(1, BigDecimal.class, ParameterMode.OUT);//saída
query.registerStoredProcedureParameter(2, Integer.class, ParameterMode.IN); //entrada
query.setParameter(2, 001); //seta o parametro
query.execute();
BigDecimal soma = query.getOutputParameterValue(1)

Além disso, a integração com CDI melhorou. Podemos injetar qualquer dependência com CDI
dentro de EntityListeners
class ContaListener {

@Inject
private GeradorDeNumero gerador;

@PrePersist
public void geraNumero (Object o){
Conta conta = (Conta) o;
conta.setNumero(gerador.novoNumero());
}
}

Veja também outras novidades do Java EE 7 no blog da Caelum:


http://blog.caelum.com.br/novidades-javaee7-2/

188 14.23 AS NOVIDADES DO JPA 2.1 NO JAVA EE 7


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 15

VALIDAÇÃO E INTEGRIDADE DOS


MODELOS

"Procure detalhes pequenos e sem importância para achar alguma falha." -- Provérbio holandês

Ao término desse capítulo, você será capaz de:

Definir restrições no banco de dados pelo JPA


Compreender a Bean Validation;
Utilizar o Hibernate Validator para facilitar a validação dos objetos.

15.1 DEFINIÇÃO DE CONSTRAINTS NO BANCO PELO JPA


Qualquer aplicação desenvolvida deve se preocupar com a integridade e consistência dos dados
fornecidos pelos usuários. Para isso, diversas ações são tomadas, entre elas, o uso de chaves primárias e
estrangeiras no banco de dados.

Quando definimos o atributo que representa a chave primária com @Id , o banco de dados
automaticamente cuidará de sua unicidade garantindo que não haverá nenhum valor duplicado.

Há casos onde queremos definir outros atributos como únicos, não só a chave. Por exemplo, em
nossa classe Conta , pode fazer sentido declarar o atributo número também como único. A JPA possui
para isso a anotação @Column com qual podemos manipular a coluna associada com o atributo:

@Entity
//outras anotações
public class Conta {

@Id
@GeneratedValue
private Integer id;

@Column(unique=true)
private String numero;

//outros atributos e métodos

@Column não serve apenas para configurar a unicidade do valor, mas também para definir o nome
da coluna, o tamanho do campo, não permitir alterações e não aceitar valores nulos:
@Column(name="description", length="20", nullable=false, updatable=false)

15 VALIDAÇÃO E INTEGRIDADE DOS MODELOS 189


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

private String descricacao;

Como já aprendemos, qualquer tipo em Java é automaticamente mapeado para um tipo no banco.
Por exemplo, um String do mundo Java se torna um varchar(255) no MySQL. Através da anotação
@Column também é possível redefinir o tipo no banco:

@Column(columnDefinition="text")
private String descricao;

É preciso ter cuidado com essa configuração pois perdemos a portabilidade entre banco de dados
diferentes.

Em alguns casos, é preciso definir a unicidade baseada em várias colunas. Por exemplo, não faz
sentido ter apenas número como unique , mas o conjunto número e a agência. Para isso, o @Column
não serve já que é associado a apenas um atributo.

O JPA prevê a anotação @Table , que possui um atributo uniqueConstraints que recebe a
anotação @UniqueConstraint . Veja o exemplo:

@Entity
//outras anotações
@Table(uniqueConstraints= {@UniqueConstraint(columnNames={"agencia", "numero"})})
public class Conta {

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

15.2 EXERCÍCIOS: VALIDAÇÃO ATRAVÉS DE CONSTRAINTS


1. Para testar a geração dos constraints, é preciso recriar as tabelas no MySQL. Abra o
persistence.xml e altere o valor da propriedade hbm2ddl para create.

Ao subir, o Hibernate apagará e regerará as tabelas.

2. Na classe Conta , defina a restrição pela anotação @Table :

@Table(uniqueConstraints= {

190 15.2 EXERCÍCIOS: VALIDAÇÃO ATRAVÉS DE CONSTRAINTS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

@UniqueConstraint(columnNames={"agencia", "numero"})
})

3. Tente também a anotação @Column : deixe a coluna para o nome do banco com um tamanho de 20
caracteres e não nulo:
@Column(length=20, nullable=false)
private String banco;

4. Reinicie o servidor WildFly. Depois, abra um terminal e se conecte com o MySQL. Verifique se a
coluna banco foi gerada não nula:
mysql -u root
use fj25;
desc Conta;

5. Acesse o endereço http://localhost:8080/fj25-financas-web/contas.xhtml

Tente inserir duas contas com o mesmo número e agência. A segunda conta deve causar uma
exceção.

No console do WildFly, procure a primeira exceção lançada. Repare que foi o MySQL que validou e
gerou a exceção.

6. Para que as tabelas sejam mantidas quando reiniciarmos o servidor, abra o persistence.xml e
retorne o valor da propriedade hbm2ddl para update.

15.3 VALIDANDO OBJETOS


Na seção anterior vimos que podemos verificar os dados pelo banco de dados. O JPA ajuda na
definição das restrições e, caso alguma restrição seja violada, o driver receberá um exceção.

O banco de dados sempre deve proteger-se e verificar a integridade dos dados antes de qualquer
alteração. No entanto, faz parte do trabalho do desenvolvedor verificá-los antes de enviar uma alteração
para o banco. Isso geralmente é resolvido com a criação de rotinas de validação de dados.

O desenvolvimento dessa validação de dados pode ser demorado e muitas vezes acaba se tornando
de difícil manutenção com o passar do tempo.

15.4 A FORMA TRADICIONAL DE VALIDAÇÃO


Uma das formas de se fazer validação é pela adição de códigos na aplicação. Dessa forma,
poderíamos, por exemplo, antes de persistir uma conta no banco de dados, verificar se o seu titular
encontra-se ou não nulo. Algo como:

if (conta.getTitular() != null) {
entityManager.persist(conta);
}

15.3 VALIDANDO OBJETOS 191


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

No entanto, essa abordagem gera um problema, pois, sempre que precisarmos persistir uma Conta ,
devemos lembrar de fazer essa verificação em todos os lugares onde ela seja salva. Essa não é uma boa
abordagem. Estamos ferindo gravemente o princípio do encapsulamento que aprendemos quando
estudamos orientação a objetos, além de correr risco de esquecer de realizar essa validação.

Uma solução, então, seria adicionarmos essa validação dentro dos métodos adequados no nosso
DAO , dessa forma teríamos o comportamento encapsulado num único lugar e evitaríamos duplicidade
de código.

No entanto, sempre que tivermos essas validações corriqueiras precisaremos adicionar seu respectivo
código. Suponha que a entidade Conta possua 15 atributos que devem ser verificados antes de ser
persistida. Um if para cada um dos atributos? Se novos atributos fossem adicionados? Teríamos que
fazer diversos ifs , o que não é nada elegante.

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

15.5 A FORMA SIMPLES: BEAN VALIDATION E HIBERNATE


VALIDATOR
Com o intuito de tornar a validação dos objetos mais simples e mais elegante, foi criada a
especificação Bean Validation (JSR 303). A ideia é que apenas seja necessário indicar qual validação deve
ser feita e em qual propriedade.

Para isso, a especificação define um conjunto de anotações que podem ser colocadas em suas
entidades para que a validação seja realizada.

Por exemplo, para validarmos que o titular de uma Conta não será persistido com o valor nulo,
podemos utilizar a anotação @NotNull sobre o atributo titular :
@Entity
//outras anotações
public class Conta {

@NotNull
private String titular

// outros atributos e métodos

192 15.5 A FORMA SIMPLES: BEAN VALIDATION E HIBERNATE VALIDATOR


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Ao anotarmos o atributo titular , garantimos que ele não aceite valores nulos. A questão é:
quando a validação será executada e quem a realizará para nós?

Precisamos de uma implementação da especificação Bean Validation e uma delas atualmente é o


Hibernate Validator , que é distribuído como um projeto separado no próprio
http://hibernate.org . Felizmente, nosso servidor de aplicações, o WildFly, já tem essa
implementação incorporada.

15.6 UTILIZANDO O HIBERNATE VALIDATOR


Podemos testar que a validação do nosso modelo é feita iniciando o Hibernate Validator, processo
esse que, a princípio, é bastante parecido com o de construir um EntityManager .

O primeiro passo é conseguirmos uma fábrica de validadores. Para isso, utilizamos a classe
Validation invocando o método buildDefaultValidatorFactory() . Ele devolverá uma instância
de ValidatorFactory , que utilizamos para conseguir as instâncias dos validadores do Hibernate
Validator:

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();

Com o uso da fábrica, conseguimos validadores invocando o método getValidator , que retorna
uma instância de Validator :
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();

Poderíamos também isolar a criação do ValidatorFactory em uma classe similar à JPAUtil que
criamos anteriormente:

public class ValidatorUtil {


private static ValidatorFactory factory =
Validation.buildDefaultValidatorFactory();

public Validator getValidator() {


return factory.getValidator();
}
}

No nosso caso, como estamos dentro de um container Java EE, não é preciso inicializar o Bean
Validation programaticamente. Novamente a inversão de controle aplica e ajuda o desenvolvedor. A
única coisa que precisamos fazer é injetar o Validator :
@Inject
private Validator validator;

Com o validador pronto, podemos criar um objeto do tipo Conta para tentarmos validá-lo. Ao
final, o objeto que queremos validar é passado como parâmetro para o método validate do
Validator . Esse método retorna um Set contendo os erros de validação, que são do tipo

15.6 UTILIZANDO O HIBERNATE VALIDATOR 193


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

ConstraintViolation . Dessa forma, podemos exibir os erros de validação iterando pelo Set
retornado, da seguinte forma:
Conta conta = new Conta(); //criamos uma conta sem titular preenchido

Set<ConstraintViolation<Conta>> validate = validator.validate(conta); //validator já foi criado e


inicializado

for (ConstraintViolation<Conta> constraintViolation : validate) {


System.out.println(constraintViolation.getMessage());
}

15.7 EXERCÍCIOS: ADICIONANDO O HIBERNATE VALIDATOR AOS


MODELOS
1. Como o WildFly já possui a implementação do Hibernate Validator, precisamos apenas usar as
anotações. Vamos validar o titular da nossa Conta para não aceitar valores null .

Use a anotação @NotNull do pacote javax.validation.constraints em cima do atributo.


Também defina uma expressão para garantir que o titular comece com letra maiúscula:
@Pattern(regexp="[A-Z].*")

2. Abra a classe ValidacaoBean . Nela, injete o validador do Bean Validation


( javax.validation.Validator ):
@Inject
private Validator validator;

3. Na mesma classe ValidacaoBean , no método validar() , use o validador para receber os erros de
validação. O método geraMensagemJsf() já está preparado na classe ValidacaoBean .
Set<ConstraintViolation<Conta>> erros = validator.validate(conta);

for (ConstraintViolation<Conta> erro : erros) {


geraMensagemJsf(erro);
}

4. Reinicie o servidor e acesse o endereço http://localhost:8080/fj25-financas-


web/validacao.xhtml

Valide uma conta sem titular.

5. Também habilite a validação na página de inclusão de contas:

Abra o arquivo contas.xhtml e modifique a propriedade 'disabled' da tag <f:validateBean> de


true para false :

<f:validateBean disabled="false">

6. (Opcional) Também valide o atributo banco da conta. Ele não pode ser nulo e deve ter no mínimo 3
e no máximo 20 caracteres. Use a anotação @Size .

194 15.7 EXERCÍCIOS: ADICIONANDO O HIBERNATE VALIDATOR AOS MODELOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

15.8 OUTROS VALIDADORES


Uma das grandes vantagens da Bean Validation é o fato de ela possuir diversas validações já
especificadas com devidas anotações. Abaixo segue uma tabela com as anotações possíveis em ordem
alfabética da especificação Bean Validations:

Anotações do Bean Validations


@AssertFalse

Verifica que o elemento anotado possui valor falso


Pertence à especificação: Sim

@AssertTrue

Verifica que o elemento anotado possui valor verdadeiro


Pertence à especificação: Sim

@DecimalMax

Indica qual é o valor máximo para um valor com casas decimais


Pertence à especificação: Sim

@DecimalMin

Indica qual é o valor mínimo para um valor com casas decimais


Pertence à especificação: Sim

@Digits

Indica quantos dígitos possuem a parte inteira e a parte decimal do valor indicado

15.8 OUTROS VALIDADORES 195


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Pertence à especificação: Sim

@Future

Verifica que é uma data futura


Pertence à especificação: Sim

@Max

Indica qual é o valor máximo aceito por um atributo


Pertence à especificação: Sim

@Min

Indica que é o valor mínimo aceito por um atributo


Pertence à especificação: Sim

@NotNull

Verifica que o atributo não é nulo


Pertence à especificação: Sim

@Null

Indica que o valor do atributo deve ser nulo


Pertence à especificação: Sim

@Past

Verifica que uma data está no passado


Pertence à especificação: Sim

@Pattern

Verifica que o atributo segue uma determinada expressão regular


Pertence à especificação: Sim

@Size

Verifica se uma String ou Collection possui conteúdo dentro de um intervalo


Pertence à especificação: Sim

@Valid

Verifica se o atributo anotado está válido


Pertence à especificação: Sim

196 15.8 OUTROS VALIDADORES


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Há também anotações especificas do Hibernate Validator, que não vão funcionar em qualquer
implementação:

Anotações do Hibernate Validator


@Email

Verifica que o atributo anotado é um e-mail válido


Pertence à especificação: Não

@Length

Valida que uma String esteja entre um tamanho pré-determinado


Pertence à especificação: Não

@NotEmpty

Verifica que o valor não é nulo ou não é uma String vazia, sem fazer o trim
Pertence à especificação: Não

@NotBlank

Verifica que o valor não é nulo ou não é uma String vazia, fazendo o trim
Pertence à especificação: Não

@Range

Indica que o valor está dentro de um determinado intervalo


Pertence à especificação: Não

Quando anotamos com @NotNull , o titular da Conta ainda aceita valores em branco, ou seja, com
uma String vazia. Olhando a tabela dos validadores, notamos que existe a anotação @NotBlank , que
verifica se o valor está nulo e também se é uma String vazia. Dessa forma, podemos utilizá-la ao invés
da anotação @NotNull :
@Entity
public class Conta {

private Integer id;

@NotBlank
private String titular

// outros atributos
}

No entanto, a anotação @NotBlank não faz parte da especificação Bean Validation. Ela é apenas
uma conveniência que existe no Hibernate Validator. Isso significa que, se um dia precisarmos mudar de

15.8 OUTROS VALIDADORES 197


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

implementação, precisaremos alterar também a anotação do nosso modelo, algo que não precisamos nos
preocupar quando programamos voltado à especificação.

Como alternativa da especificação também podemos utilizar a anotação @Size para definir um
range de caracteres (min e max), por exemplo:

@NotNull @Size(min=3, max=20)


private String banco;

Uma outra validação interessante que podemos adicionar ao nosso modelo é a Movimentacao só
poder ter valores positivos. Podemos conseguir isso através da anotação @DecimalMin , passando uma
String contendo o valor mínimo aceitável para o valor.

Dessa forma, podemos utilizar a anotação como em:

@Entity
public class Movimentacao {

@Id
@GeneratedValue
private Integer id;

@DecimalMin("0.01")
private BigDecimal valor;

// outros atributos
}

15.9 ALTERANDO A MENSAGEM DE ERRO PADRÃO DAS VALIDAÇÕES


Quando uma validação falha, recebemos uma mensagem padrão do Hibernate Validator para cada
erro de validação. No entanto, nem sempre queremos utilizar a mensagem padrão, e gostaríamos de
utilizar a nossa própria mensagem.

Todas as anotações de validação recebem um atributo chamado message , no qual podemos passar
um valor contendo a mensagem que queremos que seja exibida. Dessa forma, poderíamos customizar a
mensagem de erro para a validação do valor mínimo da movimentação da seguinte forma:
@DecimalMin(value="0.01", message="O valor deve ser igual ou acima de 1 centavo")
private BigDecimal valor;

Agora, quando a validação falhar, a mensagem que definimos na anotação será a utilizada para
descrever o erro. O ponto negativo dessa abordagem é que sempre que utilizarmos essa validação
precisaremos sobrescrever a mensagem dentro da anotação, com isso a String ficará espalhada por
toda a nossa aplicação.

Podemos melhorar isolando as mensagens em um lugar único que o Hibernate Validator suporte e
encontre essas mensagens. Esse lugar é o arquivo ValidationMessages.properties que deve ficar
dentro do diretório src do seu projeto.

198 15.9 ALTERANDO A MENSAGEM DE ERRO PADRÃO DAS VALIDAÇÕES


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Dentro desse arquivo podemos sobrescrever as mensagens padrões do Hibernate Validator. Por
exemplo, podemos mudar a mensagem da validação do @DecimalMin , simplesmente adicionando a
seguinte linha no nosso novo properties :
javax.validation.constraints.DecimalMin.message = Deve ser maior ou igual a {value}

Do lado esquerdo adicionamos uma chave que indica qual é a mensagem que queremos alterar
enquanto que à direita informamos qual é o valor que desejamos alterar. No caso, usamos o valor do
parâmetro value dinamicamente na mensagem.

ONDE ENCONTRAR AS MENSAGENS PADRÕES?

Caso seja necessário conhecer todas as chaves possíveis para alterar as mensagens de erro de
validação, o arquivo contendo as mensagens padrões do Hibernate Validator pode ser encontrado
dentro do seu .jar no pacote org.hibernate.validator .

15.10 CRIANDO SEU PRÓPRIO VALIDADOR


Nos exemplos anteriores, poderíamos ter utilizado a validação @Pattern que verifica através de
uma expressão regular se um atributo contém um valor que segue um formato pré definido, para validar
o número da Conta (que deve possuir um hífen). Além disso, teríamos que verificar se a agência foi
informada.

A princípio, nossa regra de negócio diz que não é preciso informar a agência e o número da Conta ,
no entanto, quando uma delas for preenchida, ambas deverão ser informadas.

Não conseguimos fazer isso anotando os nossos atributos. No entanto, queremos utilizar o
Hibernate Validator para disparar a nossa validação. O quê podemos fazer a respeito?

Nesse caso, poderíamos criar o nosso próprio validador que o Hibernate Validator entende e
consegue executar para nós quando pedimos para ele executar as validações.

Criar um validador customizado demanda alguns passos. O primeiro é definirmos a nossa própria
anotação que marcará a classe indicando que ela sofrerá uma validação. No nosso caso, chamaremos a
nossa anotação de @NumeroEAgencia . Ao criarmos a anotação, precisamos indicar que ela será retida
em tempo de execução e que a anotação será colocada em classes:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface NumeroEAgencia {

A anotação deve ainda declarar os atributos message , groups e payload . O atributo message

15.10 CRIANDO SEU PRÓPRIO VALIDADOR 199


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

define a chave da mensagem que será utilizada para buscar uma no arquivo de mensagens do Hibernate
Validator. Já, o atributo groups define o nome do agrupamento para essa validação que o Hibernate
Validator gerenciará internamente. Por fim, o atributo payload , que é requerido pela especificação
mas o framework a ignora, serve para estender a validação com objetos customizados. Dessa forma
teremos:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface NumeroEAgencia {

String message() default


"{br.com.caelum.financas.validator.NumeroEAgencia.message}";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};


}

Por fim, precisamos anotar a nossa interface com a anotação @Constraint indicando em qual
classe estará o código que executará a validação. Podemos declarar a classe
NumeroEAgenciaValidator . Adicionando a anotação, teremos o seguinte código:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = NumeroEAgenciaValidator.class)
public @interface NumeroEAgencia {

String message() default


"{br.com.caelum.financas.validator.NumeroEAgencia.message}";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};


}

O próximo passo é criarmos a classe NumeroEAgenciaValidator que implemente a interface


ConstraintValidator . A interface precisa receber como tipagem a anotação que vamos utilizar e
também qual o tipo do objeto que vamos validar. Dessa forma, teremos:
public class NumeroEAgenciaValidator
implements ConstraintValidator<NumeroEAgencia, Conta>{

// vamos implementar métodos aqui

A interface ConstraintValidator declara dois métodos que precisamos implementar, que são o
initialize e o isValid .

O método initialize recebe como parâmetro a anotação que declaramos no objeto para ser
validado. Assim podemos recuperar algum parâmetro que especificamos na anotação. Já o método
isValid é onde adicionaremos a nossa validação e retornaremos um booleano indicando se a
validação foi feita com sucesso ou não.

200 15.10 CRIANDO SEU PRÓPRIO VALIDADOR


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public class NumeroEAgenciaValidator


implements ConstraintValidator<NumeroEAgencia, Conta> {

public void initialize(NumeroEAgencia anotacao) {


}

public boolean isValid(Conta conta, ConstraintValidatorContext ctx) {


return false;
}
}

Agora, precisamos implementar o método isValid . Vamos utilizar a Conta que é recebida como
parâmetro e caso ela seja nula, retornaremos true , pois esse não é o objetivo da nossa validação.

Após essa verificação, precisamos verificar se o valor da agencia e do numero da Conta estão
informados. Caso estejam, precisamos retornar true . Conseguimos fazer isso simplesmente através do
uso do operador XOR (ou exclusivo). Dessa forma, teremos a seguinte implementação para o método
isValid :

if (conta == null) {
return true;
}
return ! (conta.getAgencia() == null ^ conta.getNumero() == null);

Por fim, precisamos anotar a classe Conta com a anotação @NumeroEAgencia .

@NumeroEAgencia
@Entity
public class Conta {
// atributos e métodos
}

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

15.11 EXERCÍCIOS: CRIANDO UM VALIDADOR CUSTOMIZADO


1. Vamos criar o nosso validador para verificar se a Conta possui a agencia e o numero
preenchidos.

15.11 EXERCÍCIOS: CRIANDO UM VALIDADOR CUSTOMIZADO 201


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Crie a anotação NumeroEAgencia no pacote br.com.caelum.financas.validator e anote-a


como no código abaixo:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = NumeroEAgenciaValidator.class)
public @interface NumeroEAgencia {

Dentro da anotação, declare os atributos obrigatórios da especificação Bean Validation


( message , groups e payload ):
String message() default
"{br.com.caelum.financas.validator.NumeroEAgencia.message}";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

2. A chave e o valor da mensagem devem estar dentro de um arquivo


ValidationMessages.properties .

Crie o arquivo ValidationMessages.properties no diretório src da sua aplicação.


Adicione a chave da mensagem dentro do arquivo:
br.com.caelum.financas.validator.NumeroEAgencia.message = Agência e número devem ser informados

3. Vamos criar a classe que executará o código da nossa validação.

Crie a classe chamada NumeroEAgenciaValidator no pacote


br.com.caelum.financas.validator e faça-a implementar a interface
ConstraintValidator .

public class NumeroEAgenciaValidator


implements ConstraintValidator<NumeroEAgencia, Conta>{

// vamos implementar métodos aqui

Precisamos implementar os métodos da interface. Não vamos usar o initialize , portanto


vamos deixá-lo vazio. Toda a lógica estará dentro do método isValid :
public class NumeroEAgenciaValidator
implements ConstraintValidator<NumeroEAgencia, Conta>{

@Override
public void initialize(NumeroEAgencia anotacao) {
}

@Override
public boolean isValid(Conta conta, ConstraintValidatorContext ctx) {
if(conta == null) {
return true;
}

202 15.11 EXERCÍCIOS: CRIANDO UM VALIDADOR CUSTOMIZADO


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

boolean agenciaEhVazia =
(conta.getAgencia() == null ||
conta.getAgencia().trim().isEmpty());

boolean numeroEhVazio =
(conta.getNumero() == null ||
conta.getNumero().trim().isEmpty());

return !(agenciaEhVazia ^ numeroEhVazio);


}
}

4. Anote a classe Conta para informar que ela deve ser validada baseada em nossa nova anotação.
Basta usar a nova anotação @NumeroEAgencia no topo da classe.

5. Reinicie o servidor e acesse o endereço http://localhost:8080/fj25-financas-


web/validacao.xhtml

Para testar preencha apenas a agência e submeta o formulário.

Teste os outros casos nos quais nossa validação deveria verificar.

15.12 PARA SABER MAIS: AGRUPANDO ANOTAÇÕES DE VALIDAÇÃO


PARA EVITAR REPETIÇÃO
Se colocarmos @Length(min=5) no atributo titular da classe Conta e passarmos para um
validator uma instância de conta em que o titular está com o valor null o que acontecerá? A validação
@Length inclui a validação NotNull ? A resposta é não.

Cada validação deve ser especializada seguindo o princípio DRY ( Don't Repeat Yourself ). Para
aproveitarmos a implementação de anotações já existentes, podemos agrupar várias anotações de
validação em outra anotação de validação. A anotação abaixo valida se um campo String é não nulo,
possui no mínimo 5 caracteres dos quais o primeiro é uma letra maiúscula. Se um campo for anotado
com ela e qualquer uma das validações falhar ocorrerá um ou mais ConstraintViolation , com as
respectivas mensagens das anotações originais.
@NotNull
@Length(min=5)
@Pattern(regexp="[A-Z].*")
@Constraint(validatedBy={})
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface MaiusculaNaoNulaELonga {
Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

String message() default "{minha.msg}";


}

15.12 PARA SABER MAIS: AGRUPANDO ANOTAÇÕES DE VALIDAÇÃO PARA EVITAR REPETIÇÃO 203
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

15.13 VALIDAÇÃO EM CASCATA


Um outro ponto importante da nossa validação é quando tentamos salvar um objeto que pode
possuir objetos associados que também estejam inválidos. No nosso modelo, isso pode acontecer, por
exemplo quando tentamos salvar uma Movimentacao com uma Conta que esteja inválida. Por
padrão, as associações dos objetos não são validadas pelo Hibernate Validator.

Para que a validação das associações seja feita, precisamos anotar as associações com a anotação
@Valid . Nesse caso, se quiséssemos validar a Conta ao salvar uma movimentação, poderíamos anotar
o atributo conta .
@Entity
public class Movimentacao {

@Id
@GeneratedValue
private Integer id;

@Valid
@ManyToOne
private Conta conta;

// outros atributos

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

15.14 PARA SABER MAIS: NOVIDADES DO BEAN VALIDATION NO


JAVA EE 7
A nova versão do Bean Validation traz a validação de parametros de métodos e construtores, como
também do retorno de métodos. Isso pode ser muito útil principalmente para frameworks web action-
based onde recebemos os paramêtros pelos métodos, por exemplo:
public class ContaController {

public @Valid Conta buscaConta(@NotNull @Size(min=3) String titular) {

204 15.13 VALIDAÇÃO EM CASCATA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

//...
}
}

Outro ponto interessante é a integração do Bean Validation com CDI. Assim podemos aproveitar a
injeção de dependência com CDI nos _Validator_s do Bean Validation:
public class NumeroEAgenciaValidator
implements ConstraintValidator<NumeroEAgencia, Conta> {

@Inject
private ContaDao contaDao;

//método omitidos
}

15.15 INTEGRAÇÃO COM OUTRAS TECNOLOGIAS


Atualmente, é muito comum o uso de um framework como o Hibernate Validator para fazer a
validação dos objetos. E com esse aumento de uso, aumentou também o suporte dos frameworks web
para essas ferramentas. Muitos deles possuem suporte à Bean Validation transparentemente, como o
VRaptor 3, Spring e o JSF 2.

Além do suporte dos frameworks, tem crescido também a disponibilidade de validadores criados
pela comunidade compatíveis com Bean Validation. É o caso do Caelum Stella, que permite que
validemos nossas entidades usando @CPF e @CNPJ, por exemplo.

No curso FJ-26, onde focamos o uso do JSF 2, vemos com mais detalhes as maneiras de lidar com
validação usando esse framework MVC.

Outra integração importante que conseguimos fazer é com a JPA; dessa forma, modelos inválidos
não serão persistidos através dos métodos de persistência como persist ou merge .

Ao tentar persistir
um objeto cuja validação falhe, é lançada uma
ConstraintValidationException . Por meio desta exception, conseguimos recuperar a mesma lista de
erros que usamos nos exercícios iniciais. O método que precisamos invocar é
getConstraintViolations() , mas o ideal é que esses dados já tivessem sido validados antes.

Essa integração já vem habilitada por padrão. E para desabilitá-la, basta adicionar a seguinte linha no
persistence.xml :

<property name="javax.persistence.validation.mode" value="none"/>

15.15 INTEGRAÇÃO COM OUTRAS TECNOLOGIAS 205


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 16

MAIS MAPEAMENTOS

"Quando se diz às pessoas que a felicidade é uma questão simples, querem-nos sempre mal." -- Bertrand
Russel

Ao término desse capítulo, você será capaz de:

Mapear relacionamentos um para um com o Hibernate;


Criar Value Objects através da anotação @Embeddable

16.1 RELACIONAMENTOS UM PARA UM


Na modelagem da nossa aplicação, precisamos fazer com que toda a conta possua um gerente.
Podemos fazer isso através da criação de uma nova entidade na nossa aplicação chamada Gerente que
se relacionará com a Conta através de um relacionamento OneToOne .

A entidade Gerente armazenará o nome , telefone e endereço do gerente. Para isso, a


entidade terá o seguinte código:
@Entity
public class Gerente {

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

private String nome;


private String telefone;
private String rua;
private String cidade;
private String estado;

// getters e setters necessários


}

Precisamos agora dizer que a conta será cuidada por um gerente. Para isso, vamos adicionar o
atributo gerente na classe Conta e indicarmos a cardinalidade do relacionamento, que nesse caso
será @OneToOne :
@Entity
public class Conta {

// outros atributos

206 16 MAIS MAPEAMENTOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

@OneToOne
private Gerente gerente;

Com isso, o Hibernate vai criar um campo gerente_id na tabela Conta referenciando a tabela
Gerente .

Mas existe um problema, para que o gerente só possa cuidar de uma única Conta é preciso que o
atributo gerente_id na tabela Conta seja único, algo que a anotação @OneToOne não nos fornece
por padrão. Para conseguirmos esse comportamento precisamos anotar a classe Conta com @Table e
indicar no atributo uniqueConstraints que queremos que o campo gerente_id tenha uma chave
única:
@Entity
@Table(uniqueConstraints= {@UniqueConstraint(columnNames="gerente_id")})
public class Conta {
// resto do código
}

Essa configuração também pode ser feita por meio da anotação @JoinColumn :

@Entity
//@Table(uniqueConstraints= {@UniqueConstraint(columnNames="gerente_id")})
public class Conta {

@OneToOne
@JoinColumn(unique=true)
private Gerente gerente;

// restante do código
}

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

16.2 EXERCÍCIOS: MAPEANDO RELACIONAMENTOS UM PARA UM


1. Precisamos mapear o relacionamento entre a conta e um gerente.

16.2 EXERCÍCIOS: MAPEANDO RELACIONAMENTOS UM PARA UM 207


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Abra a classe Gerente e deixe-a da seguinte maneira:

@Entity
public class Gerente {

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

private String nome;


private String telefone;

//dados do endereço do gerente


private String rua;
private String cidade;
private String estado;

// getters e setters
}

Adicione um atributo gerente na classe Conta e mapeie com @OneToOne :


@Entity
public class Conta {

// outros atributos

@OneToOne
private Gerente gerente;

// getters e setters
}

Como o Hibernate não deixa o relacionamento @OneToOne como único, vamos criar uma
restrição unique através da anotação @JoinColumn :
@Entity
public class Conta {

// outros atributos

@OneToOne
@JoinColumn(unique=true)
private Gerente gerente;

// restante do código

2. Reinicie o servidor WildFly e verifique no banco se a tabela conta foi modificada.

16.3 O PATTERN VALUE OBJECT DO DOMAIN DRIVEN DESIGN


Basicamente entidades são objetos que precisam ser unicamente identificados, não importa seu
valor, exemplo, "Quero a Conta com número 12345!". Nesta frase não faz diferença quanto tem de saldo
na conta, ou qual a data de abertura, é uma busca de uma conta exata, não importando o seu valor.

208 16.3 O PATTERN VALUE OBJECT DO DOMAIN DRIVEN DESIGN


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Mas nem todo objeto funciona dessa maneira, existem objetos que neste ponto tem um
comportamento contrário, ao invés de buscar pela sua identidade, a busca é feita pelo seu valor. "Quero
um objeto que represente a cor azul!". Qualquer objeto que o valor represente a cor azul caberá na busca,
pois o que importa é seu valor. Este tipo de objeto que apenas serve para representar um ou mais valores
relativos ao domínio é chamado de Value Object.

Como a função dos Value Objects é apenas representar valores, dois objetos com as mesmas
informações, são mutualmente intercambiáveis, onde serve um, serve o outro. Para o sistema os dois são
o mesmo, eles não precisam ser unicamente identificáveis, por isso eles não precisam implementar um
sistema de identificação. Isso facilita o design dessas classes.

A falta de identidade e a possibilidade de troca de objetos traz outra vantagem em relação ao uso de
memória, os objetos do tipo Value Object são facilmente instanciados. Ao surgir a necessidade daquele
valor basta criar o objeto, porém eles também são descartáveis, quando ele não for mais necessário e
perder a referência, o garbage collector se encarregará dele, isso não atrapalha o ciclo de vida dele, afinal
ele não tem.

16.4 IMPLEMENTANDO VALUE OBJECTS COM EMBEDDABLE


O gerente possui endereço onde devemos informar os dados da rua, cidade e estado. No entanto,
outras entidades na nossa aplicação também podem precisar dessa mesma informação. O que acontece é
que acabamos espalhando as 3 propriedades por todo o nosso código. Podemos isolar esse código em
uma classe Endereco que contenha esses atributos, mas é importante para nós que não haja um
relacionamento, ou seja, não queremos uma nova tabela para armazenar os endereços, e sim que os
mesmos sejam adicionados na tabela onde o dado deve estar, por exemplo Gerente .

Como não precisamos de uma nova tabela e muito menos de relacionamento, essa classe Endereco
não deve ter um id , e sim apenas as propriedades:
public class Endereco {

private String rua;


private String cidade;
private String estado;

// getters e setters
}

Por fim, precisamos dizer que a classe Endereco não é uma nova Entidade e sim é um objeto que
poderá ser "embutido" em outros objetos. Para isso, anotamos a classe Endereco com @Embeddable :
@Embeddable
public class Endereco {

private String rua;


private String cidade;
private String estado;

16.4 IMPLEMENTANDO VALUE OBJECTS COM EMBEDDABLE 209


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

// getters e setters
}

Agora podemos remover os 3 atributos que representam o endereço da classe Gerente e substituí-
la por Endereco , juntamente com a anotação @Embedded :
public class Gerente {

// outros atributos
@Embedded
private Endereco endereco;

// getters e setters
}

PARADIGMA ORM E OS EMBEDDABLES

A criação de Embeddables é um dos recursos interessantes para atingirmos uma solução bem
orientada a objeto, sem que a modelagem do banco de dados sofra com a modelagem de nossas
classes. Confira mais detalhes no post: http://blog.caelum.com.br/adequar-o-banco-as-entidades-
ou-o-contrario/

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

16.5 EXERCÍCIOS: UTILIZANDO O EMBEDDABLE EM ATRIBUTOS


1. Vamos isolar o endereço em um Value Object. Para isso, crie uma classe chamada Endereco no
pacote br.com.caelum.financas.modelo com o seguinte código:

@Embeddable
public class Endereco {

private String rua;


private String cidade;
private String estado;

// getters e setters
}

210 16.5 EXERCÍCIOS: UTILIZANDO O EMBEDDABLE EM ATRIBUTOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

2. Na classe Gerente remova os três atributos referentes ao endereço e troque por um atributo da
classe Endereco :
@Entity
public class Gerente {

// outros atributos
@Embedded
private Endereco endereco = new Endereco();

// getters e setters
}

3. Vamos criar uma nova classe DAO de gerentes para encapsular o acesso aos dados de Gerente. Para
isso, crie a classe GerenteDao no pacote br.com.caelum.financas.dao .

Adicione a anotação @Stateless acima da classe. Também precisaremos de um


EntityManager , portanto injete-o na classe.

@Stateless
public class GerenteDao {

@Inject
private EntityManager manager;

* Por fim, crie os métodos `adiciona`, `lista`, `remove`, `altera` e `busca`


conforme fizemos anteriormente nas demais classes DAO.

``` java
@Stateless
public class GerenteDao {

@Inject
private EntityManager manager;

public void adiciona(Gerente gerente) {


this.manager.joinTransaction();
this.manager.persist(gerente);
}

//Demais métodos omitidos


}
```

1. A classe GerentesBean será utilizada pelo JSF para a tela de adição e listagem de novos gerentes.
Vamos implementar as funcionalidades para que a tela funcione corretamente:

Abra a classe GerentesBean . Precisamos injetar um GerenteDao para trabalharmos com o


banco de dados. Precisamos também criar os atributos necessários para exibir a lista de gerentes já
cadastrados na tela e para pegar os dados do formulário e adicionar um novo gerente.
public class GerentesBean implements Serializable{

@Inject
private GerenteDao gerenteDao = new GerenteDao();

16.5 EXERCÍCIOS: UTILIZANDO O EMBEDDABLE EM ATRIBUTOS 211


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

private List<Gerente> gerentes;

private Gerente gerente = new Gerente(); //implementar o getter e setter


}

Crie o método getGerentes() para recuperarmos a lista de gerentes cadastrados no banco que
será exibida na tela.
public List<Gerente> getGerentes() {

if(this.gerentes == null){
this.gerentes = gerenteDao.lista();
}

return gerentes;
}

Para finalizar, crie os métodos grava e remove conforme fizemos anteriormente para os outros
managedBeans .

2. Agora precisamos fazer com que as novas contas adicionadas possam ser associadas a um gerente
existente. Vamos habilitar os campos na tela de contas para que essa associação seja possível:

Abra o arquivo contas.xhtml e procure pelo seguinte trecho de código:

<h:outputText value="Gerente" rendered="false"/>


<h:selectOneMenu id="gerente" value="#{contasBean.gerenteId}" rendered="false">

Altere o valor a propriedade rendered para true em ambas as tags. O código ficará assim:
<h:outputText value="Gerente" rendered="true"/>
<h:selectOneMenu id="gerente" value="#{contasBean.gerenteId}" rendered="true">

A lista de contas também precisa exibir o gerente de cada uma das contas, caso ele exista. Para isso
vamos habilitar a exibição dessa coluna. Procure pelo seguinte trecho de código:

<h:column rendered="false">
<f:facet name="header"><h:outputText value="Gerente"/></f:facet>
#{conta.gerente.nome}
</h:column>

Altere o valor a propriedade rendered para true. O código ficará assim:

<h:column rendered="true">
<f:facet name="header"><h:outputText value="Gerente"/></f:facet>
#{conta.gerente.nome}
</h:column>

3. Para finalizar, vamos alterar a classe ContasBean para que ela associe o gerente selecionado na tela
à nova conta antes de adicioná-la ao banco de dados.

Abra a classe ContasBean . Adicione um atributo gerenteId do tipo Integer , para


capturarmos o id do gerente selecionado na tela, e injete um GerenteDao , que utilizaremos para
buscar o gerente a ser associado:

212 16.5 EXERCÍCIOS: UTILIZANDO O EMBEDDABLE EM ATRIBUTOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

@Inject
private GerenteDao gerenteDao;

private Integer gerenteId; //implementar o getter e setter

Altere o método grava para, caso um gerente tenha sido escolhido na tela, buscá-lo no banco de
dados através do atributo gerenteId e associá-lo à nova conta.
public void grava() {

if(gerenteId != null){
Gerente gerenteRelacionado = gerenteDao.busca(gerenteId);
this.conta.setGerente(gerenteRelacionado);
}
//restante do código existente omitido ...
}

4. Reinicie a aplicação e acesse http://localhost:8080/fj25-financas-web/gerentes.xhtml .


Teste a adição, alteração e remoção de novos gerentes.

5. Com pelo menos um gerente criado, acesse http://localhost:8080/fj25-financas-


web/contas.xhtml . Teste a adição de uma nova conta associada a um gerente e observe a exibição
do nome do gerente na lista de contas.

16.6 PARA SABER MAIS: DETALHES SOBRE O EMBEDDABLE


Quando precisarmos utilizar um Embeddable mais de uma vez na mesma classe teremos um
problema. Quanto o provider for gerar a DDL para criação do banco, ele o fará de forma que as colunas
relativas aos atributos do Embeddable sejam criadas duas vezes. Para resolver o problema podemos
utilizar o @AttributeOverrides:
public class Gerente {
// outros atributos
@Embedded
private Endereco endereco = new Endereco();

@Embedded
@AttributeOverrides({
@AttributeOverride(name="rua", column=@Column(name="rua_alternativa")),
@AttributeOverride(name="cidade", column=@Column(name="cidade_alternativa"))
@AttributeOverride(name="estado", column=@Column(name="estado_alternativo"))
})
private Endereco enderecoAlternativo = new Endereco();
//...
}

A abordagem acima pode causar transtorno quando a quantidade de atributos Embbeded na mesma
classe for muito grande ou a quantidade de atributos do Embeddable for grande. Outra opção para
diferenciar o nome das colunas do Embeddable é customizar a estratégia de nomenclatura usada pelo
Hibernate. Para isso podemos colocar mais uma configuração no persistence:

<property name="hibernate.ejb.naming_strategy"
value="org.hibernate.cfg.DefaultComponentSafeNamingStrategy"/>

16.6 PARA SABER MAIS: DETALHES SOBRE O EMBEDDABLE 213


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

16.7 PARA SABER MAIS: MAPEANDO CHAVES COMPOSTAS


Muitos bancos de dados legados possuem chaves primárias compostas. Para representá-las através
do Hibernate basta criar uma segunda classe para representar sua chave. Essa classe deve implementar a
interface Serializable . Além disso, como essa classe não é um Entity ; ela deve ser anotada com
@Embeddable , que indica que seus atributos serão embutidos na Entity que a inclui. Na entidade, ao
invés de usar @Id , usamos uma outra anotação @EmbeddedId .

Por exemplo, queremos que as chaves primarias da entidade Gerente sejam o RG e o CPF dele,
então criamos a classe GerenteId e usamos como id:
@Embeddable
public class GerenteId implements Serializable {

private String rg;

private String cpf;

// gets e sets
}

E a classe Gerente ficaria:

@Entity
public class Gerente {

@EmbeddedId
private GerenteId id;

// ...
}

Veja a saída SQL:


create table Gerente (
cpf varchar(255) not null,
rg varchar(255) not null,
nome varchar(255),
...
primary key (cpf, rg)
)

214 16.7 PARA SABER MAIS: MAPEANDO CHAVES COMPOSTAS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

OUTRAS FORMAS DE CHAVES COMPOSTAS

Muita gente reclama do fato dos atributos da chave estar numa segunda classe, separada da
entidade. Há para isso uma terceira opção. Cria-se a segunda classe com os atributos, mas depois
copia-se todos os atributos para a entidade. Na entidade, usa-se a anotação @IdClass para indicar
qual é a classe de id.
@Entity @IdClass(GerenteId.class)
public class Gerente {

@Id
private String rg;
@Id
private String cpf;

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

16.7 PARA SABER MAIS: MAPEANDO CHAVES COMPOSTAS 215


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 17

DETALHES SOBRE OS MAPEAMENTOS

"O consenso é a negociação da liderança." -- Margaret Thatcher

Ao término desse capítulo, você será capaz de:

Mapear objetos de forma que represente um banco de dados legado.

17.1 MAPEANDO OS OBJETOS PARA UM BANCO DE DADOS LEGADO


Um dos pontos mais complicados quando se trabalha com uma ferramenta de ORM é o momento de
integrá-la com um banco de dados legado. É importante ter em mente que é possível utilizar a JPA em
qualquer projeto, inclusive com banco de dados legados.

Muitas pessoas utilizam alguma ferramenta de engenharia reversa para gerar o modelo de objetos
com as devidas anotações a partir de um banco de dados existente. Uma ferramenta bastante conhecida
e que possui essa funcionalidade é o JBoss Tools.

No entanto, mesmo que a ferramenta gere o mapeamento das entidades para nós, é importante
compreendermos o que cada anotação faz. Mesmo tendo visto diversos mapeamentos diferentes durante
o curso, acaba se tornando inviável abordarmos todas as existentes, portanto recomendamos fortemente
ao aluno que leia a documentação do Hibernate Annotations disponível em:
http://docs.jboss.org/hibernate/stable/annotations/reference/en/pdf/hibernate_referen
ce.pdf

É importante ter em mente que o Hibernate possui diversos pontos de extensão, que nos permite
alterar também vários dos seus comportamentos padrão com relação ao banco de dados. Um desses
pontos de extensão comuns que existe é o uso do Hibernate Envers para fazer auditoria dos dados.

216 17 DETALHES SOBRE OS MAPEAMENTOS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

17.2 MAPEAMENTO DE HERANÇA


Embora o modelo relacional não suporte nada parecido com a herança da orientação a objetos, a JPA
permite que possamos mapear modelos mesmo com herança.

Na JPA temos três maneiras de mapear uma herança entre entidades:

Single table
Table per Class
Joined

Essas opções estão disponíveis usando a anotação @Inheritance , passando um InheritanceType


( SINGLE_TABLE, TABLE_PER_CLASS, JOINED )

Cada modo tem suas vantagens e desvantagens. Vamos analisar cada modo separadamente. Imagine
que agora teremos dois tipos de gerentes no nosso banco, um para gerenciar alguma conta do banco, e
outro para gerenciar equipes internas e seus projetos. Teríamos o seguinte modelo:
class Gerente{
//atributos omitidos
}

class GerenteConta extends Gerente{


String numeroDaConta; //getter e setter
}

class GerenteEquipe extends Gerente{


String nomeDaEquipe; //getter e setter
}

Single table
A opção Single Table criará uma única tabela para todas as entidades com todas as colunas
disponíveis em todas as entidades, além de uma a mais chamada DTYPE, que identificará o registro pelo

17.2 MAPEAMENTO DE HERANÇA 217


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

tipo java dele (Gerente, GerenteConta, GerenteEquipe)

Tabela Gerente
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| ID | int(11) | NO | PRI | NULL | |
| DTYPE | varchar(31) | YES | | NULL | |
| cidade | varchar(255) | YES | | NULL | |
| estado | varchar(255) | YES | | NULL | |
| rua | varchar(255) | YES | | NULL | |
| nome | varchar(255) | YES | | NULL | |
| telefone | varchar(255) | YES | | NULL | |
| nomeDaEquipe | varchar(255) | YES | | NULL | |
| numeroDaConta | varchar(255) | YES | | NULL | |
+---------------+--------------+------+-----+---------+----------------+

Quando inserirmos um GerenteConta no banco, a coluna numeroDaConta será preenchida e a


nomeDaEquipe ficará nula.

Dentre todas as estratégias disponíveis para mapear heranças, esta é a mais performática, visto que
precisamos acessar apenas uma única tabela ao trabalhar com quaisquer das subclasses envolvidas.
Porém não é possível realizarmos validações no banco de dados através de constraints , pois todas as
colunas de diferentes modelos estão presentes na mesma tabela.

Confira as anotações usadas nesse modelo:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class Gerente {
//...
}

@Entity
public class GerenteConta extends Gerente{
//...
}

@Entity
public class GerenteEquipe extends Gerente{
//...
}

Table per Class


Nessa estratégia a JPA trabalhará com um tabela para cada uma das entidade. Cada tabela é
independente das outras. Dois pontos devem ser levados em consideração: não podemos usar a anotação
@GeneratedValue com a estratégia IDENTITY , e se fizermos uma busca por uma tabela pai (Gerente),
a JPA também procurará nas filhas (esse é o princípio da herança, um GerenteConta é um Gerente).
Tabela Gerente
+----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| cidade | varchar(255) | YES | | NULL | |

218 17.2 MAPEAMENTO DE HERANÇA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

| estado | varchar(255) | YES | | NULL | |


| rua | varchar(255) | YES | | NULL | |
| nome | varchar(255) | YES | | NULL | |
| telefone | varchar(255) | YES | | NULL | |
+----------+--------------+------+-----+---------+-------+

Tabela GerenteConta
+---------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| cidade | varchar(255) | YES | | NULL | |
| estado | varchar(255) | YES | | NULL | |
| rua | varchar(255) | YES | | NULL | |
| nome | varchar(255) | YES | | NULL | |
| telefone | varchar(255) | YES | | NULL | |
| numeroDaConta | varchar(255) | YES | | NULL | |
+---------------+--------------+------+-----+---------+-------+

Tabela GerenteEquipe
+--------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| cidade | varchar(255) | YES | | NULL | |
| estado | varchar(255) | YES | | NULL | |
| rua | varchar(255) | YES | | NULL | |
| nome | varchar(255) | YES | | NULL | |
| telefone | varchar(255) | YES | | NULL | |
| nomeDaEquipe | varchar(255) | YES | | NULL | |
+--------------+--------------+------+-----+---------+-------+

Veja como fica a anotação:

@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class Gerente {
//...
}

O TopLink não implementa essa estratégia, o Hibernate e OpenJPA sim

Joined
Nessa estratégia a JPA trabalhará com tabelas com o mínimo de informações possíveis. Cada classe
terá uma tabela apenas com seus atributos, e as tabelas das classes filhas terão uma coluna a mais que
será uma chave estrangeira para a tabela pai.

Quando salvarmos um objeto de uma das classes filhas a JPA vai gerar um registro na tabela filha e
um na tabela pai.
Tabela Gerente
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| cidade | varchar(255) | YES | | NULL | |
| estado | varchar(255) | YES | | NULL | |

17.2 MAPEAMENTO DE HERANÇA 219


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

| rua | varchar(255) | YES | | NULL | |


| nome | varchar(255) | YES | | NULL | |
| telefone | varchar(255) | YES | | NULL | |
+----------+--------------+------+-----+---------+----------------+

Tabela GerenteConta
+---------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| numeroDaConta | varchar(255) | YES | | NULL | |
+---------------+--------------+------+-----+---------+-------+

Tabela GerenteEquipe
+--------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| nomeDaEquipe | varchar(255) | YES | | NULL | |
+--------------+--------------+------+-----+---------+-------+

Veja como a anotação:

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public class Gerente {
//...
}

17.3 FAZENDO SCRIPTS DE CRIAÇÃO DE TABELAS COM


SCHEMAEXPORT
Em muitas empresas não é permitido que o desenvolvedor tenha acesso ao banco de dados para fazer
a criação das tabelas, e portanto é necessário que uma pessoa faça essa criação para o desenvolvedor.
Geralmente, essa função é do DBA.

Uma das vantagens do Hibernate, e que pode salvar bastante tempo do DBA com a criação do banco
de dados é que ele pode gerar também os scripts de criação das tabelas, também conhecidos como DDL.
Dessa forma, nós podemos enviar esse script para que o próprio DBA faça a criação para nós.

Muita gente também não gosta de poluir suas classes de modelo com informações específicas do
schema do banco, irrelevantes para o mapeamento objeto-relacional, como, por exemplo, os índices
( @Index ), constraints ( @UniqueConstraint ) e até mesmo detalhes sobre tamanho dos campos
( @Column(lenght=100) ). Todos esses exemplos são importantes para o schema relacional mas não
importantes para o programa e seu mapeamento. Uma abordagem que pode ser feita é adicionar essas
informações diretamente ao schema do banco sem interferir na classe da entidade.

Utilizando a JPA via Hibernate, podemos criar uma classe com um método main e utilizarmos a
classe SchemaExport , específica do Hibernate, para fazer a criação do banco de dados para nós.
Precisamos passar para ela as configurações do projeto, mas como usamos JPA e configurações da JPA,
precisamos convertê-las para o Hibernate usando a classe EJB3Configuration :

220 17.3 FAZENDO SCRIPTS DE CRIAÇÃO DE TABELAS COM SCHEMAEXPORT


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public class GeraBanco {


public static void main(String[] args) {
EntityManagerFactory factory = Persistence.
createEntityManagerFactory("controlefinancas");
Ejb3Configuration econf = new Ejb3Configuration();
econf.configure("controlefinancas",factory.getProperties());
new SchemaExport(econf.getHibernateConfiguration()).create(true, false);
}
}

O método create do SchemaExport recebe dois booleanos, sendo que o primeiro indica que
queremos visualizar o DDL no console e o segundo indica se queremos executar o DDL. Nesse caso, só
queremos visualizar o script sem executá-lo.

O SchemaExport vai gerar todas as tabelas do banco de dados, adicionando um comando para
excluir as tabelas existentes. Caso a ideia seja apenas fazer alterações como adicionar colunas novas na
tabela, pode se usar a classe SchemaUpdate ao invés de SchemaExport .

17.4 EXERCÍCIOS: MAPEAMENTO DE HERANÇA COM JPA


1. Teremos um novo tipo de gerente que deverá ser relacionado com as contas:

Crie a classe GerenteConta e faça-a herdar da classe Gerente . Ela também possuirá um
atributo do tipo String chamado numeroDaConta, que armazenará o numero da conta
relacionada ao gerente.

public class GerenteConta extends Gerente{

private String numeroDaConta; //getter e setter

Para indicar que a nova classe deve ser gerenciada pela JPA, utilize a anotação @Entity sobre a
mesma.
@Entity
public class GerenteConta extends Gerente{

private String numeroDaConta;

//métodos omitidos
}

2. Vamos indicar que teremos uma herança a ser mapeada:

Na classe Gerente , utilize a anotação @Inheritance para indicar o mapeamento. Utilizaremos


a estratégia de mapeamento JOINED:
@Inheritance(strategy=InheritanceType.JOINED)
public class Gerente implements Serializable {

//métodos e atributos omitidos


}

17.4 EXERCÍCIOS: MAPEAMENTO DE HERANÇA COM JPA 221


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

3. Para que a nova classe GerenteConta seja utilizada, vamos alterar o nosso código responsável por
adicionar novos gerentes:

Abra a classe GerentesBean e altere o atributo gerente para que referencie a um novo
GerenteConta .

private Gerente gerente = new GerenteConta();

Altere também o método limpaFormularioDoJSF para criar um novo GerenteConta .

private void limpaFormularioDoJSF() {


this.gerente = new GerenteConta();
}

4. Para finalizar, vamos adicionar o número da conta relacionada ao gerente em seu atributo
numeroDaConta antes de gravar uma nova conta no banco de dados:

O primeiro passo é alterar o método busca na classe GerenteDao para que retorne um objeto
do tipo GerenteConta :
public GerenteConta busca(Integer id) {
return manager.find(GerenteConta.class, id);
}

Agora que o método busca retorna um GerenteConta , podemos alterar o método grava da
classe ContasBean para associar o número da conta ao gerente:

public void grava() {


//código anterior
// if(gerenteId != null){
// Gerente gerenteRelacionado = gerenteDao.busca(gerenteId);
// this.conta.setGerente(gerenteRelacionado);
// }

if(gerenteId != null){
GerenteConta gerenteRelacionado = gerenteDao.busca(gerenteId);
gerenteRelacionado.setNumeroDaConta(this.conta.getNumero());
this.conta.setGerente(gerenteRelacionado);
}

//restante do método omitido


}

5. Vamos testar as mudanças:

Reinicie a aplicação e acesse http://localhost:8080/fj25-financas-web/gerentes.xhtml


para adicionar um novo gerente.

Acesse http://localhost:8080/fj25-financas-web/contas.xhtml e adicione uma nova


conta selecionando o gerente que acabou de ser criado.

Perceba que tudo ocorreu perfeitamente como antes.

6. Vamos analisar como estão organizadas as tabelas no banco de dados e como o gerente e a conta que

222 17.4 EXERCÍCIOS: MAPEAMENTO DE HERANÇA COM JPA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

acabaram de ser criados foram armazenados:

Para acessar o banco de dados, abra o terminal e digite:


> mysql -u root

mysql> use fj25

Então, para visualizar a descrição das tabelas, digite:

mysql> desc Gerente;

e depois

mysql> desc GerenteConta;

Visualize também como os dados foram armazenados através do comando select :


mysql> select * from Gerente;
mysql> select * from GerenteConta;
mysql> select * from Conta;

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

17.4 EXERCÍCIOS: MAPEAMENTO DE HERANÇA COM JPA 223


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 18

APÊNDICE - CRIANDO CONSULTAS COM


CRITERIA

"O verdadeiro discípulo é aquele que supera o mestre." -- Aristóteles

Ao término desse capítulo, você será capaz de:

Realizar consultas usando Criteria;


Melhorar as consultas que envolvam condicionais com o uso da Criteria.

18.1 EVITANDO CONCATENAÇÃO DE STRING NAS QUERIES


Muitas vezes, temos telas onde o usuário solicita alguns filtros e, dependendo do que ele selecionou,
montamos a query específica. Para criarmos essa query dinamicamente, temos que verificar os filtros
solicitados e concatenando as condições.

Imagine que queremos dar a possibilidade para o usuário de realizar uma pesquisa podendo
selecionar uma conta ou não. Para não ficar concatenando strings diretamente, vamos usar um
StringBuilder para realizar essas operações. Então ficamos com um código parecido com:

StringBuilder jpql = new StringBuilder("select m from Movimentacao");

Agora, precisamos testar se foi passada ou não a conta. Então, podemos adicionar um if :

StringBuilder jpql = new StringBuilder("select m from Movimentacao");


if (conta != null){
jpql.append("where conta= :conta");
}

Aqui, já temos um detalhe, estamos esquecendo de dar um espaço no momento de concatenar a


query. Se fossemos, nesse momento, invocar o toString() iria ser retornado para gente a seguinte
String :

select m from Movimentacaowhere conta= :conta

Então já temos que lembrar de dar esse espaço. Outro problema é que, temos de alguma forma
guardar os parâmetros que foram utilizados na JPQL para depois substituí-los para a execução da query.
O usuário também pode escolher o tipo de movimentação que ele quer trazer, de saída ou de entrada.
Então vamos adicionar mais uma condição:

224 18 APÊNDICE - CRIANDO CONSULTAS COM CRITERIA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

StringBuilder jpql = new StringBuilder("select m from Movimentacao");


if (conta != null) {
jpql.append(" where conta= :conta");
}
if (tipoMovimentacao != null) {
jpql.append("and tipoMovimentacao= :tipo");
}

Tudo parece certo? O problema aqui é que talvez uma conta não tenha sido passada e, como não
entramos no primeiro if , também não colocamos o where . Como consequência, na hora da execução
da query, vamos receber um erro de sintaxe.

Podemos até gastar um tempo para ir arrumando cada problema, mas vamos gastar muito tempo em
uma tarefa que não é o foco. O que queremos na verdade é simplesmente montar uma consulta
dinamicamente. A JPA2 oferece um conjunto de classes que já resolveram este problema para gente e
permite montarmos query orientada a objetos. Vamos começar a estudar a Criteria API.

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

18.2 AS CLASSES PRINCIPAIS DA CRITERIA


Para entender a API da Criteria vamos dar uma olhada nos principais protagonistas. Conheceremos
as classes mais importantes envolvidas na construção da query.

CriteriaBuilder
O CriteriaBuilder é uma fábrica auxiliar para criar expressões sobre as funções que utilizaremos
na busca. A fábrica não executa a query em si, ela apenas ajuda a criar. Ela é muito parecida com as
classes Restrictions ou Projections da API de Criteria do Hibernate.

Para receber um objeto do tipo CriteriaBuilder , usaremos o EntityManager :

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

18.2 AS CLASSES PRINCIPAIS DA CRITERIA 225


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

O CriteriaBuilder possui métodos que definem operações da busca como por exemplo:

equal(), greaterThan(), lesserThan(), like() ...


sum(), max(), min(), avg(), count(), desc(), distinct() ...

Qualquer método acima retorna um objeto do tipo Predicate ou Expression , ambos são
interfaces ( Predicate estende a interface Expression ). Por exemplo, podemos definir um filtro
usando um método do builder:
Predicate lesser = builder.lesserThan(caminho, 500.5 )

ou somando todos os valores um atributo de uma entidade:

Expression<BigDecimal> soma = builder.sum(caminho);

Para determinar o que estamos filtrando ou somando existe o MetaModel (no exemplo indicado
pelo caminho) que veremos um pouco mais na frente. Primeiro, é preciso conhecer a definição da query,
a CriteriaQuery.

CriteriaQuery
CriteriaQuery
é a definição da pesquisa. O método createQuery(classe) recebe como
parâmetro o tipo do resultado da query. Se buscarmos Movimentações devemos colocar
Movimentacao.class , ou se quisermos somar o valor de todas as movimentações
BigDecimal.class . O CriteriaBuilder também é responsável pela criação da CriteriaQuery :

CriteriaQuery<Movimentacao> criteria = builder.createQuery(Movimentacao.class);

Imagine que queiramos apenas os valores de cada movimentação, nesse caso, precisamos definir que
o tipo de retorno da consulta agora é um BigDecimal . O exemplo alterado fica da seguinte forma:
CriteriaQuery<BigDecimal> criteria = builder.createQuery(BigDecimal.class);

Perceba que, agora, criamos uma criteria de busca informando que o tipo de retorno é BigDecimal .
Se tentarmos criar uma query baseado apenas nesta definição, do jeito que está, vamos receber uma
exception informando que ele não sabe de onde deve ser feita a consulta.

Temos que, explicitamente, informar de onde deve ser feito o select . Existe um método no
CriteriaQuery cujo o nome é from e, através deste método, podemos passar a classe que vai servir de
base na consulta. Nosso código está da seguinte forma:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Movimentacao> criteria = builder.createQuery(Movimentacao.class);
criteria.from(Movimentacao.class);

Com a CriteriaQuery descrevemos a busca parecido com o JPQL. Quando escrevermos JPQL,
usaremos palavras chaves como select , from , where ou groupBy . As mesmas palavras chaves
aparecem na CriteriaQuery , mas aqui são nomes de métodos. Resumindo, encontramos os seguintes

226 18.2 AS CLASSES PRINCIPAIS DA CRITERIA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

métodos na interface CriteriaQuery :

select
from
where
orderBy
groupBy
having

Os métodos trabalham com Expression ou Predicate . O método select , por exemplo, recebe
como parâmetro uma Expression . Então podemos usar o CriteriaBuilder para criar uma
Expression :

Expression soma = builder.sum(caminho);


criteria.select(soma);

Para definir um filtro usaremos o método where que recebe 0, 1 ou mais Predicate s:
Predicate lesser = builder.lesserThan(caminho, 500.5);
criteria.where(lesser);

Os outros métodos da CriteriaQuery como orderBy , groupBy e having funcionam de


maneira parecida e todos têm em comum que eles são opcionais. Então se for preciso, podemos chamar
select , where ou groupBy . O único método que deve ser chamado obrigatoriamente é from . Com
from definimos a raiz ( Root ) da pesquisa:

//no JPQL: "from Movimentacao m"


Root<Movimentacao> root = criteria.from(Movimentacao.class);

O root é preciso para acessar o MetaModel e para definir os caminhos.

A próxima imagem seguinte mostra a execução de uma CriteriaQuery comparando com JPQL:

18.2 AS CLASSES PRINCIPAIS DA CRITERIA 227


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

MetaModel
No exemplo anterior vimos como pegar a Root:
//a movimentação como raiz
Root<Movimentacao> root = criteria.from(Movimentacao.class);

O método from retorna um objeto que representa a mesma ideia do alias que usamos quando
estamos criando uma consulta usando a JPQL. O tipo deste objeto retornado é Root .

A raiz é usada para acessar o MetaModel e definir os caminhos a partir dela. Por meio desse model
descrevemos o que queremos filtrar, somar ou calcular. Por exemplo, se quisermos pesquisar pelo valor
da movimentação usaremos:

// no JPQL: "m.valor > 500.0"


Path<BigDecimal> caminho = root.<BigDecimal>get("valor");
Predicate lesser = builder.lesserThan(caminho , new BigDecimal("500.0"));
criteria.where(lesser);

Path é uma interface que estende Expression . Root por sua vez também é um Path . O mesmo
filtro pode ser escrito mais compactado sem variáveis auxiliares:

criteria.where(
builder.lesserThan(
root.<BigDecimal>get("valor"),
new BigDecimal("500.0"))
);

A próxima imagem mostra a relacionamento entre JPQL e Criteria na clausula where :

Com o Root na mão podemos também determinar o retorno da query. Aqui entra o método
select da query (opcional). Veja o código seguinte:

//no JPQL "select m.valor ..."


Path<BigDecimal> caminho = root.<BigDecimal>get("valor");
criteria.select(caminho);

Caso que queremos somar o valor de todas as movimentações:


//no JPQL "select sum(m.valor) ..."

228 18.2 AS CLASSES PRINCIPAIS DA CRITERIA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Path<BigDecimal> caminho = root.<BigDecimal>get("valor");


Expression<BigDecimal> soma = builder.sum(caminho);
criteria.select(soma);

O mesmo exemplo mais compacto:


criteria.select(
builder.sum(
root.<BigDecimal>get("valor")
)
);

A próxima imagem mostra a construção completa da CriteriaQuery comparando com JPQL:

PARA SABER MAIS

Para acessar o relacionamento da movimentação com a conta pelo MetaModel:

Path<Conta> path = root.<Conta>get("conta");

A partir da raiz da query podemos definir join e tipos de joins explícitos:


Fetch<Movimentacao, Conta> fetch = root.fetch("conta", JoinType.LEFT);

O JoinType é opcional.

18.3 EXERCÍCIO OPCIONAL: CONHECENDO A CRITERIA


1. Objetivo é conhecer a API da criteria. Queremos fazer uma busca simples. A busca com JPQL seria:
select m from Movimentacao m

Mas claro que queremos usar a Criteria. Para isso, crie na classe MovimentacaoDao um novo

18.3 EXERCÍCIO OPCIONAL: CONHECENDO A CRITERIA 229


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

método listaTodasComCriteria que retorna uma lista de movimentações ( List<Movimentacao> ).


public List<Movimentacao> listaTodasComCriteria() {

2. Dentro do método listaTodasComCriteria vamos buscar as movimentações com a criteria. Antes


de criarmos a query em si, precisamos de um objeto que será o responsável pela criação delas. Para
recuperar esse objeto, vamos usar o EntityManager . Para essa primeira parte adicione o seguinte
código:
CriteriaBuilder builder = this.manager.getCriteriaBuilder();

3. Com um CriteriaBuilder criado, podemos começar a criar um critério de consulta. Inicialmente


invocamos o método createQuery passando a classe que é o tipo de retorno da nossa consulta.
Com isso, temos o seguinte código:

CriteriaBuilder builder = this.manager.getCriteriaBuilder();


CriteriaQuery<Movimentacao> criteria = builder.createQuery(Movimentacao.class);
criteria.from(Movimentacao.class);

4. Com o critério mais simples criado, podemos agora pedir para o EntityManager criar uma query
( TypedQuery ) baseada nesse critério e executar com o método getResultList .

Com isso, teremos o seguinte código:


return this.manager.createQuery(criteria).getResultList();

5. Abra a classe CriteriaBean e injete o MovimentacaoDao :

@Inject
private MovimentacaoDao movimentacaoDao;

No método listarTodasAsMovimentacoes , chame o método listaTodasComCriteria , que


acabamos de criar, do MovimentacaoDao :
this.movimentacoes = this.movimentacaoDao.listaTodasComCriteria();

6. Reinicie o servidor e acesse o endereço http://localhost:8080/fj25-financas-


web/criteria.xhtml .

Teste apenas o primeiro formulário.

18.4 EXERCÍCIO OPCIONAL: PESQUISA NO RELACIONAMENTO COM


METAMODEL DINÂMICO
1. O objetivo agora é definir uma criteria que soma o valor de todas as movimentações de uma conta
com um determinado titular. No JPQL podemos escrever a query:
select sum(m.valor) from Movimentacao m where m.conta.titular like :pTitular

230 18.4 EXERCÍCIO OPCIONAL: PESQUISA NO RELACIONAMENTO COM METAMODEL DINÂMICO


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Desta vez teremos que usar os métodos select , from e where da CriteriaQuery. Também
trabalharemos com o MetaModel para definir o filtro ( m.conta.titular ) e a projeção
( m.valor ). O CriteriaBuilder auxilia na definição da query para fazer um like(..) e sum(..) .

Na classe MovimentacaoDao adicione um novo método com a assinatura:

public BigDecimal somaMovimentacoesDoTitular(String titular) {


return null;
}

A query vai retornar a soma das movimentações que vai ser do tipo BigDecimal . Vamos usar
este tipo na criação da query. Dentro do método somaMovimentacoesDoTitular escreva:
CriteriaBuilder builder = this.manager.getCriteriaBuilder();
CriteriaQuery<BigDecimal> criteria = builder.createQuery(BigDecimal.class);

A raiz da query será a movimentação, adicione a linha seguinte:

Root<Movimentacao> root = criteria.from(Movimentacao.class);

Com o root podemos definir que queremos somar o valor da movimentação:


//no JPQL: select sum(m.valor)
criteria.select(
builder.sum(
root.<BigDecimal>get("valor")
)
);

Já usamos criteria.select e criteria.from da CriteriaQuery , mas falta ainda criar o


filtro. Para isso, usaremos criteria.where() . Nesse caso queremos selecionar as
movimentações de um determinado titular. É preciso acessar o relacionamento entre
Movimentação e Conta. A partir do root pegamos a conta e depois o atributo titular :
//no JPQL: where m.conta.titular like :pTitular
criteria.where(
builder.like(
root.<Conta>get("conta").<String>get("titular"),
"%" + titular + "%"
)
);

Finalmente vamos executar a query. Adicione a última linha no método


somaMovimentacoesDoTitular :

return this.manager.createQuery(criteria).getSingleResult();

2. Abra a classe CriteriaBean . Procure o método somaMovimentacoesDoTitular e chame lá


dentro o DAO. Na classe CriteriaBean já tem um atributo titular do tipo String e um
atributo soma do tipo BigDecimal :

this.soma = movimentacaoDao.somaMovimentacoesDoTitular(this.titular);

3. Reinicie o servidor e acesse o endereço http://localhost:8080/fj25-financas-

18.4 EXERCÍCIO OPCIONAL: PESQUISA NO RELACIONAMENTO COM METAMODEL DINÂMICO 231


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

web/criteria.xhtml .

Teste o segundo formulário para calcular o total movimentado de um titular.

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

18.5 MONTANDO UMA CONSULTA DINÂMICA


Agora vamos dar a oportunidade ao nosso usuário de escolher alguns filtros para listar as
movimentações. Ele vai ter a opção de escolher, opcionalmente, por uma conta ou tipo de
movimentação associado a uma movimentação. Para montar a query, vamos usar novamente a Criteria
API.

Adicionamos mais um método na classe MovimentacaoDao para realizar esta busca baseada em
filtros dinâmicos. Inicialmente, criaremos um CriteriaQuery informando o tipo de resultado da nossa
busca. Precisamos também verificar as condições para ir montando a consulta. Para poder testar, por
exemplo, se existe uma movimentação igual à conta passada como parâmetro, temos que recuperar esse
atributo dentro da consulta. Para isso, mais uma vez, vamos recuperar um objeto do tipo Root . Temos
o seguinte código inicial:
public List<Movimentacao> pesquisa(Conta conta,
TipoMovimentacao tipo) {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaQuery<Movimentacao> criteria =
builder.createQuery(Movimentacao.class);
Root<Movimentacao> root = criteria.from(Movimentacao.class);
}

Para nosso caso, vamos querer adicionar um AND na nossa consulta cada vez que precisarmos uma
nova condição no filtro. Para poder adicionar quantas condições forem necessárias vamos fazer uso de
um objeto do tipo Predicate . Para recuperar um objeto deste tipo, que consiga adicionar AND a cada
nova condição, invocamos o método da classe CriteriaBuilder chamado conjunction . Com isso
temos o seguinte código:
public List<Movimentacao> pesquisa(Conta conta,
TipoMovimentacao tipo) {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaQuery<Movimentacao> criteria =

232 18.5 MONTANDO UMA CONSULTA DINÂMICA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

builder.createQuery(Movimentacao.class);
Root<Movimentacao> root = criteria.from(Movimentacao.class);
Predicate conjunction = builder.conjunction();
}

Agora basta testarmos as condições e ir acrescentando-as ao nosso objeto do tipo Predicate :

public List<Movimentacao> pesquisa(Conta conta,


TipoMovimentacao tipo) {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaQuery<Movimentacao> criteria =
builder.createQuery(Movimentacao.class);
Root<Movimentacao> root = criteria.from(Movimentacao.class);
Predicate conjunction = builder.conjunction();
if (conta != null) {
conjunction = builder.and(conjunction,
builder.equal(root.<Conta>get("conta"), conta));
}
if (tipo != null) {
conjunction = builder.and(conjunction,
builder.equal(root.<TipoMovimentacao>get("tipo"), tipo));
}
}

Repare que a classe CriteriaBuilder possui métodos que podemos utilizar para montar a nossa
query. Por exemplo, adicionamos um AND para o nosso objeto Predicate cuja condição que deve ser
testada é se a conta da movimentação é igual a conta que foi passada como parâmetro. Agora, depois de
termos definido todas as condições necessárias para a nossa pesquisa, vamos, de fato, adicioná-la a nossa
busca. Para isso vamos invocar o método where da classe CriteriaQuery . Com isso temos o seguinte
código:
public List<Movimentacao> pesquisa(Conta conta,
TipoMovimentacao tipo) {
CriteriaBuilder builder = this.em.getCriteriaBuilder();
CriteriaQuery<Movimentacao> criteria =
builder.createQuery(Movimentacao.class);
Root<Movimentacao> root = criteria.from(Movimentacao.class);
Predicate conjunction = builder.conjunction();
if (conta != null) {
conjunction = builder.and(conjunction,
builder.equal(root.<Conta>get("conta"), conta));
}
if (tipo != null) {
conjunction = builder.and(conjunction,
builder.equal(root.<TipoMovimentacao>get("tipo"), tipo));
}
criteria.where(conjunction);
return this.em.createQuery(criteria).getResultList();
}

18.6 CRITERIA TYPESAFE COM METAMODEL


Até agora, toda vez que precisamos usar algum atributo do nosso objeto nas queries, temos que
referenciá-lo por String. Por exemplo, quando queremos acessar o valor, através do objeto do tipo
Root , usamos um código como esse abaixo:

18.6 CRITERIA TYPESAFE COM METAMODEL 233


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Root<Movimentacao> root = criteria.from(Movimentacao.class);


root.get("valor");

Tentando deixar a consulta, utilizando a Criteria API, menos propensa a erros, a JPA 2 introduziu
uma maneira de nos referenciarmos aos atributos do nosso modelo ainda mais orientada a objetos. Em
vez de passarmos a String para o método get , podemos passar um objeto que representa esse
atributo. Primeiro precisamos, de alguma maneira, recuperar um objeto que contém todas as
informações referentes à nossa classe mapeada. O EntityManager tem um método chamado
getMetaModel . Este método retorna um objeto que consegue recuperar informações sobre quaisquer
entidades mapeadas. Então temos o seguinte:
EntityManager em = //recuperar EntityManager;
Metamodel metamodel = em.getMetamodel();

Com um objeto do tipo MetaModel criado, podemos pedir informações sobre qualquer entidade.
Para isso usamos o método entity passando como parâmetro a classe que queremos pegar
informações. Este método nos retorna um objeto do tipo EntityType . Então, agora teríamos o seguinte
código:

EntityManager em = //recuperar EntityManager;


Metamodel metamodel = em.getMetamodel();
EntityType<Movimentacao> movimentacao_ = metamodel.entity(Movimentacao.class);

Um detalhe interessante é que, ao contrário da convenção do Java, demos o nome à nossa variável de
movimentacao_. Essa foi a convenção adotada para a JPA 2 quando temos uma variável que contém um
objeto do tipo EntityType , ou seja, nomeDaClasse_ . Como estamos trabalhando com a
Movimentacao , ficou movimentacao_. Imagine agora que queremos acessar o atributo valor da
Movimentacao , para isso invocamos o método getSingularAttribute . Ele nos retorna um objeto
que representa nosso atributo, e podemos agora usar o método get da classe Root passando ele como
parâmetro. Temos então o seguinte código:

EntityManager em = //recuperar EntityManager;


Metamodel metamodel = em.getMetamodel();
EntityType<Movimentacao> movimentacao_ = metamodel.entity(Movimentacao.class);
SingularAttribute<? super Movimentacao, BigDecimal> atributoValor = movimentacao_
.getSingularAttribute("valor", BigDecimal.class);
Root<Movimentacao> movimentacao = criteria.from(Movimentacao.class);
movimentacao.get(atributoValor);

Repare que agora temos um objeto que representa nosso atributo, porém tivemos que escrever muito
mais código. Visando diminuir o código para usar a Criteria API com o Meta Model, a JPA 2 trouxe uma
outra maneira de recuperarmos os objetos que representam nossos atributos.

18.7 CRITERIA TYPESAFE COM STATIC METAMODEL


Uma outra maneira encontrada para realizar as consultas sem ter que ficar lembrando os nomes dos
atributos é a de criar uma classe contendo apenas atributos justamente com os nomes dos atributos das
nossas entidades. Por exemplo, para a classe Movimentacao teríamos a seguinte classe:

234 18.7 CRITERIA TYPESAFE COM STATIC METAMODEL


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public class AtributosMovimentacao {

public static final String DESCRICAO = "descricao";


public static final String VALOR = "valor";
public static final String CATEGORIAS = "categorias";
public static final String TIPO_MOVIMENTACAO = "tipo";
public static final String DATA = "data";
}

O problema é dessa abordagem era a de ter que ficar escrevendo uma classe semelhante para toda
entidade gerenciada do nosso projeto. Mas perceba que a ideia é exatamente a mesma que usamos
anteriormente, ter uma maneira typesafe de nos referenciarmos aos atributos da nossa entidade.
Percebendo isso, a JPA 2 trouxe uma maneira de geramos essas classes automaticamente, mas em vez de
todos atributos dela serem do tipo String , são dos tipos definidos pela JPA 2, como o
SingularAttribute por exemplo. Então a classe gerada, para a Movimentacao , teria o seguinte
código:
@StaticMetamodel(Movimentacao.class)
public abstract class Movimentacao_ {

public static volatile ListAttribute<Movimentacao, Categoria> categorias;


public static volatile SingularAttribute<Movimentacao, Integer> id;
public static volatile SingularAttribute<Movimentacao, BigDecimal> valor;
public static volatile SingularAttribute<Movimentacao, TipoMovimentacao> tipo;
public static volatile SingularAttribute<Movimentacao, LocalDateTime> data;
public static volatile SingularAttribute<Movimentacao, Conta> conta;
public static volatile SingularAttribute<Movimentacao, String> descricao;

Agora, o mesmo código que fizemos anteriormente, criando Metamodel dinamicamente, ficaria
assim:
Root<Movimentacao> root = criteria.from(Movimentacao.class);
root.get(Movimentacao_.valor);

Na JPA 2, essa classe é chamada de Static Metamodel. A classe responsável pela geração do Static
Metamodel vai variar de provider para provider. As configurações que faremos durante o treinamento
serão realizadas pelo Hibernate. Para outro provider, basta trocar o JAR da implementação. Para fazer
essa geração automática, temos que adicionar algumas opções para o javac no momento da compilação
dos nossos arquivos:

javac -processor org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor -proc:only

Dessa maneira, quando compilarmos o código, automaticamente serão geradas as classes contendo
informações sobre os atributos de cada entidade mapeada. Normalmente isso fica configurado na IDE
utilizada para desenvolvimento, para que não percamos tempo gerando o Static Metamodel. Veremos
também como fazer pelo Eclipse.

18.7 CRITERIA TYPESAFE COM STATIC METAMODEL 235


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

18.8 EXERCÍCIOS: CONSULTAS UTILIZANDO STRINGS NA CRITERIA


1. Adicione o método para realizar a busca dinâmica na classe MovimentacaoDao . Vamos aproveitar e
filtrar, inclusive, pelo mês da movimentação.

//outros métodos e atributos da classe MovimentacaoDao aqui

public List<Movimentacao> pesquisa(Conta conta,


TipoMovimentacao tipoMovimentacao, Integer mes) {
CriteriaBuilder builder = manager.getCriteriaBuilder();
CriteriaQuery<Movimentacao> criteria =
builder.createQuery(Movimentacao.class);

Root<Movimentacao> root = criteria.from(Movimentacao.class);

Predicate conjunction = builder.conjunction();


if (conta.getId() != null) {
conjunction =
builder.and(conjunction,
builder.equal(root.<Conta>get("conta"), conta));
}

if (mes != null && mes != 0) {


Expression<Integer> expression =
builder.function("month", Integer.class, root.<Calendar> get("data"));
conjunction =
builder.and(conjunction, builder.equal(expression, mes));
}

if (tipoMovimentacao != null) {
conjunction = builder.and(conjunction,
builder.equal(
root.<TipoMovimentacao>get("tipoMovimentacao"),
tipoMovimentacao));
}

criteria.where(conjunction);
return manager.createQuery(criteria).getResultList();
}

Note que, para adicionar o filtro por mês, precisamos usar a mesma função month que usamos com

236 18.8 EXERCÍCIOS: CONSULTAS UTILIZANDO STRINGS NA CRITERIA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

a JPQL. Para acessar funções SQL através da Criteria API usamos o método function passando o
tipo de retorno e o atributo que deve ser utilizado para recuperar o valor e passar para a função.

2. Na classe PesquisaMovimentacoesBean injete o MovimentacaoDao .

3. Na mesma classe PesquisaMovimentacoesBean altere o método pesquisa da classe para realizar


a busca por meio do nosso DAO. O método alterado deve ficar da seguinte forma:
public void pesquisa() {
movimentacoes = movimentacaoDao.pesquisa(getConta(), tipoMovimentacao, getMes());
}

4. Inicie o servidor, vá até o navegador e acesse: http://localhost:8080/fj25-financas-


web/pesquisaMovimentacoes.xhtml .

Realize algumas pesquisas para testar seu filtro.

18.9 DESAFIO: JOIN E FETCH NAS CRITERIAS


1. Repare que, no momento em que fazemos a busca pelas movimentações, as contas não são trazidas
automaticamente. Isto deve-se ao fato de que, ao efetuar busca utilizando Criteria, o Hibernate não
traz nem mesmo os relacionamentos cuja anotação usada termine com @..ToOne .

Evite este problema e tente que, ao buscar as movimentações, sejam carregadas as contas
automaticamente. Dica: Utilize o objeto Root<Movimentacao> movimentacao juntamente com o
método fetch .

2. Tente efetuar uma busca nas movimentações filtrando pelo nome do titular da conta. Dica: Utilize o
objeto Root<Movimentacao> movimentacao juntamente com o método join .

18.10 EXERCÍCIO OPCIONAL: CONFIGURANDO A GERAÇÃO DO


STATIC METAMODEL PELO ECLIPSE
1.
* Clique com o botão direito no nome do projeto e escolha a opção _Properties_
* Vá até a opção _Project Facets_
* Marque a opção _JPA_, na versão _2.1_
* Clique na opção _Further configuration available..._ , que aparece no
final da tela.
* Marque a opção _Discover annotated classes automatically_, caso não esteja marcado.
* Clique em _OK_ para fechar esta tela.
* Clique em _Apply_, e em _OK_ em seguida.
* Clique com o botão direito no nome do projeto e novamente escolha a opção _Properties_.
* Vá até a opção _JPA_, que não estava disponível anteriormente.
* Na área _Canonical Metamodel_, selecione a pasta _src_ na lista de opções.
* Clique em _OK_.

1. Tente alterar o projeto, para ao invés de usar a abordagem com strings, usar o Static Metamodel.

18.9 DESAFIO: JOIN E FETCH NAS CRITERIAS 237


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

18.11 DISCUSSÃO: QUAL TIPO DE CRITERIA DEVO UTILIZAR?

18.12 DESAFIO: EXPLORE A API DE CRITERIA


Assim como fora feito no exercício em que listamos os valores de todas as movimentações agrupadas
por mês de um determinada conta e tipo, implemente a mesma solução utilizando a API de Criteria.

238 18.11 DISCUSSÃO: QUAL TIPO DE CRITERIA DEVO UTILIZAR?


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 19

APÊNDICE - HIBERNATE ENVERS

"A paz é mais importante que qualquer porção de terra" -- Anwar Sadat

Ao término desse capítulo, você será capaz de:

Auditar operações com o banco de dados através do Hibernate Envers.

19.1 AUDITANDO AS ALTERAÇÕES: LISTENERS


Uma funcionalidade que muitas empresas demandam atualmente é manter um histórico das
alterações dos registros no banco de dados. Para isso, podemos desenvolver o nosso próprio mecanismo
de auditoria, alterando os nossos DAOs para que sempre que uma operação seja executada no banco de
dados, um histórico seja gravado em uma outra tabela. Uma outra alternativa seria usar o próprio banco
de dados através de triggers.

No entanto, ambas as alternativas possuem um forte ponto negativo: são formas muito trabalhosas.

Uma terceira alternativa seria utilizar o próprio Hibernate e utilizar seu recurso de listerners que
nada mais são do que classes que podem ser registradas para executar algum código quando uma
operação é disparada através do Hibernate. Dessa forma "apenas" teríamos que desenvolver tais listeners.

Mas e como definir o que auditar? O que não auditar? E a codificação desses listeners para satisfazer
todas as regras? Temos uma solução melhor que as duas primeiras sugestões, no entanto, ainda assim
trabalhosa.

19 APÊNDICE - HIBERNATE ENVERS 239


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

19.2 UTILIZANDO O HIBERNATE ENVERS


Dentro dos diversos projetos que existem no Hibernate, existe um cuja função é permitir a auditoria
das alterações nas entidades. A ideia é que os listeners já existem e basta que nós digamos quais entidades
e atributos queremos auditar. O projeto que faz tudo isso para nós é o Hibernate Envers.

Precisamos dizer que quais das nossas entidades serão auditadas. Para isso, basta adicionar a
anotação @Audited nas suas entidades. Portanto, para auditar a entidade Conta basta fazer:
@Entity
@Audited
public class Conta {
// atributos e métodos
}

No entanto, precisamos indicar se queremos auditar as associações da Conta , que possui


relacionamento com Movimentacao e com Gerente . Portanto precisamos adicionar a anotação
@Audited em todas as entidades envolvidas com Conta . Ao anotarmos a entidade Movimentacao
para ser auditada, teremos que marcar também a entidade Categoria .

Com isso, o Hibernate Envers vai gerar para cada tabela da aplicação uma tabela com o sufixo _AUD
que guardará as alterações que fizermos nos registros. Para quem está acostumado com um sistema de
controle de versões como CVS ou Subversion, o funcionamento é parecido e para cada alteração que é
feita em uma informação, uma nova versão do registro é gerada na tabela de auditoria.

19.3 EXERCÍCIOS: UTILIZANDO O HIBERNATE ENVERS


1. Vamos configurar nosso projeto para ser auditado. Anote as classes Conta , Movimentacao ,
Categoria , Gerente e GerenteConta com a anotação @Audited .

240 19.2 UTILIZANDO O HIBERNATE ENVERS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Não é necessário incluir nenhuma classe nova no classpath, já que o Envers é parte do core do
Hibernate.

19.4 PARA SABER MAIS: REVISION ENTITY


E se fosse necessário guardar informações adicionais sobre uma revisão? Por exemplo, se quisermos
vincular a operação efetuada (insert, delete ou update) ao usuário que efetuou a operação?

Para essa finalidade o Envers disponibiliza um recurso chamado Revision Entity. Trata-se de uma
entidade anotada com @RevisionEntity . Toda vez que uma nova revisão for criada (ou seja, quando
uma entidade auditada for criada ou alterada) uma nova instância da Revision Entity será persistida.

Uma Revision Entity obrigatoriamente deve conter no mínimo dois atributos:

um atributo Integer ou Long anotado com @RevisionNumber


um atributo Long ou java.util.Date anotado com @RevisionTimestamp

Podemos criar uma classe com os atributos previamente descritos ou podemos optar por estender
org.hibernate.envers.DefaultRevisionEntity que já possui os atributos obrigatórios.

A tarefa de popular os dados da Revision Entity é feita por um listener que implemente a interface
RevisionListener do Envers. Devemos criar esse listener e informá-lo na anotação de nossa Revision
Entity: @RevisionEntity(MeuRevisionListener.class) .

A interface RevisionListener possui um único método com a seguinte assinatura:


public void newRevision(Object revisionEntity);

Nesse método recebemos como argumento a instância criada da Revision Entity. Primeiramente
precisamos fazer um casting para a classe que criamos para ser nossa Revision Entity e então podemos
popular nela os dados relativos à revisão que está ocorrendo.
@Entity
@RevisionEntity(MeuRevisionListener.class)
public class MinhaRevisionEntity extends DefaultRevisionEntity{

private String userName;

//getters and setters


}

public class MeuRevisionListener implements RevisionListener {


public void newRevision(Object revisionEntity) {
MinhaRevisionEntity minhaRevisionEntity =
(MinhaRevisionEntity) revisionEntity;

String nomeUsuario = // busca o usuário logado da sessao, por exemplo...

minhaRevisionEntity.setUserName(nomeUsuario);
}
}

19.4 PARA SABER MAIS: REVISION ENTITY 241


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

242 19.4 PARA SABER MAIS: REVISION ENTITY


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 20

APÊNDICE - GOOGLANDO SUA


APLICAÇÃO ATRAVÉS DO HIBERNATE
SEARCH

"O tempo rende muito quando é bem aproveitado." -- Johann Wolfgang von Goethe

Ao término desse capítulo, você será capaz de:

Melhorar pesquisas da sua aplicação através do Hibernate Search.

20.1 O PROBLEMA DE BUSCA POR TEXTO: A DESCRIÇÃO DA


MOVIMENTAÇÃO
Queremos dar a possibilidade aos nossos usuários de pesquisar pelas movimentações associadas a
determinadas categorias ou/e descrição. A solução padrão para isso é disponibilizar um campo de texto
onde o usuário possa inserir o termo de busca para que, com isso, seja feita a busca das movimentações
associadas as mesmas.

Imagine então que vai chegar na sua aplicação a seguinte String que deve ser buscada: "restaurante
lanche". Nesse caso o usuário está querendo saber quais são as movimentações associadas possium
restaurante ou lanche. Como poderíamos realizar essa busca? Uma primeira ideia é realizar uma query
usando LIKE. Com a JPQL ficaríamos com o seguinte código:

String jpql = "select m from Movimentacao m


where m.descricao like '%restaurante lanche%'";

Só que quando formos executar isso, a não ser que tenha uma descrição cujo nome contenha o texto
"restaurante lanche", não será retornado nada no resultado. O primeiro trabalho que teríamos é tratar o
texto de entrada para que possamos separar o texto em várias palavras. Depois, podemos trocar o LIKE
para usar um IN e, com isso, conseguimos testar a descrição que contenha um texto ou outro:

String jpql = "select m from Movimentacao m


where m.descricao in ('restaurante', 'lanche')";

Até resolvemos parcialmente nosso problema. Apesar que, se agora for buscada uma palavra
levemente diferente da que consta no banco, já não conseguimos trazer os resultados. Depois de algum
esforço, até podemos resolver isso, com um subselect por exemplo.

20 APÊNDICE - GOOGLANDO SUA APLICAÇÃO ATRAVÉS DO HIBERNATE SEARCH 243


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Agora vamos imaginar a seguinte situação: ao procurarmos pela descrição restaurante e lanche
teremos alguns resultados contendo ambas as palavras, alguns contendo apenas a uma palavra
restaurante , outros apenas lanche . Qual seria a ordem ideal para os resultados? Provavelmente os
que contem ambas as palavras são mais relevantes, mas e quanto aqueles que possuem apenas
restaurante OU lanche ? Talvez poderíamos priorizar uma mais "popular", que aparece mais vezes
no banco.

Como fazemos para descobrir quantas vezes o restaurante foi associada a uma movimentacao?
Podemos usar um count como na busca abaixo:
String jpql = "select count(m) from Movimentacao m
where m.descricao = 'restaurante'";

Será performático ter que fazer o count para descobrir a ocorrência de cada uma das categorias que o
usuário buscar? E pior: O que colocaríamos no order by da jpql que faz a busca das movimentações?

Ainda na questão da ordenação, e se para o usuário a presença da palavra restaurante é, digamos,


duas vezes mais importante que lanche para a busca. Como exprimimos isso na jpql?

Como se a situação não fosse complicada o suficiente, o advento de sites de busca simples e eficientes
como o Google levou os usuários a se habituarem a não precisar acertar a grafia exata de uma palavra
para obter os resultados desejados na busca. Como fazer para possibilitar que o usuário digite
restaurantes , restarante , restaruante ou até rstrante e ainda sim tenha as movimentações
associadas com restaurante ?

Devemos lembrar que existe também a possibilidade do usuário errar a grafia no momento de
cadastrar as movimentações, então pode ser que nosso banco de dados tenha dados incorretos. Nesse
caso mesmo se o usuário digitar corretamente a palavara na busca, as movimentações associadas às
palavrass cadastradas com nomes incorretos não apareceriam no resultado. Esse problema se agrava
pensando que além de possíveis erros d e grafia, deve estar repleta de artigos, preposições etc que não são
relevantes para nossa busca.

Como poderíamos contornar esses problemas, que aparecem no dia a dia de uma pesquisa textual?

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

244 20.1 O PROBLEMA DE BUSCA POR TEXTO: A DESCRIÇÃO DA MOVIMENTAÇÃO


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

20.2 APACHE LUCENE


Focando no problema que passamos logo acima, no caso fazer busca baseado em determinado texto,
alguns projetos surgiram para atacá-lo, como, por exemplo, Apache Lucene, Solr, Compass, Oxyus e
Egothor.

Pelo foco que estes projetos dão a parte de busca textual, normalmente eles são chamados de Search
Engines. O mais famoso, e utilizado no mercado, de longe, é o Lucene. Inclusive, vários projetos que
encontramos, que fazem busca textual, são na verdade construídos em cima do Lucene.

Basicamente, o que o Lucene faz é criar uma estrutura de documentos, onde cada documento pode
ter um ou mais campos, e cada campo tem um valor associado. O diferencial dele é conseguir realizar
pesquisas sobre esses documentos seguindo os critérios que queríamos na nossa busca envolvendo a
descrição, isto é, busca textual. Para isso ele vai indexar todo documento de acordo com cada palavra
contida em cada campo seu. Muitas vezes, também podemos ir além com o Lucene e fazer pesquisas
obedecendo a critérios complexos, como: "quero todas as movimentações que contenham restaurante ou
lanche mas não contenham mercado, e que mercado possa estar escrito de várias maneiras".

Alguns consideram o Lucene também como sendo um banco de dados não relacional (NoSQL).

20.3 ALÉM DA ESPECIFICAÇÃO DA JPA2: RECURSOS ESPECÍFICOS DO


HIBERNATE
Trabalhar diretamente com a API do Lucene é um pouco burocrático. Como vimos anteriormente,
existem alguns projetos que foram criados justamente com o intuito de facilitar a vida de quem resolve
usar uma Search Engine. Um destes projetos se integra muito bem com o Hibernate, ou seja, perfeito
para o nosso caso. O nome dele é Hibernate Search e pode ser baixado diretamente do site do
Hibernate: http://www.hibernate.org/subprojects/search.html.

Mas o Hibernate Search não faz parte da especificação que define a JPA 2, então teremos que fazer
algumas configurações específicas do Hibernate para adaptar nosso projeto.

20.4 ENCAPSULANDO O LUCENE COM HIBERNATE SEARCH


Comentamos anteriormente que o Lucene cria documentos para representar as informações que
devem ser buscadas posteriormente, nosso trabalho com o Hibernate Search vai ser configurar os objetos
para que eles, além de terem representação no banco de dados, agora sejam representados como
documentos que são indexados pelo Lucene.

O primeiro trabalho é configurar o Hibernate Search para que ele identifique determinado objeto
como documento do Lucene. Para isso, usamos a anotação @Indexed nas nossas entidades:
@Entity

20.2 APACHE LUCENE 245


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

@Indexed
public class Movimentacao {
}

Agora, precisamos informar quais atributos devem ser adicionados ao documento para que o Lucene
consiga realizar buscas pelos documentos. Para isso, utilizamos a anotação @Field . Supondo que
queremos realizar pesquisas baseadas nas descrições das movimentações, teríamos o seguinte código:

@Entity
@Indexed
public class Movimentacao {

@Field
private String descricao;
}

Aqui temos alguns detalhes. Podemos configurar como o Lucene deve tratar o valor, no caso,
associado à descrição, que vai ser gravado no documento. A ideia é que ele tente gravar isso da melhor
maneira possível para que a busca seja a mais efetiva. Para realizar essa análise, o Lucene faz uso de
algumas classes, que, se quisermos, podemos definir, que são chamada de Analyzers. Por exemplo, para o
Brasil podemos usar o BrazilianAnalyzer , que automaticamente já tiraria expressões como
preposições e artigos das nossas buscas. Por padrão, a implementação utilizada é a StandardAnalyzer .
Essa tarefa de separar cada palavra de determinada texto e analisar é comumente chamada de
tokenização. Cada Analyzer tem os seus tokenizadores.

Após separar cada token, podem ser aplicadas algumas regras para deixá-los ainda mais fáceis de
serem buscados. Em uma situação hipotética, poderíamos ter uma busca que gerasse o token "Roberta",
para melhorar a indexação, o BrazilianAnalyzer ainda retiraria a última vogal, no caso, a letra a. O
objetivo disso é permitir que alguém busque por Roberto e a busca poderia retornar tanto "Roberta" ou
"Roberto". Esse técnica que é aplicada sobre cada token é chamada de stemming. Para informarmos que
nosso atributo deve ser analisado, usamos o atributo index da anotação @Field :
@Entity
@Indexed
public class Movimentacao {

@Field(index=Index.YES) //YES já é o padrão


private String descricao;
}

Uma questão importante, é que todo atributo anotado com @Id automaticamente já é atribuído ao
documento indexado pelo Lucene. Os documentos do Lucene também possui uma id, como os registros
no banco de dados. Hibernate Search usará a mesma id do registro para o documento. O Hibernate faz
isso para que, após realizar a pesquisa textual, consiga recuperar o objeto associado a determinado id no
banco de dados.

Hibernate Search também define uma anotação @DocumentId que pode ser omitida já que o
atributo com @Id automaticamente define a id do documento.

246 20.4 ENCAPSULANDO O LUCENE COM HIBERNATE SEARCH


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Por padrão, o Lucene não guarda o texto original após tokenizá-lo. Ele faz isso basicamente para
otimizar o espaço e a quantidade de informação que deve ser recuperada após a busca. Se quisermos
mudar esse comportamento, podemos usar o atributo store da anotação @Field :
@Entity
@Indexed
public class Movimentacao {

@Field(store=Store.YES)
private String descricao;
}

Uma questão que poderia surgir é que queremos indexar os nomes das categorias associadas a cada
movimentação. Precisamos informar que os valores dos atributos dos objetos do tipo Categoria
devem ser indexados pelo Lucene. Para isso, existe uma anotação chamada de @IndexedEmbedded .
Como cada movimentação contém uma lista de categorias associadas, vamos anotar este atributo com
@IndexedEmbedded . Temos agora o seguinte código:

package br.com.caelum.financas.modelo;

@Entity
@Indexed
public class Movimentacao {

@Field
private String descricao;

@ManyToMany
@IndexedEmbedded
private List<Categoria> categorias = new ArrayList<Categoria>();
}

Agora, na classe Categoria , precisamos informar quais atributos devem ser indexados. Seguimos a
mesma ideia de quando anotamos a descrição da movimentação com @Field :
package br.com.caelum.financas.modelo;

@Entity
public class Categoria {
@Id
@GeneratedValue
private Integer id;

@Field
private String nome;

Com isso, agora é sabido que quando alguma movimentação for sofrer alguma operação que reflita
no estado do banco de dados, o Hibernate vai sincronizar o estado dele com os documentos do Lucene.
Para cada movimentação será criado um documento do Lucene. Dentro desse documento teremos a id,
a descrição e uma lista de categorias (id e nome da categoria).

20.4 ENCAPSULANDO O LUCENE COM HIBERNATE SEARCH 247


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

20.5 CONFIGURAÇÃO DO HIBERNATE SEARCH


Já definimos o documento do Lucene através das anotações do Hibernate Search. Agora só falta
definir onde queremos persistir os documentos. Vamos gravar os documentos no HD
( FSDirectoryProvider ). Além disso, vamos informar também o Analyzer que deve ser utilizado
para o processo de indexação.

Todas essas configurações devem ser realizadas no persistence.xml. Aqui temos as que são
necessárias para a nossa aplicação:

<property name="hibernate.search.default.directory_provider"
value="filesystem" />

<property name="hibernate.search.default.indexBase"
value="lucene/indexes" />

<property name="hibernate.search.analyzer"
value="org.apache.lucene.analysis.standard.StandardAnalyzer"/>

Todas essas configurações são específicas para o Hibernate Search e vão ser ignoradas por qualquer
outra implementação da JPA 2 que venha a ser utilizada. Vamos detalhar elas:

hibernate.search.default.directory_provider indica onde os índices vão ser


armazenados. Estamos utilizando o filesystem porque queremos armazená-los como
arquivos. Para guardá-los apenas em memória, poderíamos utilizar o ram .

hibernate.search.default.indexBase indica qual pasta deve ser usada como base para
armazenar os índices.

hibernate.search.analyzer indica qual Analyzer deve ser utilizado.

20.6 EXERCÍCIOS: INDEXAÇÃO DAS MOVIMENTAÇÕES

248 20.5 CONFIGURAÇÃO DO HIBERNATE SEARCH


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

1. Primeiramente vamos adicionar as configurações necessárias ao persistence.xml para


habilitarmos o uso do Hibernate Search. Dessa maneira, teremos o seguinte código no
persistence.xml :

<property name="hibernate.search.default.directory_provider"
value="filesystem" />

<property name="hibernate.search.default.indexBase"
value="/home/jpaXXXX/workspace/fj25-financas-web/WebContent/WEB-INF/
lucene/indexes" />

<property name="hibernate.search.analyzer"
value="org.apache.lucene.analysis.standard.StandardAnalyzer"/>

Onde está escrito jpaXXXX, altere pelo login da sua turma.

2. Agora, vamos adicionar as anotações necessárias para configurarmos nossas classes a serem
indexadas através do Hibernate Search. No nosso caso, queremos indexar a descrição da
movimentação. Vamos adicionar as anotações @Indexed na classe Movimentacao e @Field no
atributo descricao . Os imports devem ser realizados do pacote
org.hibernate.search.annotations .

package br.com.caelum.financas.modelo;

//import omitidos

//outras anotacoes
@Entity
@Indexed
public class Movimentacao {

//outros atributos aqui.

@Field
private String descricao;

//outros atributos e métodos omitidos


}

3. No banco de dados já temos algumas categorias associadas às movimentações. Precisamos fazer com
que o Hibernate Search consiga indexar todas essas categorias.

Para tal já preparamos uma classe SearchIndexacaoBean . Nela injete o EntityManager :

@Inject
EntityManager manager;

A classe SearchIndexacaoBean já fornece um método utilitário indexar que vamos usar na


interface gráfica para indexar as categorias. Adicione no método:
public void indexar() throws InterruptedException {
FullTextEntityManager fullTextEntityManager =
Search.getFullTextEntityManager(manager);
fullTextEntityManager.createIndexer().startAndWait();
}

20.6 EXERCÍCIOS: INDEXAÇÃO DAS MOVIMENTAÇÕES 249


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

4. Inicie o servidor, vá até o navegador e acesse: http://localhost:8080/fj25-financas-


web/searchIndexacao.xhtml

Aperte o botão para inicializar a indexação. Depois atualize o projeto no Eclipse e verifique a pasta
WebContent/WEB-INF . Nela deve se encontrar uma pasta lucene que possui os documentos
criados na indexação.

20.7 BUSCANDO ATRAVÉS DO HIBERNATE SEARCH


Vamos agora tentar buscar algumas movimentações pela descrição. Para isso, vamos usar um tipo de
objeto parecido com o EntityManager , mas com capacidade de realizar buscas baseadas nos
documentos do Lucene. O Hibernate Search vem com uma classe chamada Search , que consegue
recuperar um EntityManager para busca textual, cuja classe é FullTextEntityManager , baseado no
EntityManager comum:

EntityManager em = ...
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);

Antes de criarmos a nossa busca de verdade, precisamos de um objeto que consiga pegar os termos
pesquisados e aplicar as mesmas regras que foram utilizadas durante o processo de indexação. Caso as
regras aplicadas para o processo de busca sejam diferentes, fatalmente o resultado não vai ser o esperado,
pois será realizada uma busca por termos que não foram indexados. Para isso, utilizamos um objeto do
tipo QueryParser :

FullTextEntityManager fullTextEntityManager =
Search.getFullTextEntityManager(manager);
Analyzer analyzer = fullTextEntityManager.getSearchFactory()
.getAnalyzer( Movimentacao.class );
QueryParser parser = new QueryParser("descricao",analyzer);

Passamos alguns parâmetros para o QueryParser . O primeiro é nome do campo que queremos
pesquisar no documento. A regra padrão utilizada para criação dos campos no documento é a do nome
do atributo anotado com @Field . No caso da Movimentacao , temos o atributo descricao anotado
com @Field , ou seja temos descricao O segundo e último parâmetro indica o Analyzer que vamos
utilizar para parsear o termo pesquisado. Para obter o Analyzer default para a classe Movimentação
usamos o método getAnalyzer() .

Com um objeto do tipo QueryParser , podemos criar uma consulta que deve ser realizada nos
documentos para trazer todos que atenderem aos critérios especificados. Através do método parse da
classe QueryParser , criamos um objeto do tipo Query que representa a nossa consulta. Vamos
apenas tomar cuidado para não confundir essa classe com a javax.persistence.Query ; o pacote
referente a essa classe Query que vamos usar é o org.apache.lucene.search :
QueryParser parser = new QueryParser("descricao",analyzer);
Query query = parser.parse("almoço");

250 20.7 BUSCANDO ATRAVÉS DO HIBERNATE SEARCH


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Para realizar a busca, vamos usar o FullTextEntityManager . Através do método


createFullTextQuery , conseguimos realizar a busca baseada na consulta. Além disso, especificamos
sobre quais objetos a busca deve ser realizada. Com isso, temos o código final:
FullTextEntityManager fullTextEM = Search.getFullTextEntityManager(em);
Analyzer analyzer = fullTextEntityManager.getSearchFactory()
.getAnalyzer( Movimentacao.class );

QueryParser parser = new QueryParser("descricao", analyzer);


Query query = parser.parse("mercado lanche");

FullTextQuery textQuery = fullTextEM.createFullTextQuery(query, Movimentacao.class);

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


for (Movimentacao movimentacao : movimentacoes) {
System.out.println(movimentacao.getDescricao());
}

É interessante notar que usamos o mesmo método getResultList usado anteriormente. Isso
porque FullTextQuery apenas implementa a interface javax.persistence.Query provida pela JPA
2.

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

20.8 EXERCÍCIOS: UTILIZANDO O HIBERNATE SEARCH PARA BUSCAS


TEXTUAIS
1. Adicione o método para realizar a busca textual dentro da classe MovimentacaoDAO . Cuidado com
os imports aqui, eles devem ser realizados dos seguintes pacotes: org.hibernate.search.jpa ,
org.apache.lucene e org.apache.lucene.queryParser.classic .

public List<Movimentacao> buscaPorDescricao(String descricao) {


FullTextEntityManager fullTextEntityManager =
Search.getFullTextEntityManager(manager);
Analyzer analyzer = fullTextEntityManager.getSearchFactory()
.getAnalyzer( Movimentacao.class );
QueryParser parser = new QueryParser("descricao",analyzer);
try {
org.apache.lucene.search.Query query = parser.parse(descricao);

20.8 EXERCÍCIOS: UTILIZANDO O HIBERNATE SEARCH PARA BUSCAS TEXTUAIS 251


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

FullTextQuery textQuery = fullTextEntityManager.createFullTextQuery(


query, Movimentacao.class);
return textQuery.getResultList();
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
}

2. Altere agora o método pesquisar do SearchPesquisaTextualBean para usar o


MovimentacaoDAO para realizar as buscas. Ele deve ficar da seguinte forma:

public void pesquisar() {


movimentacoes = movimentacaoDao.buscaPorDescricao(descricao);
}

3. Inicie o servidor, vá até o navegador e acesse: http://localhost:8080/fj25-financas-


web/searchPesquisaTextual.xhtml Digite algumas descrições, separadas por espaço, e realize as
pesquisas. Por padrão, o Lucene interpreta os espaços entre as palavras que devem ser buscadas
como operadores OR. Então ele vai realizar uma busca por um termo ou outro, trazendo os
resultados ordenados pela relevância.
4. Agora vamos brincar um pouco com o poder do lucene. Quer pesquisar por exemplo por todas
movimentações que sejam de "alimentação", mas que não tenha relação com "restaurante". Digite o
seguinte "alimentação -restaurante".
5. Quero só buscar pelo que tem "alimentacao" e "restaurante". Digite então "alimentacao AND
restaurante".

20.9 PARA SABER MAIS: ATUALIZANDO O ÍNDICE


Por padrão, toda vez que uma operação de atualização, ou seja, insert, update ou delete, é realizada
sobre um objeto, o índice automaticamente já é atualizado também. Caso essa maneira esteja
interferindo no desempenho da aplicação, pode ser configurado para que o índice seja atualizado de
maneira assíncrona. Dessa maneira, o Hibernate Search vai deixar para atualizar o índice em outro
momento. O único detalhe que deve ser analisado, é que os documentos, em determinado instante do
tempo, não vão representar fielmente o estado do seu banco de dados. Na documentação do Hibernate
Search é indicado que o projeto inicie utilizando a maneira síncrona, e, se precisar, recorrer a maneira
assíncrona.

20.10 PARA SABER MAIS: MULTIFIELDQUERYPARSER


Algumas vezes precisamos realizar a busca em mais de um campo do documento, para esses casos
precisamos de um parser que seja capaz de aplicar as regras nesse conjunto de campos. Para isso, existe a
classe MultiFieldQueryParser , que aceita um array de strings com os nomes dos campos que devem
ser pesquisados.

252 20.9 PARA SABER MAIS: ATUALIZANDO O ÍNDICE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Editora Casa do Código com livros de uma forma diferente

Editoras tradicionais pouco ligam para ebooks e novas tecnologias. Não dominam
tecnicamente o assunto para revisar os livros a fundo. Não têm anos de
experiência em didáticas com cursos.
Conheça a Casa do Código, uma editora diferente, com curadoria da Caelum e
obsessão por livros de qualidade a preços justos.

Casa do Código, ebook com preço de ebook.

20.11 INDEXANDO MAIS DE UM CAMPO


Podemos estar interessados em fazer a busca textual não apenas na descrição como também nas
categorias basta colocarmos o @Field e @IndexEmbedded nos atributos da movimentação para incluir
no documento no processo de indexação.
public class Movimentacao {
//...
@Field(indexNullAs="Nenhuma")
private String descricao;
//...
}

Mas, por exemplo, quando indexamos a descrição e as categorias da movimentação, se para nosso
usuário for mais relevante encontrar os termos da busca na descrição do que nas categorias, o que
podemos fazer? Sabemos que podemos influenciar a relevância usando o boost em nossas buscas. Se
quisermos que em nossas buscas a relevância de encontrar os termos na descrição da movimentação seja
duas vezes maior que encontrar nas categorias podemos configurar um boost para ser aplicado no
campo descrição nos índices.
@Field(indexNullAs="nenhuma", boost=@Boost(2f))
private String descricao;

Outra maneira é usando a anotação @Boost diretamente no atributo:


@Boost(1.5f)
@Field(indexNullAs="nenhuma")
private String descricao;

No caso acima o boost total da busca em descrição é 6.

20.12 PARA SABER MAIS: UTILIZANDO O HIBERNATE SEARCH DSL

20.11 INDEXANDO MAIS DE UM CAMPO 253


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

PARA FACILITAR BUSCAS AVANÇADAS


O poder trazido pelo Lucene para buscas textuais é grande, mas qual a melhor maneira de
disponibilizar isso para o usuário de nosso sistema? Criamos um help para explicar a sintaxe de busca?
Colocamos um link para a página do Lucene com os exemplos de query?

A decisão de delegar para o usuário a responsabilidade de entender a sintaxe do Lucene nem sempre
é possível, então como fazer para possibilitar uma busca como
"alimntação~0.3 familia^2 NOT trabalho"

de forma a esconder sua complexidade?

Para resolver esse problema podemos criar componentes visuais na página para que o usuário
escolha opções avançadas em menus, como:

o grau de similaridade entre os termos digitados e o conteúdo indexado o multiplicador de relevância


para os termos *o resultado da busca deve conter os termos ou os termos NÃO podem aparecer

Tendo criado os componentes, precisaríamos gerar a query para o Lucene a partir das escolhas que o
usuário fez. Escrever um código que gera uma String concatenando os termos digitados pelo usuário os
modificadores do Lucene é perfeitamente possível. Felizmente o Hibernate Search n os provê uma
ferramenta para facilitar a execução essa tarefa.

No que se refere a buscas textuais, uma alternativa ao uso do Lucene query parser é o uso do
Hibernate Search DSL . Nessa forma de busca a query é gerada a partir do QueryBuilder de forma
programática, mais próxima à API de Criteria da JPA2.

É partir do FullTextEntityManager que obtemos o QueryBuilder e para obtermos uma instância


precisamos determinar em qual a entidade indexada queremos fazer a busca.

FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(em);

QueryBuilder queryBuilder =
fullTextEntityManager.getSearchFactory().buildQueryBuilder()
.forEntity( Movimentacao.class ).get();

Para criar a query precisamos passar para o builder a String que desejamos buscar e em qual parte
do documento ( field ) desejamos fazer a busca. Se desejarmos fazer a busca pela descrição teríamos o
código:

org.apache.lucene.search.Query query = queryBuilder.keyword()


.onField("descricao")
.matching( "mercado lanche" ).createQuery();

Para fazer a busca em mais de um field basta criar a query usando o método onFields , que
aceita um ou mais campos String (um var-arg de String).

org.apache.lucene.search.Query query = queryBuilder.keyword()


.onFields("descricao", "categorias.nome")

254 20.12 PARA SABER MAIS: UTILIZANDO O HIBERNATE SEARCH DSL PARA FACILITAR BUSCAS AVANÇADAS
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

.matching( "mercado lanche" ).createQuery();

Precisamos agora fazer o wrapping da Query gerada pelo Hibernate Search DSL em uma Query da
JPA, com isso ganhamos os métodos que conhecemos, como o getResultList .

O FullTextEntityManager tem o método createFullTextQuery que é responsável por esse


processo de "embalar" a query gerada pela DSL. Esse método pede, além da query, qual (ou quais)
entidades serão utilizadas para filtrar a busca.
Query jpaQuery = fullTextEntityManager.createFullTextQuery(query,
Movimentacao.class);
List lista = jpaQuery.getResultList();

Se quisermos migrar o método de nosso DAO para utilizar o Hibernate Search DSL teremos o
código:
public List<Movimentacao> buscaMovimentacoesBaseadoNaDescricao(String texto) {
FullTextEntityManager fullTextEntityManager =
Search.getFullTextEntityManager(em);

QueryBuilder queryBuilder =
fullTextEntityManager.getSearchFactory().
buildQueryBuilder().forEntity( Movimentacao.class ).get();

org.apache.lucene.search.Query query =
queryBuilder.keyword()
.onField("descricao")
.matching(texto).createQuery();

Query jpaQuery = fullTextEntityManager


.createFullTextQuery(query, Movimentacao.class);

return jpaQuery.getResultList();
}

O problema de fazer essa mudança é que a capacidade que o usuário tinha de usar a sintaxe do
Lucene em suas buscas é perdida (no processo de tokenização os termos NOT, ^3 e outros serão
retirados).

Agora faremos a inclusão das opções de busca avançadas programaticamente. Se quisermos, por
exemplo, aumentar a relevância dos termos de uma query podemos usar o método boostedTo . O
exemplo de código abaixo ilustra o uso do método:
TermContext termContext = queryBuilder.keyword();
termContext = termContext.boostedTo(2f);
Query query = termContext.onField("descricao").matching(texto).createQuery();

Há inclusive o suporte ao fator para aplicar Fuzzy na busca:

TermContext termContext = queryBuilder.keyword();


FuzzyContext fuzzyContext = termContext.fuzzy().withThreshold(0.5f);
Query query = fuzzyContext.onField("descricao").matching(texto).createQuery();

A Hibernate Search DSL , assim como a API de Criteria da JPA2, foi concebida usando o
conceito de interface fluente. Podemos unir o boosting com Fuzzy da seguinte maneira:

20.12 PARA SABER MAIS: UTILIZANDO O HIBERNATE SEARCH DSL PARA FACILITAR BUSCAS AVANÇADAS 255
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Query query = queryBuilder.keyword()


.fuzzy().withThreshold(0.5f)
.boostedTo(3f)
.onField("descricao")
.matching(texto).createQuery();

20.13 PARA SABER MAIS: BUSCANDO TERMOS USANDO AND E NOT


Podemos usar o Hibernate Search DSL para fazer uma busca com vários termos, usando opções
diferentes para cada um deles classificando os mesmos como termos obrigatórios ou facultativos no
resultado. Podemos ainda requisitar que o termo não apareça nos resultados. Para esse fim podemos
usar o BooleanJunction:
public List<Movimentacao> busca(List<ElementoDaBusca> elementos) {
FullTextEntityManager fullTextEntityManager =
Search.getFullTextEntityManager(em);

QueryBuilder queryBuilder = fullTextEntityManager.getSearchFactory()


.buildQueryBuilder().forEntity(Movimentacao.class).get();

BooleanJunction<BooleanJunction> junction = queryBuilder.bool();

ElementoDaBusca termo0 = elementos.get(0);


ElementoDaBusca termo1 = elementos.get(1);

junction.must(criaQuery(em, termo0));
junction.must(criaQuery(em, termo1)).not();

//...
}

private Query criaQuery(EntityManager em, ElementoDaBusca elemento) {

return criaBuilder(em).keyword()
.fuzzy()
.withThreshold(elemento.getSemelhanca().getValor())
.boostedTo(elemento.getMultiplicador())
.onField("descricao")
.matching(elemento.getTexto()).createQuery();
}

256 20.13 PARA SABER MAIS: BUSCANDO TERMOS USANDO AND E NOT
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Já conhece os cursos online Alura?

A Alura oferece centenas de cursos online em sua plataforma exclusiva de


ensino que favorece o aprendizado com a qualidade reconhecida da Caelum.
Você pode escolher um curso nas áreas de Programação, Front-end, Mobile,
Design & UX, Infra e Business, com um plano que dá acesso a todos os cursos. Ex aluno da
Caelum tem 15% de desconto neste link!

Conheça os cursos online Alura.

20.13 PARA SABER MAIS: BUSCANDO TERMOS USANDO AND E NOT 257
Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

CAPÍTULO 21

APÊNDICE - HIBERNATE PURO

"O dinheiro não traz felicidade - para quem não sabe o que fazer com ele." -- Machado de Assis

21.1 ENTENDENDO A DIFERENÇA JPA E HIBERNATE


A JPA sendo uma especificação praticamente baseada no Hibernate e o Hibernate 5 sendo uma
implementação da JPA as duas tecnologias deveriam ser praticamente iguais, mas a verdade é que elas
tem diversas características diferentes.

O hibernate na maioria das vezes possui todas as funcionalidade descritas na JPA e mais um pouco,
algumas vezes ele até possui a funcionalidade descrita na JPA implementada em mais de uma maneira.

No fundo as duas tecnologias fazem a mesma coisa, porém quando olhamos para o hibernate, sem as
amarras impostas pela JPA, obtemos uma ferramenta com muito mais funcionalidades.

Algumas dessas funcionalidade a mais faz com que o hibernate se torne um forte opção na hora de
escolher uma ferramenta ORM, muitas vezes até mais usada que a JPA.

258 21 APÊNDICE - HIBERNATE PURO


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Saber inglês é muito importante em TI

O Galandra auxilia a prática de inglês através de flash cards e spaced


repetition learning. Conheça e aproveite os preços especiais.

Pratique seu inglês no Galandra.

21.2 CONFIGURAÇÃO DO HIBERNATE SEM JPA


Para configurar o Hibernate podemos não usar o arquivo persistence.xml e passar a usar um
chamado hibernate.cfg.xml. Basicamente os dois tem o mesmo propósito.

Um esqueleto de hibernate.cfg.xml:

<!DOCTYPE hibernate-configuration PUBLIC


"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
<property name="connection.url">jdbc:mysql://localhost/teste</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.username">root</property>
<property name="connection.password"></property>
<property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>

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

<mapping class="br.com.caelum.financas.modelo.Conta" />


<mapping class="br.com.caelum.financas.modelo.Movimentacao" />
</session-factory>
</hibernate-configuration>

As propriedade que passamos para o hibernate.cfg.xml são muito parecidas com as passadas para o
persistence.xml.

21.3 STARTUP E CRUD COM HIBERNATE


Ao invés de usar um EntityManager para manipular as entidades, no hibernate temos o objeto
Session , nele temos os métodos de CRUD e consultas.

Para conseguir uma session precisamos de uma SessionFactory , que por sua vez precisa de um
objeto do tipo Configuration (nas versões anteriores tínhamos que usar o

21.2 CONFIGURAÇÃO DO HIBERNATE SEM JPA 259


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

AnnotationConfiguration .

Ao criar um objeto do tipo Configuration precisamos, antes de criar a SessionFactory , fazê-lo


ler o arquivo hibernate.cfg.xml, para buscar as configurações dele.

O código final fica mais ou menos assim:


SessionFactory factory = new Configuration().configure().buildSessionFactory();
Session session = factory.openSession();

Com uma Session na mão, podemos inserir, atualizar/gerenciar, deletar e carregar objetos de
maneira muito parecida com o EntityManager :

Exemplo do método save inserindo um objeto no banco, usando transação:


Conta c = new Conta();
c.setAgencia("123");
c.setBanco("BB");
c.setNumero("1231-1");
c.setTitular("Joao da Silva");

Transaction transaction = session.beginTransaction();


session.save(c);
transaction.commit();

Uma diferença para o método persist da JPA é que o aqui no save, mesmo que o objeto tenha
um id e o campo seja @GeneratedValue o hibernate vai ignorá-lo ao invés de lançar uma exceção.

Exemplo de uso o método update para uma atualizar e gerenciar um objeto no banco:
Conta c = new Conta();
c.setId(1);
c.setAgencia("123");
c.setBanco("BB");
c.setNumero("1231-1");
c.setTitular("Outro Joao da Silva");

Transaction transaction = session.beginTransaction();


session.update(c);
transaction.commit();

Uma diferença entre o update do hibernate e o merge da JPA é que o update torna o objeto
passado como parâmetro gerenciado e o merge devolve uma cópia gerenciada, mas não gerencia o
objeto que foi passado.

Exemplo de uso do delete com hibernate em paralelo ao remove da JPA:

Conta c = new Conta();


c.setId(1);

Transaction transaction = session.beginTransaction();


session.delete(c);
transaction.commit();

Uma observação importante aqui é que diferente da JPA a entidade passada para o delete não

260 21.3 STARTUP E CRUD COM HIBERNATE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

precisa estar gerenciada.

Exemplo do uso do load no hibernate carregando um objeto do banco:


Conta conta = (Conta) session.load(Conta.class, 2l);
System.out.println(conta.getTitular());

Tabela comparativa do CRUD básico JPA e Hibernate

JPA Hibernate

persist save

merge update

remove delete

getReference load

find get

refresh refresh

As sessões ainda possuem um método a mais muito importante, é o evict . Ele tira do estado
managed um único objeto, tornando-o detached.
Movimentacao m = (Movimentacao) session.get(Movimentacao.class, 1l);
session.evict(m);

21.4 CONSULTAS COM CRITERIA DO HIBERNATE


Desde a versão 3 o Hibernate possui uma API própria para montar as pesquisas, é a Criteria. A
aceitação do mercado foi bem grande e por isso na versão 2 da JPA ela virou especificação, porém as
diferenças entre a versão do hibernate e a versão da JPA são enormes, apenas a ideia é a mesma.

Pegando algumas queries escritas no capítulo de JPQL vamos traduzi-las para criteria do hibernate.

Começando com a consulta mais simples:


public List<Movimentacao> listaTodos(){
String sql = "from Movimentacao";
Query q = entityManager.createQuery(sql);
return q.getResultList();
}

Com criteria ao invés de criar um objeto Query , criamos um objeto do tipo Criteria , passando
em qual tabela ele buscará os resultados:
Criteria criteria = session.createCriteria(Movimentacao.class);

Este objeto criado já é suficiente para devolver todas as movimentações, basta chamar o método
list :

public List<Movimentacao> listaTodos(){

21.4 CONSULTAS COM CRITERIA DO HIBERNATE 261


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Criteria c = session.createCriteria(Conta.class);
return c.list();
}

Adicionando uma cláusula para obter apenas as contas com valor maior que algum valor temos o
seguinte código na JPQL:
public List<Movimentacao> movimentacoesComValorMaiorQue(double valor){
String sql = "from Movimentacao m where m.valor > :valor";
Query q = entityManager.createQuery(sql);
q.setParameter("valor", valor);
return q.getResultList();
}

Para fazer uma consulta semelhante com criteria, devemos adicionar uma Restriction à criteria,
usando o método add e a fábrica Restrictions :

public List<Movimentacao> movimentacoesComValorMaiorQue(double valor){


Criteria criteria = session.createCriteria(Movimentacao.class);
criteria.add(Restrictions.gt("valor", valor);
return criteria.list();
}

Restrictions é uma fábrica de expressões, um pouco parecido com o CriteriaBuilder . Com


Restrictions temos diversos tipos de expressões como por exemplo:

eq => igual
lt => menor
gt => maior
le => menor igual
ge => maior igual
not => negação
like => texto igual
ilike => texto igual, ignorando maiúscula ou minúscula
between => dentro de um intervalo

Com essa lista podemos traduzir a JPQL abaixo:


public List<Movimentacao>
movimentacoesComValorMaiorQueEDescricao(double valor, String descricao){
String sql = "from Movimentacao m where m.valor > :valor " +
"and m.descricao like :descricao";
Query q = entityManager.createQuery(sql);
q.setParameter("valor", valor);
q.setParameter("descricao", descricao);
return q.getResultList();
}

Para a criteria:
public List<Movimentacao>
movimentacoesComValorMaiorQueEDescricao(double valor, String descricao){
Criteria criteria = session.createCriteria(Movimentacao.class);
criteria.add(Restrictions.gt("valor", valor));

262 21.4 CONSULTAS COM CRITERIA DO HIBERNATE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

criteria.add(Restrictions.like("descricao", descricao));
return criteria.list();
}

Até podemos adicionar paginação neste código de maneira muito simples e idêntica à JPQL:
public List<Movimentacao>
movimentacoesComValorMaiorQueEDescricao
(double valor, String descricao, int primeiro, int tamanhoDaPagina){

Criteria criteria = session.createCriteria(Movimentacao.class);


criteria.add(Restrictions.gt("valor", valor));
criteria.add(Restrictions.like("descricao", descricao));
criteria.setFirstResult(primeiro);
criteria.setMaxResults(tamanhoDaPagina);
return criteria.list();
}

Com JPQL veja como fica complicado fazer buscas condicionais:


public List<Movimentacao>
movimentacoesComValorMaiorQueEDescricao(double valor, String descricao){
String sql = "from Movimentacao m ";
if(valor > 0 || (descricao != null && !descricao.isEmpty())){
sql = sql+" where ";
}
if(valor > 0){
sql = sql + " m.valor > :valor ";
if(descricao != null && !descricao.isEmpty()){
sql = sql + " and ";
}
}
if(descricao != null && !descricao.isEmpty()){
sql = sql + " m.descricao like :descricao ";
}
Query q = entityManager.createQuery(sql);

if(valor > 0){


q.setParameter("valor", valor);
}
if(descricao != null && !descricao.isEmpty()){
q.setParameter("descricao", descricao);
}

return q.getResultList();
}

Talvez pudéssemos até melhorar algum if ou outro, mas isso também nos geraria bastante trabalho,
porém com criteria este trabalho já foi feito:
public List<Movimentacao>
movimentacoesComValorMaiorQueEDescricao(double valor, String descricao){
Criteria criteria = session.createCriteria(Movimentacao.class);
if(valor > 0){
criteria.add(Restrictions.gt("valor", valor));
}
if(descricao != null && !descricao.isEmpty()){
criteria.add(Restrictions.like("descricao", descricao));
}
return criteria.list();
}

21.4 CONSULTAS COM CRITERIA DO HIBERNATE 263


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

O hibernate ainda possui uma outra opção interessante para fazer buscas dinâmicas é a Query By
Example. Com query by example podemos passar um modelo do objeto que estamos procurando para
criteria que automaticamente o hibernate ignora os atributos não preenchidos e faz uma pesquisa por
aquilo que estiver preenchido.

Movimentacao movimentacao = new Movimentacao();


movimentacao.setDescricao("lanche de domingo");
movimentacao.setValor(new BigDecimal("30"));
Example example = Example.create(movimentacao);

Criteria criteria = session.createCriteria(Movimentacao.class);


criteria.add(example);
criteria.list();

'or' nas criterias


Com criteria uma das pequenas complicação que temos é fazer o or , pois ele exige que juntemos
duas restrições em uma só, com JPQL é muito simples, muito próximo a SQL:
public List<Movimentacao>
movimentacoesComValorMaiorQueOuPorDescricao(double valor, String descricao){
String sql = "from Movimentacao m where m.valor > :valor " +
"or m.descricao like :descricao";
Query q = entityManager.createQuery(sql);
q.setParameter("valor", valor);
q.setParameter("descricao", descricao);
return q.getResultList();
}

Já com criteria temos um passo a mais, juntar as duas restrições através da cláusula or , o or
também é obtido através da fábrica Restrictions :
public List<Movimentacao>
movimentacoesComValorMaiorQueEDescricao(double valor, String descricao){
Criteria criteria = session.createCriteria(Movimentacao.class);
Criterion criterion1 = Restrictions.gt("valor", valor);
Criterion criterion2 = Restrictions.like("descricao", descricao);
Criterion or = Restrictions.or(criterion1, criterion2);
criteria.add(or);
return criteria.list();
}

Até poderíamos reduzir o código utilizando chamadas inline e o import static do java 5:

public List<Movimentacao>
movimentacoesComValorMaiorQueEDescricao(double valor, String descricao){
Criteria criteria = session.createCriteria(Movimentacao.class);
criteria.add(or(
gt("valor", valor),
like("descricao", descricao)
));
return criteria.list();
}

Se quiséssemos adicionar várias restrição ligadas por or este código se complicaria muito, por isso
temos a disjunction , que pode ser interpretada como uma lista de restrições ligadas por or :

264 21.4 CONSULTAS COM CRITERIA DO HIBERNATE


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public List<Movimentacao> movimentacoes


(double valor, String descricao, LocalDateTime dataInicio, LocalDateTime dataFim){
Criteria criteria = session.createCriteria(Movimentacao.class);
Disjunction d = Restrictions.disjunction();
d.add(Restrictions.gt("valor", valor));
d.add(Restrictions.like("descricao", descricao));
d.add(Restrictions.between("data", dataInicio, dataFim));
criteria.add(d);
return criteria.list();
}

O código correspondente com JPQL seria:


public List<Movimentacao> movimentacoes
(double valor, String descricao, LocalDateTime dataInicio, LocalDateTime dataFim){
String sql = "from Movimentacao m where m.valor > :valor " +
" or m.descricao like :descricao "+
" or m.data between :dataInicio, :dataFim";
Query q = entityManager.createQuery(sql);
q.setParameter("valor", valor);
q.setParameter("descricao", descricao);
q.setParameter("dataInicio", dataInicio);
q.setParameter("dataFim", dataFim);
return q.getResultList();
}

Projections
Com JPQL para buscarmos a soma das movimentações usaríamos o seguinte código:
public BigDecimal somaDaMovimentacoes(){
String sql = "select sum(m.valor) from Movimentacao m"
Query q = entityManager.createQuery(sql);
return (BigDecimal) q.getSingleResult();
}

Com criteria temos que usar a classe Projections para isso, ela é uma fábrica de projeções , com
isso podemos escolher o que será retornado da pesquisa:
public BigDecimal somaDaMovimentacoes(){
Criteria criteria = session.createCriteria(Movimentacao.class);
criteria.setProjection(Projections.sum("valor"));
return (BigDecimal) criteria.uniqueResult();
}

Se precisássemos mais de um valor de retorno, por exemplo a soma e a contagem deveríamos usar
uma lista de projeções:
public Object[] somaEQuantidadeDeMovimentacoes(){
Criteria criteria = session.createCriteria(Movimentacao.class);
ProjectionList list = Projections.projectionList();
list.add(Projections.sum("valor"));
list.add(Projections.rowCount());
criteria.setProjection(list);
return (Object[]) criteria.uniqueResult();
}

O JPQL correspondente seria:

21.4 CONSULTAS COM CRITERIA DO HIBERNATE 265


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

public Object[] somaEQuantidadeDeMovimentacoes(){


String sql = "select sum(m.valor), count(m) from Movimentacao m"
Query q = entityManager.createQuery(sql);
return (Object[]) q.getSingleResult();
}

Em ambos os casos o retorno é um array de Object, na primeira posição a soma dos valores e na
segunda posição a quantidade de movimentações.

Seus livros de tecnologia parecem do século passado?

Conheça a Casa do Código, uma nova editora, com autores de destaque no


mercado, foco em ebooks (PDF, epub, mobi), preços imbatíveis e assuntos atuais.
Com a curadoria da Caelum e excelentes autores, é uma abordagem diferente
para livros de tecnologia no Brasil.

Casa do Código, Livros de Tecnologia.

21.5 RECURSOS ESPECIAIS NAS BUSCAS


Com JPQL temos um recurso muito importante que o de instanciar elementos nas consultas,
melhorando o retorno. Com criteria para termos o mesmo efeito podemos fazer algo parecido, porém
mais versátil, são os ResultTransformer 's.

Os ResultTransformer 's podem ser usados tanto com HQL como Criteria, desde que usemos a
Session do hibernate. Esta funcionalidade permite ao programador executar um código em cada
elemento da lista, ou mesmo executar um código na lista inteira antes da lista ser devolvida pelo método
list .

Pegando a consulta da seção anterior, que devolve um Object[] poderíamos devolver algo mais
prático, como por exemplo a classe abaixo:
class SomaETotal {
private BigDecimal soma;
private Long total;

public SomaETotal(BigDecimal soma, Long total) {


this.soma = soma;
this.total = total;
}
public BigDecimal getSoma() {
return soma;
}
public Long getTotal() {
return total;

266 21.5 RECURSOS ESPECIAIS NAS BUSCAS


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

}
}

Para transformar os resultados Object[] em SomaETotal temos que criar um transformador de


resultados, implementando a interface ResultTransformer do hibernate.

Essa interface nos obriga a ter um método chamado transformTuple , que serve para manipular
cada resultado separadamente e também o transformList , que trabalha com a lista inteira de uma vez.

Um exemplo de como transformaríamos o Object[] em SomaETotal :


class SomaETotalTransformer implements ResultTransformer{
public List transformList(List list) {
return list;
}
public Object transformTuple(Object[] values, String[] aliases) {
BigDecimal soma = (BigDecimal) values[0];
Long contagem = (Long) values[1];
return new SomaETotal(soma, contagem);
}
}

Cada resultado é transformado e devolvido através de um objeto SomaETotal . Poderíamos até ter
usado o segundo argumento do método transformTuple para ler o nome que o banco devolveu de
cada coluna de resultado, para fazer algo mais dinâmico.

Agora basta passar esse ResultTransformer para a Criteria (lembrando que também
funcionaria com uma Query desde que fosse através da Session .

Criteria criteria = session.createCriteria(Movimentacao.class);


ProjectionList list = Projections.projectionList();
list.add(Projections.sum("valor"));
list.add(Projections.rowCount());
criteria.setProjection(list);
criteria.setResultTransformer(new SomaETotalTransformer());
SomaETotal somaETotal = (SomaETotal) criteria.uniqueResult();

Neste caso poderíamos inclusive usar um ResultTransformer já existente no hibernate, o


Transformers.aliasToBean , este transformer pega os campos que a busca retornou e tenta chamar os
setters com aquele nome, assim não precisaríamos criar nosso próprio transformer.

Perceba que o código abaixo demos um alias para cada projection, assim o hibernate sabe qual
setter chamar.

Criteria criteria = session.createCriteria(Movimentacao.class);


ProjectionList list = Projections.projectionList();
list.add(Projections.sum("valor"), "soma");
list.add(Projections.rowCount(), "total");
criteria.setProjection(list);
criteria.setResultTransformer(Transformers.aliasToBean(SomaETotal.class));
SomaETotal somaETotal = (SomaETotal) criteria.uniqueResult();

21.6 JOINS E PRODUTO CARTESIANO

21.6 JOINS E PRODUTO CARTESIANO 267


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Com JPQL foi muito simples de realizar uma pesquisa com join.

public List<Movimentacao> movimentacoesDoTitularDaConta(String titular){


String jpql = "from Movimentacao m where m.conta.titular like :titular";
Query query = entityManager.createQuery(jpql);
query.setParameter("titular", titular);
return query.getResultList();
}

Já com criteria temos um trabalho a mais, precisamos criar uma criteria filha da criteria principal
(Não confundir com subselect), essa criteria filha é responsável pelas buscar na tabela que será feito o
join.
public List<Movimentacao> movimentacoesDoTitularDaConta(String titular){
Criteria principal = session.createCriteria(Movimentacao.class);
Criteria conta = principal.createCriteria("conta");
conta.add(Restrictions.like("titular", titular));
return principal.list();
}

Com um mesmo efeito poderíamos dar um apelido para a criteria de conta e usado a criteria
principal para adicionarmos a restrição:
Criteria principal = session.createCriteria(Movimentacao.class);
principal.createAlias("conta", "c");
principal.add(Restrictions.like("c.titular", titular));
return principal.list();

Dependendo da situação, provavelmente é mais simples realizar joins com HQL/JPQL.

Ainda no falando de join, com JPQL explicitamos a fetch do join através do join fetch :
public List<Conta> todasAsContasComMovimentacaoPreenchidas(){
String jpql = "from Conta c "+
" inner join fetch c.movimentacoes "+
" where c.titular like :titular";
Query query = entityManager.createQuery(jpql);
query.setParameter("titular", titular);
return query.getResultList();
}

Com criteria também podemos fazer isso, temos o setFetchMode para configurar o fetch do
relacionamento.
public List<Conta> todasAsContasComMovimentacaoPreenchidas(){
Criteria principal = session.createCriteria(Conta.class);
principal.add(Restrictions.like("titular", titular));
principal.setFetchMode("movimentacoes", FetchMode.JOIN);
return principal.list();
}

Os dois código são equivalentes.

21.7 SCROLLABLE RESULTS UM COMPARATIVO COM O JDBC


Um caso comum que o hibernate piora a performance da aplicação é quando temos buscas muito

268 21.7 SCROLLABLE RESULTS UM COMPARATIVO COM O JDBC


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

grandes para executar. Com hibernate cada resultado da busca acaba se tornando um objeto na
memória, com buscas grandes o consumo de memória é bem grande.

Com JDBC podemos ter um caso diferente, pois o ResultSet não devolve todos os resultado de
uma vez, o que ele devolve é um cursor para os resultados que estão no banco ainda. Cada vez que
chamamos um next no ResultSet o movemos o cursor para o próximo resultado, com isso não
precisamos guardar todos os resultados em memória, podemos processar cada um separadamente,
consumindo a memória de um resultado pode vez.

Os desenvolvedores do hibernate, percebendo esta falha, criaram, baseado no JDBC, um modelo de


cursor também, funcionando de maneira semelhante ao ResultSet , porém com uma abordagem que
tirasse proveito das funcionalidades ORM do hibernate. Estamos falando do ScrollableResults .

Para usar o ScrollableResults basta que ao invés de chamar o método list chamemos o
método scroll , ele retornará um objeto do tipo ScrollableResults .
Criteria criteria = session.createCriteria(Movimentacao.class);
ScrollableResults results = criteria.scroll();

O uso do ScrollableResults é muito parecido com o ResultSet , temos o método next que
move o cursor para o próximo resultado devolvendo um boolean caso tenha conseguido ir para o
próximo.

Criteria criteria = session.createCriteria(Movimentacao.class);


ScrollableResults results = criteria.scroll();
while(results.next()){
//usar os resultados
}

Cada linha de resultado possui métodos para recuperar as informações vindas do banco. Porém cada
linha de resultado não vêm em forma de tupla do banco (separado por colunas) igual ao ResultSet os
resultados obtidos pelo ScollableResults são entidades já em formato de objeto. Cada resultado é na
verdade um Object[] onde cada posição é preenchido com o que foi pedido na consulta, se pedimos
só Movimentacao esse array estará com apenas a primeira preenchida com uma Movimentacao .
Podemos acessar esse array através do método get ou podemos usar outro método get , mas
passando um índice que ele já retornará o conteúdo do array daquele índice.
Criteria criteria = session.createCriteria(Movimentacao.class);
ScrollableResults results = criteria.scroll();
while(results.next()){
Movimentacao m = (Movimentacao) results.get(0);
}

Caso tenhamos pedido o count e a soma, cada array de resultado terá dois objetos dentro, um no
índice 0 e outro no índice 1. No ScrollableResults se os tipos que pedimos na consulta forem
comuns, temos alguns métodos que evitam o casting.

Criteria criteria = session.createCriteria(Movimentacao.class);


ProjectionList projectionList = Projections.projectionList();

21.7 SCROLLABLE RESULTS UM COMPARATIVO COM O JDBC 269


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

projectionList.add(Projections.rowCount());
projectionList.add(Projections.sum("valor"));
criteria.setProjection(projectionList);
ScrollableResults results = criteria.scroll();
while(results.next()){
Long count = results.getLong(0);
BigDecimal soma = results.getBigDecimal(1);

Agora é a melhor hora de respirar mais tecnologia!

Se você está gostando dessa apostila, certamente vai aproveitar os cursos


online que lançamos na plataforma Alura. Você estuda a qualquer momento
com a qualidade Caelum. Programação, Mobile, Design, Infra, Front-End e
Business! Ex-aluno da Caelum tem 15% de desconto, siga o link!

Conheça a Alura Cursos Online.

21.8 SUBSELECT COM DETACHED CRITERIA


Para quem conhece SQL realizar subselect's com JPQL é muito parecido:
public List<Movimentacao> movimentacoesMaioresQueAMedia(){
Query q = entityManager.createQuery("from Movimentacao m where m.valor >="+
"(select avg(m.valor) from Movimentacao m)");
return q.getResultList();
}

Com criteria também podemos realizar subselect's, para isso temos que usar as
DetachedCriteria 's. DetachedCriteria pode ser considerada uma criteria que ainda não está presa
à uma session. Com isso podemos escrever o _subselect` com ela como se fosse uma criteria normal e
depois adicionarmos a criteria real.

Usando o mesmo exemplo da JPQL acima, mas com criteria:


public List<Movimentacao> movimentacoesMaioresQueAMedia(){
Criteria criteria = session.createCriteria(Movimentacao.class);
DetachedCriteria sub = DetachedCriteria.forClass(Movimentacao.class);
sub.setProjection(Projections.avg("valor"));
criteria.add(Property.forName("valor").ge(sub));
return criteria.list();
}

21.9 STATELESSSESSION

270 21.8 SUBSELECT COM DETACHED CRITERIA


Apostila gerada especialmente para Romero Veloso Costa Filho - romerovcf@gmail.com
.

Alguns sistema, trabalham com recebimento de arquivos online, esses arquivo podem ser xml,
posicional, CSV(comma separated values) e seus tamanhos podem chegar a ter vários _Gigas::, 2Gb,
3Gb.

Se imaginarmos um arquivo xml de movimentações, ele poderia ser algo como:

<movimentacoes>
<movimentacao>
<descricao>Movimentacao 1</descricao>
<data>2000-09-12</data>
<valor>1000.12</valor>
</movimentacao>
</movimentacoes>

Seria muito simples gravá-lo no banco:


Movimentacao m = lerMovimentacaoDoXml();
session.save(m);

Até poderíamos ter uma classe para ler movimentação por movimentação de um arquivo maior:
while(leitor.temProximaMovimentacao()){
session.save(leitor.proximaMovimentacao();
}

Porém se o arquivo que estamos lendo tiver muitas movimentações, podemos rapidamente estourar
a memória da máquina virtual. Mas por quê?

Porque cada movimentação que salvamos a session gerencia este objeto, nesse caso se na mesma
sessão salvarmos 1 milhão de objetos, a sessão gerenciará esses 1 milhão de objetos e os conservará na
memória, provavelmente desnecessariamente pois essa é uma operação em lote e não vamos mexer nos
objetos neste instante.

Nesses casos o ideal seria que pudéssemos salvar os objetos porém que eles não ficassem gerenciados.
É exatamente isso que o StatelessSession faz. Ele possui todas acesso a todas os mapeamentos que
foram feitos, porém ele não gerencia o ciclo de vida das entidades.

A criação do StatelessSession é parecida com a Session , ambas vêm da SessionFactory ,


porém para abrir uma StatelessSession temos que chamar o método openStatelessSession .
Session session = factory.openSession();
factory.openStatelessSession();

O uso da StatelessSession muda um pouco. Ao invés de termos o save usamos o método


insert , não temos o método load, apenas temos o get para carregar objetos através do `id%% e os
outros continuam iguais, exceto pelo fato que não gerenciarão os objetos.
while(leitor.temProximaMovimentacao()){
statelessSession.insert(leitor.proximaMovimentacao();
}

21.9 STATELESSSESSION 271

Você também pode gostar