600 likes | 717 Views
Hibernate – conceitos e consultas. Jobson Ronan {jrjs@cin.ufpe.br}. 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.
E N D
Hibernate – conceitos e consultas Jobson Ronan {jrjs@cin.ufpe.br}
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ão Session session = factory.openSession(); • 3) Inicie uma transação Transaction tx = session.beginTransaction(); • 4) Grave o objeto session.save(user); • 5) Cometa a transação tx.commit(); • 6) Feche a sessão session.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 desligado Session sessionTwo = sessions.openSession(); Transaction tx = sessionTwo.beginTransaction(); sessionTwo.update(user); user.setUsername("jonny"); // objeto persistente tx.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’
BillingDetails CreditCard BankAccount Queries polimórficos • Considere a hierarquia ao lado • O query abaixo é polimórfico from BillingDetails pois 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 mapeadas from java.lang.Object • Criteria também suporta polimorfismo List res = session.createCriteria(java.lang.Object.class) .list();
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 = 'foo@hibernate.org‘ • 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) • Exemplos from Bid bid where bid.amount between 1 and 10 from Bid bid where bid.amount > 100 from User u where u.email in ( "foo@abc.org", "bar@abc.org" ) from User u where u.email is null from 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) = 'foo@abc.org' • 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 ( "foo@abc.org", "bar@abc.org" ) 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 • Inner join (ou simplesmente “join”) - apenas Itens com Bids • 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 • 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 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 Apenas Items que têm Bid • E usando um left outer join com fetch Query q = session.createQuery("from Item item leftjoinfetch item.bids"); Iterator items = q.list().iterator(); while ( items.hasNext() ) { Item item = (Item) items.next(); Bid bid = (Bid) item.getBid(); } Retorna Items 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 • Exemplo Query 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, usando from Bid bid join bid.item item where item.category.name like 'Laptop%‘ and item.successfulBid.amount > 100 • Ou ainda from Bid as bid join bid.item as itemjoinitem.categoryas 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 Produto cartesiano 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]; } 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.id Esta última sintaxe permite que se passe parâmetros (tipo, o id) para comparação.