Objetivos
Aprender sobre os conceitos de persistência relacionados ao Hibernate
Aprender como recuperar os dados e efetuar consultas de maneira eficientemente no Hibernate
Ciclo de vida da persistência
Como Hibernate oferece um sistema transparente de persistência, as classes não sabem de sua capacidade de persistência
Por outro lado, a aplicação que usa os objetos, os utiliza em estados diferentes Transientes, antes de serem gravados em meio
persistente Persistentes, quando estão armazenados Desligados, quando suas instâncias são manipuladas
sem afetar a base de dados
Objetos transientes
Objetos que acabaram de ser criados (com new) ainda não são persistentes Seu estado é transiente (ainda não foram armazenados no banco e
deixarão de existir assim que perderem sua referência) Session.delete() sobre um objeto persistente torna-o transiente
Instâncias transientes são não-transacionais Rollback não recupera seu estado anterior
Objetos referenciados por instâncias transientes são (por default) também transientes
Para mudar para o estado persistente é preciso Passar o objeto como argumento de um Session.save(), ou Criar a referência a partir de uma instância persistente
Objetos persistentes
Uma instância persistente é uma instância com uma identidade no banco de dados Tem uma chave primária como identificador
Podem ser Objeto criado com new e armazenado com Session.save() Objeto criado a partir da referência de uma instância
persistente Objeto obtido a partir de um query no banco
Estão sempre associados com uma Session São transacionais
Seu estado é sincronizado com o banco ao fim da transação Automatic dirty checking (transparente ao usuário) é usado
como estratégia de atualização eficiente de dados
Objetos desligados (detached) Quando a sessão é fechada (Session.close())
todas as instâncias ainda mantém seu estado, mas não estão mais sincronizadas com o banco Podem ficar obsoletas se houver mudança no banco Podem mudar de estado, que não será refletido no banco Mas, a qualquer momento, a sessão pode ser reativada, tornando
o objeto persistente e sincronizando seu estado Objetos desligados são úteis para transportar o estado de
objetos persistentes para outras camadas Camada de apresentação, em substituição aos DTOs (Data
Transfer Objects, também chamados de Value Objects)
Como tornar um objeto persistente 1) Crie o objeto e inicialize suas propriedades
User user = new User(); user.getName().setFirstname("John");user.getName().setLastname("Doe");
2) Abra uma sessãoSession session = factory.openSession();
3) Inicie uma transaçãoTransaction tx = session.beginTransaction();
4) Grave o objetosession.save(user);
5) Cometa a transaçãotx.commit();
6) Feche a sessãosession.close();
Como atualizar estado de instâncias desligadas
Quando a sessão for fechada, user torna-se uma instância desligada Qualquer alteração no seu estado não afeta o banco Para religá-lo pode-se usar update()
user.setPassword("secret"); // objeto desligadoSession sessionTwo = sessions.openSession();Transaction tx = sessionTwo.beginTransaction();sessionTwo.update(user);user.setUsername("jonny"); // objeto persistentetx.commit();sessionTwo.close();
Como recuperar um objeto persistente
A forma mais simples de recuperar um objeto é pelo identificador, usando o comando get():
Uma vez que a sessão foi fechada, o objeto é uma instância desligada, e pode ser repassada para outras camadas (apresentação, por exemplo)
Se o objeto não existir, a chamada get() retorna null
Session session = sessions.openSession(); Transaction tx = session.beginTransaction(); int userID = 1234; User user = (User) session.get(User.class, new Long(userID)); tx.commit(); session.close();
Como atualizar um objeto persistente
Qualquer objeto retornado por get() é um objeto persistente Quaisquer modificações no seu estado serão sincronizadas com o banco de
dados Hibernate automaticamente verifica e grava as mudanças ocorridas dentro de
uma sessão (automatic dirty checking) As mudanças tornam-se permanentes ao cometer a transação O objeto torna-se desligado quando a sessão fecha
Session session = sessions.openSession();Transaction tx = session.beginTransaction();int userID = 1234;User user = (User) session.get(User.class, new Long(userID));user.setPassword("secret");tx.commit();session.close();
Como tornar transiente um objeto persistente
Um objeto persistente pode tornar-se transiente se for removido do banco Para isto é preciso usar o gerente de persistência e
chamar o método delete() Quando a sessão terminar o objeto será considerado um
mero objeto Java transiente (cujo estado será perdido assim que o coletor de lixo atuar)
Session session = sessions.openSession();Transaction tx = session.beginTransaction();int userID = 1234;User user = (User) session.get(User.class, new Long(userID));session.delete(user);tx.commit();session.close();
Como tornar transiente um objeto desligado
Para tornar um objeto desligado transiente, não é preciso ligá-lo ou fazer update()
Basta chamar delete() sobre sua instância
Session session = sessions.openSession();Transaction tx = session.beginTransaction();session.delete(user);tx.commit();session.close();
API Session: ciclo de vida (Resumida) beginTransaction()
Demarca o início de uma transação. Retorna um objeto Transaction que deve chamar commit() ou rollback() no final da transação.
close() Fecha e desconecta a sessão
disconnect() Desconecta a sessão da conexão JDBC atual sem fechá-la.
reconnect() Obtém uma nova conexão JDBC para a sessão ou tenta conectar a uma
conexão JDBC, se passada como argumento flush()
Sincroniza a camada de objetos com a camada de dados. Este método é chamado automaticamente quando a transação é cometida.
Session: gerência de persistência (Resumida) save(Object)
Torna persistente o objeto passado como argumento saveOrUpdate(Object)
Insere (tornando persistente) ou atualiza o objeto passado como argumento update(Object)
Atualiza o objeto passado como argumento delete(Object instancia)
Remove os dados de um objeto do banco, tornando-o transiente load(classe, identificador)
Carrega e retorna a instância de objeto identificado pela classe e identificador refresh(Object)
Recarrega do banco os dados da instância passada como argumento evict(Object)
Remove o objeto passado como argumento do cache do sistema
Recuperação de dados
Formas de recuperar objetos Pelo identificador Navegando na árvore de objetos
[ ex: usuario.getEndereco().getCidade() ] Usando HQL Usando a API Criteria Usando SQL nativo
Recuperação por identificador Há dois métodos de Session para recuperar objetos pelo
identificador Ambos utilizam o cache, e evitam acessar o banco se não for
necessário Object get(Object id)
Devolve o objeto se existir e null se o objeto não for encontrado no banco ou no cache
Object load(Object id) Devolve o objeto ou um proxy para o objeto se existirem, ou causa
exceção se nenhum for encontrado no banco ou no cache O proxy pode apontar para objeto que ainda ou não mais existe
HQL Hibernate Query Language é um dialeto orientado a objetos
do SQL Assemelha-se a ODMG OQL e EJB-QL mas é adaptado para uso
em bancos de dados SQL Não é uma linguagem de manipulação de dados (como SQL); não
serve para inserir, remover, atualizar É usada apenas para recuperação de objetos
Exemplo
Query q = session.createQuery("from User u where u.firstname = :fname");q.setString("fname", "Max");List result = q.list();
HQL HQL suporta vários recursos úteis e avançados; entre eles
Pesquisas com navegação do grafo de objetos Recuperação seletiva de propriedades dos objetos selecionados
(sem ter que carregar objeto inteiro) Ordenação de resultados Paginação de resultados Agregação com group by, having, e funções sobre agregados
como sum, count, min e max Outer joins ao recuperar múltiplos objetos por registro Chamadas a funções SQL definidas pelo usuário Subqueries
Consulta usando Criteria (QBC) QBC = Query By Criteria Queries podem ser expressos usando uma API em vez de usar HQL
Evita uso de strings Validação é feita em tempo de compilação Enfoque mais orientado a objetos Mais difícil de ler (é Java e não HQL)
Exemplo Em HQL: from User u where u.firstname = :fname Em QBC:
Criteria criteria = session.createCriteria(User.class); criteria.add( Expression.like("firstname", "Max") ); List result = criteria.list();
Como achar os dados? Talvez o problema mais difícil de solucionar em
ORM: acesso eficiente a dados relacionais Aplicação prefere tratar os dados como um grafo de
objetos interligados por associações É mais fácil é fazer vários pequenos queries
(performance baixa) Alternativa: escrever queries para cada associação
(muito complexo e pouco flexível) Hibernate permite especificar uma estratégia de
recuperação default (no mapeamento) que pode ser sobreposta em tempo de execução
Estratégias de recuperação Fetching strategies Estratégias para qualquer associação (nos metadados e em
tempo de execução) Lazy fetching – objeto associado só é recuperado quando
chamado Eager fetching – objeto associado é recuperado através de SQL
outer join Batch fetching – acessa uma coleção de objetos pré-determinada
Lazy fetching Permite que apenas parte do grafo de objetos seja
carregado inicialmente O restante do grafo pode ser carregado à medida em
que for necessário Vai requer mais chamadas ao banco posteriormente,
mas pode ser que elas não sejam necessárias Pode ser otimizada com Batch Fetching
Deve ser a estratégia inicial para todas as associações no arquivo de mapeamento A estratégia pode depois ser sobreposta em tempo de
execução por estratégias mais ávidas
LazyInitializationException Erro comum, causado por inicialização lazy
A forma mais simples de evitá-lo, é realizar tudo dentro da sessão (o commit() sincroniza os dados e evita a inconsistência)
Sabendo-se do estado do objeto, os dados podem ser recuperados em outra sessão (quando voltar a ser persistente)
s = sessions.openSession();User u = (User) s.createQuery("from User u where u.name=?“).setString(userName).list().get(0);Map permissions = u.getPermissions();s.connection().commit();s.close();
Integer accessLevel = (Integer) permissions.get("accounts"); // Error!
Recuperação em lote (Batch fetching) Na verdade não é uma outra estratégia
É uma solução rápida para otimizar lazy fetching É um lazy menos lazy: usuário define quantos níveis de
associações devem ser carregadas Hibernate procura as outras instâncias associadas à
coleção e tenta carregá-las ao mesmo tempo Pode ser melhor que lazy simples
Menos chamadas ao banco
Eager fetching É uma solução que ajuda a reduzir a carga sobre o banco
de dados de forma mais inteligente que Batch Fetching Pode ser um a boa estratégia default
Permite que se especifique explicitamente quais objetos associados devem ser carregados juntos com o objeto que os referencia
Os objetos associados podem então ser retornados em única requisição Usa SQL outer join
Otimização de performance em Hibernate freqüentemente usa esse tipo de estratégia em tempo de execução para um query em particular
Qual estratégia usar? A seleção de uma estratégia de recuperação de dados
default é feita através de atributos no XML do arquivo de mapeamento.
Mapeamentos de coleção diferem dependendo do tipo de associações. Coleções e associações de muitos para muitos comportam-se
diferentemente de associações singulares “-to-one”.
outer-join em associações “-to-one” O atributo outer-join sobrepõe lazy fetching
Usado do lado oposto da associação O outro lado escolhe lazy fetching (ou immediate fetching por omissão)
Opções outer-join=“auto” (default).
O comportamento é lazy, se o outro lado (singular) da associação especificar, ou eager, se não especificado.
outer-join=“true” Comportamento é sempre eager para esta associação (logo diferentes associações para mesmo objeto podem ter estratégias diferentes)
outer-join=“false” Se o objeto tiver sido mapeado como lazy, ocorre lazy fetching, caso contrário, immediate fetching (SQL select)
Exemplo:<many-to-one name="item" class="Item" outer-join="true">
Em coleções Lazy
Use atributo lazy=“true” (não depende de proxy do outro lado)<set name="bids" lazy="true">
Batched Lazy Acrescente o atributo batch-size=“tamanho” (refere-se ao número
de coleções<set name="bids" lazy="true" batch-size="9">
Eager Use atributo outer-join=“true” Evite usar como opção default
Consultas HQL O query mais simples possível
Os resultados de um query podem ser lidos em “páginas” O query abaixo lê 10 páginas, a partir da primeira página
Listagem de resultados
Query q1 = session.createQuery("from User");
Query query = session.createQuery("from User u order by u.name asc");
query.setFirstResult(0);query.setMaxResults(10);List results = query.list();
List result = session.createQuery("from User").list();
Passagem de parâmetros Evite montar queries via concatenação de strings
Risco de segurança: basta esquecer de fechar a aspa Longos queries ficam ilegíveis
O ideal é passar parâmetros que serão ligados ao query Parâmetros podem ser passados duas formas
Por nome de variável Por ordem (como PreparedStatement em java.sql)
Passagem de parâmetros
Parâmetros passados por nome No query, o nome deve ser precedido de um “:”
Parâmetros por posição
String queryString = "from Item item where “ + “item.description like :searchString";
String queryString = "from Item item "+ "where item.description like ? "+ "and item.date > ?";
List result = session.createQuery(queryString) .setString(0, searchString) .setDate(1, minDate) .list();
Queries chamados pelo nome
Queries não precisam aparecer no código Na verdade, muitas vezes é melhor que não apareçam Podem ficar nos metadados e serem chamados pelo nome
Para chamar um query pelo nome, use getNamedQuery()
Mas antes, é preciso que ele seja declarado em algum arquivo de mapeamento (ex: Item.hbm.xml):
List result = session.getNamedQuery("findItemsByDescription") .setString("description", description).list();
<query name="findItemsByDescription"><![CDATA[from Item item where item.description like :description
]]></query>
Aliases Aliases (apelidos) são usados para que se possa ter uma
referência para acessar propriedades das instâncias recuperadas
from Livro
é suficiente para recuperar todos os livros Um alias é declarado da seguinte forma
from Livro as livro Mas o as é opcional
from Livro livro Exemplo: para testar as propriedades em um where
from Livro livrowhere livro.codigo = ‘005.133’
Queries polimórficos Considere a hierarquia ao lado O query abaixo é polimórfico
from BillingDetailspois os resultados são classes concretas BankAccount e CreditCard
Queries polimórficos podem ser feitos em qualquer classe, não apenas em classes mapeadas
O query abaixo retorna todas as instâncias mapeadasfrom java.lang.Object
Criteria também suporta polimorfismoList res = session.createCriteria(java.lang.Object.class) .list();
BillingDetails
CreditCard BankAccount
Mais HQL: Restrições (where) Como em SQL, HQL define restrições de um query através da
cláusula where, que suporta várias expressões. Por exemplo, a expressão de equivalência:
from User u where u.email = '[email protected]‘ A expressão where pode ter resultado true, false ou null
lógica ternária HQL suporta os mesmos operadores que SQL Queries Criteria: expressões na classe Expression
Não há suporte para expressões aritméticas via API (só via HQL) Operadores do HQL:
Comparação: =, <>, <, >, >=, <=, between, not between, in, not in Nulidade: is null, is not null Aritméticos: +, -, /, *, %, parênteses
Mais HQL: Restrições (where)
Exemplosfrom Bid bid where bid.amount between 1 and 10from Bid bid where bid.amount > 100from User u where u.email in ( "[email protected]", "[email protected]" )from User u where u.email is nullfrom Bid bid where ( bid.amount / 0.71 ) - 100.0 > 0.0
Mais HQL: strings O operador like funciona da mesma forma que SQL
O símbolo _ representa um caractere O símbolo % representa vários caracteres
Exemplos HQL: from User u where u.firstname like "G%" Criteria: session.createCriteria(User.class)
.add(Expression.like("firstname","G%")) Pode-se usar funções SQL se banco suportar
from User u where lower(u.email) = '[email protected]' Não há um operador padrão de concatenação de strings
É preciso usar recursos do banco nativo. Exemplo:
where (u.fname || ' ' || u.lname) like 'G% K%'
Mais HQL Operadores lógicos: and, or, parênteses, etc. servem para
argupar expressões
Ordenação: Semelhante a SQL: order by asc – ascendente (default) desc – descendente
from User user where ( user.firstname like "G%" and user.lastname like "K%" ) or user.email in ( "[email protected]", "[email protected]" )
from User u order by u.lname asc, u.fname asc
Joins Joins (junções) são usados para combinar dados de duas ou mais
tabelas relacionadas Há quatro tipos de joins possíveis
Inner join Left outer join Right outer join Full join
Além disso, há dois estilos em SQL ANSI: junção de tabelas através de propriedade comum com condição de join
na cláusula on Theta: produto cartesiano de tabelas com condição de join na cláusula where
Joins em HQL são bem mais simples que em SQL
SQL: ANSI inner join e left outer join
Right outer join (“right join”) - permite valores nulos na tabela esquerda (inclui Bids sem Itens mas não itens sem Bids) – não faz sentido neste caso (lance sem item)
Full join - permite valores nulos nas duas tabelas (inclui Bids que não têm Itens e itens que não têm Bids) – também não faz sentido neste caso
Inner join (ou simplesmente “join”) - apenas Itens com Bids
Left outer join (ou “left join”) - Inclui também Itens que não têm Bids
Joins em HQL
Em Hibernate, geralmente condições de join não são especificadas explicitamente Elas podem ser deduzidas das associações entre objetos
automaticamente Queries ficam mais simples e legíveis
Quatro maneiras de expressar joins em HQL Fetch join (outer, agressivo) na cláusula from Join comum (inner, lazy) na cláusula from Join theta-style (produto cartesiano) na cláusula where Join por associação explícita
Fetch join Recupera objeto inteiro (com associações inicializadas.) Exemplo HQL:
from Item item left join fetch item.bids where item.description like '%gc%‘
Retorna lista de objetos com coleções já inicializadas
Join simples (inner) com alias Freqüentemente é necessário aplicar restrições a tabelas combinadas
com join. Isto pode ser feito atribuíndo um alias ao join:
from Item item join item.bids bid where item.description like '%gc%‘ and bid.amount > 100
O query acima é um inner join Em vez de uma lista de Item, retorna uma lista de Object[] Retorna uma lista de arrays {item, bid} (Se fosse um fetch join, retornaria uma
lista de Item com coleção de bids inicializada) Um Item pode aparecer múltiplas vezes (uma vez para cada Bid associado)
inner join x outer (fetch) join
Como obter os dados usando um inner join
E usando um left outer join com fetch
Query q = session.createQuery("from Item item join item.bids bid");Iterator pairs = q.list().iterator();while ( pairs.hasNext() ) { Object[] pair = (Object[]) pairs.next(); Item item = (Item) pair[0]; Bid bid = (Bid) pair[1];}
Retorna pares de referências
Query q = session.createQuery("from Item item left join fetch item.bids");Iterator items = q.list().iterator();while ( items.hasNext() ) { Item item = (Item) items.next(); Bid bid = (Bid) item.getBid();}
Retorna Items
Apenas Items que têm Bid
Todos os Items inclusive os que não têm Bid
Cláusula select Pode-se restringir os objetos retornados com uma cláusula select
Select é opcional em HQL (mas não em SQL) A ausência do select equivale ao SQL select *
Para que o inner join do exemplo anterior devolva apenas os itens (e não uma lista de Object[] com par item-bid) pode-se usar select: select item
from Item item join item.bids bid ExemploQuery q = session.createQuery("select i from Item i join i.bids b");Iterator items = q.list().iterator();while ( items.hasNext() ) { Item item = (Item) items.next();}
Agora temos uma lista de Items contendo apenas os Items que têm Bids (inner join)
Joins implícitos Ocorrem em associações *-para-um (caminho convergente) e nunca
em caminhos *-para-muitos O HQL a seguir causa três joins em SQL
from Bid bid where bid.item.category.name like 'Laptop%' and bid.item.successfulBid.amount > 100
E pode ser expresso explicitamente, usandofrom Bid bid join bid.item item where item.category.name like 'Laptop%‘ and item.successfulBid.amount > 100
Ou aindafrom Bid as bid join bid.item as item join item.category as cat join item.successfulBid as winningBid where cat.name like 'Laptop%‘ and winningBid.amount > 100
Joins implícitossendo revelados
Produtos cartesianos (theta style joins) Permite recuperar todas as combinações possíveis de instâncias de
duas ou mais classes Útil em classes não associadas
Condição de join deve estar na cláusula where Apenas inner-join pode ser usado neste caso
Exemplo
Pode-se usar uma cláusula select para filtrar os resultados
Query query = session.createQuery("from User user, LogRecord log " + "where user.username = log.username")Iterator i = query.list().iterator();while ( i.hasNext() ) { Object[] pair = (Object[]) i.next(); User user = (User) pair[0]; LogRecord log = (LogRecord) pair[1];}
Produto cartesiano
Condição de join
Comparação de identificadores Queries que comparam chaves-primárias são realizados
implicitamente em HQL É o mesmo que comparar instâncias
Os dois queries .... from Item i, User u
where i.seller = u and u.username = 'steve' from Item i, Bid b
where i.seller = b.bidder
São respectivamente equivalentes aos queries: from Item i, User u
where i.seller.id = u.id and u.username = 'steve' from Item i, Bid b
where i.seller.id = b.bidder.idEsta última sintaxe permite que se passe parâmetros (tipo, o id) para comparação.
Queries de relatórios Relatórios geralmente focam nos dados
Instâncias não são importantes, mas valores selecionados, agrupados e organizados de certas instâncias
Dependem de recursos de seleção e agrupamento Usa-se cláusulas select e group by / having.
Projeção: cláusula select Quais os dados desejados no resultado? O query abaixo seleciona três propriedades de dois objetos
Os dados serão retornados em um vetor de Object[] de tamanho 3. Os resultados não são entidades (são valores) e não são transacionais (serão
usados para leitura, apenas)
Query query = session.createQuery("select item.id, item.description, bid.amount " + "from Item item join item.bids bid where bid.amount > 100"); Iterator i = query.list().iterator(); while ( i.hasNext() ) { Object[] row = (Object[]) i.next(); Long id = (Long) row[0]; String description = (String) row[1]; BigDecimal amount = (BigDecimal) row[2]; ItemRow itemRow = new ItemRow(id, description, amount)“;}
Instanciamento dinâmico Hibernate permite o instanciamento dinâmico de objetos que são
povoados pelos resultados do query Evita o uso de vetores de objetos É necessário que exista uma classe e construtor previamente construída
Query query = session.createQuery( "select new ItemRow(item.id, item.description, bid.amount)" + "from Item item join item.bids bid where bid.amount > 100"); Iterator i = query.list().iterator(); while ( i.hasNext() ) { ItemRow row = (ItemRow) i.next(); // ... fazer alguma coisa}
class ItemRow { public ItemRow(Long id, String description, BigDecimal amount) {...} ...}
Distinct Com o uso da cláusula select, os resultados de um query não mais
tem garantia de serem distintos A seguinte query pode retornar Items repetidos
select item.description from Item item
Para evitar duplicação, use a cláusula distinctselect distinct item.description from Item item
Funções agregadas HQL suporta as seguintes funções na cláusula select
count() – retorna Integer min(), max(), sum() e avg() – retornam BigDecimal
Integer count = (Integer) session.createQuery("select count(*) from Item").uniqueResult();
BigDecimal sum = (BigDecimal) session.createQuery("select sum(item.successfulBid.amount) " +"from Item item").uniqueResult();
BigDecimal[] minmax = (BigDecimal[]) session.createQuery("select min(bid.amount), max(bid.amount) “ +"from Bid bid where bid.item.id = 1") .uniqueResult();BigDecimal min = minmax[0];BigDecimal max = minmax[1];
Agrupamento Se houver funções agregadas no select, não pode haver seleção de
outros elementos, a menos que haja uma cláusula group by Neste caso, os elementos deverão estar também no group by
select bid.item.id, count(bid), avg(bid.amount) from Bid bid where bid.item.successfulBid is null group by bid.item.id
select bidItem.id, count(bid), avg(bid.amount) from Bid bid join bid.item bidItem where bidItem.successfulBid is null group by bidItem.id
select item.id, count(bid), avg(bid.amount) from Item item fetch join item.bids bid where item.successfulBid is null group by item.id
Um fetch (outer) join; o agrupamento teve que ser feito usando item.id
Um inner join explícito
Um inner join implícito
Restrição de grupos com having Pode-se aplicar restrições no agrupamento com a cláusula having
select user.lastname, count(user) from User user group by user.lastname having user.lastname like 'A%‘
Grupos requerem o uso de pelo menos dois aliases no select (a função e a propriedade de agrupamento)
Sempre que a cláusula select tem múltiplos aliases, o Hibernate devolve o resultado como (tuplas)vetores de Object Chato de manipular; inseguro quanto ao tipo Ideal é usar select new e instanciar objetos dinamicamente
Agrupamento com instanciamento dinâmicoselect new UsuarioReport(user.lastname, count(user)) from User user group by user.lastname having user.lastname like 'A%‘