Os 7 hábitos dos desenvolvedores Hibernate e JPA altamente eficazes

Essa última semana tive a oportunidade de palestrar no RioJUG sobre JPA e Hibernate, onde fui muito bem recebido pelo Guilherme Chapiewski e Magno Cavalcante. Isso ocorreu durante o treinamento de Arquitetura Java que demos para diversos desenvolvedores da Globo.com, e onde tive o prazer de conhecer alguns desenvolvedores e arquitetos, como Vitor Pellegrino, Anselmo Alves, Wesley Silva, Alexandre Gazola, Tiago Motta, entre outros. Também vi o Ettore Luglio e o Daniel Passos.


DSC01736 DSC01709
DSC01691 DSC01750

Infelizmente durante a palestra não tive tempo de mostrar muitos recursos avançados e boas práticas do Hibernate, então vou usar este espaço para tal.

Precisamos conhecer todo pontencial de qualquer ferramenta, framework ou biblioteca que vamos usar em um projeto. Uma ferramenta boa, sem o devido conhecimento, resulta em projetos atrasados, com problemas de performance e desculpas do tipo “O problema é o [Hibernate|Struts|JSP, insira sua tecnologia aqui...], que gera uma quantidade excessiva de [queries|objetos|scriptlets|...] durante [lazy loading|requisições|...]“. Isso vale em especial para ferramentas mais antigas, como JSP e Struts 1. Hoje em dia ambas possuem recursos poderosos que auxiliam em muito o desenvolvimento, mas alguns desenvolvedores acabam não se aprofundando e desconhecem esses detalhes que podem ser vitais no uso de determinadas tecnologias.

Com o Hibernate não é diferente. É muito comum as pessoas culparem o Hibernate pela queda do banco de dados, performance das queries, número de objetos em memória, LazyInitializationException, e outros inúmeros problemas os quais em sua maioria poderiam ter sido evitados com a utilização de alguns recursos, boas práticas e bons hábitos no uso desse framework.

Sem mais demora, os 7 hábitos:

Connection Pool – Usar o pool de conexões embutido com o Hibernate é um erro comum, e a própria documentação diz que você não deve usa-lo em produção! Pode acontecer até connections leak!
A Caelum teve ótimas experiências com o C3P0, e é muito fácil configurá-lo como Provider para o Hibernate.

Second Level Cache – Todos já passamos por situações em que precisamos criar caches para as linhas de banco de dados mais acessadas. Aqui temos diversos problemas: sincronismo, gasto de memória, memory leak, tamanho do cache, política de prioridade da fila (LFU, LRU, FIFO, etc), tempo de expiração e modos de invalidar o cache. Escrever um cache eficiente e seguro é um grande trabalho, imagine ainda dar suporte a um cache distribuído e que possa se aproveitar do disco rígido para não gastar tanta memória? Esse é o papel do second level cache. Você pode usá-lo com diversos providers, sendo o EhCache um dos mais conhecidos.

Query Cache – Um recurso fantástico do Hibernate. No caso de você ter queries que são executadas inúmeras vezes, você pode pedir para o Hibernate fazer o cache do resultado desta query. O interessante é que ele não vai armazenar todos os objetos resultantes, e sim apenas suas primary keys: no momento que ele precisar executar novamente aquela query, ele já tem todos os IDs resultantes, e através destes ele consulta o second level cache, sem fazer um único hit ao banco de dados! Esse cache será invalidado quando alguma das tabelas envolvidas nesta query for atualizada, ou um determinado tempo passar.

Controle do Lazy – Algumas pessoas costumam reclamar do lazy loading, dizendo que em alguns casos teria sido melhor ele carregar tudo em uma única query. Você sempre pode redefinir o comportamento desses relacionamentos quando fizer uma query, através de um eager fetch.

Stateless Session – Algumas vezes precisamos fazer um processamento em batch de objetos, ou mesmo inserir uma quantidade grande deles na base de dados. Em muitos casos uma bulk operation é o suficiente, mas se quisermos manter a Orientação a Objetos, devemos tomar cuidado com a grande quantidade de objetos que ficarão armazenados no first level cache. A StatelessSession resolve esse problema: simplesmente não há first level cache e nenhum objeto se comportará como managed, tendo praticamente o mesmo efeito que chamar entityManager.clear() a cada operação.

Open Session in View – Na arquitetura MVC, muitas vezes renderizamos em nossa view diversas entidades do nosso modelo, e essas podem ter sido carregas pelo Hibernate. Se essas entidades possuem relacionamentos lazy, precisamos que a sessão esteja aberta no momento da renderização da View, caso contrário teremos uma LazyInitializaionException ou algum código macarrônico para carregar relacionamentos que nem sempre precisamos. Para isso devemos manter a session aberta através de um filtro, interceptador ou algum outro mecanismo. Isso resulta no pattern Open Session in View e também se aplica ao EntityManager.
O mesmo efeito pode ser obtido através de inversão de controle e injeção de dependências através da anotação @PersistenceContext, que é tratada por containers EJB3 e também por muitos frameworks web, como o Spring. O EJB3 ainda possui o conceito de um contexto de persistência extendido, quem é interessante em casos de conversações longas: o EntityManager usado será o mesmo enquanto aquele stateful session bean não for removido.

Evitando número de queries excessivas (n+1) – Se uma NotaFiscal possui muitos Items, e essa coleção é lazy, gastaremos duas queries para buscar a NotaFiscal e seus respectivos Itens. Mas se temos uma lista de NotaFiscal resultante de uma query, para cada NotaFiscal teremos uma nova query executada para todo getItems invocados. 1 query para listar NotaFiscal, N queries para pegar os relacionamentos: é o problema das n+1 queries. Você deve usar as configurações de batch-size e fetch-size para pedir ao Hibernate carregar as entidades/relacionamentos em blocos em vez de um em um. Você também pode utilizar o second level cache nesses relacionamentos, diminuindo consideravelmente o número de queries disparada.

Essas são apenas alguns dos hábitos, poderíamos ainda falar sobre o bom tratamento de exceções, o cuidado ao fechar todos os recursos abertos pelo Hibernate, o uso de queries nativas, o mapeamento de queries nativas para entidades através do ResultTransformer, filtros de coleções, dynamic insert e update, a criação do seu próprio tipo de persistência, e muitos outros. Conhecer bem o capítulo de performance do Hibernate é fundamental além de um bom começo.

44 Comentários

  1. Daniel F. Martins 28/01/2008 at 09:31 #

    Excelente post! :)

  2. cleuber 28/01/2008 at 12:43 #

    Só tenho uma dúvida, em relação à primeira recomendação. Não seria melhor usar um pool de conexões provido pelo servidor de aplicação JEE ?

  3. Paulo Silveira 28/01/2008 at 13:00 #

    Olá Cleuber!

    Tem razão, sem dúvida é mais interessante usar o pool/data source que o servidor disponibilizar. Minhas dicas aqui foram focadas no possível uso stand alone do hibernate+jpa, tanto que no Open Session in View cito a possibilidade do @PersistenceContext no caso de você se encontrar em um servidor de aplicação.

    Dentro de um servidor de aplicação Java EE você terá diversas facilidades e vantagens ao usar a JPA.

  4. Eduardo Bregaida 28/01/2008 at 22:38 #

    Show de bola Paulo =)

  5. Tiago Albineli Motta 29/01/2008 at 20:27 #

    Muito útil, post pra ser guardado nos favoritos.

  6. Vitor Pellegrino 30/01/2008 at 16:40 #

    Grande Paulo!
    Assim como foi a palestra, este foi um excelente post.

    Foi muito bom conhecer a ti e a teu irmão.
    Um grande abraço para vocês todos aí da Caelum.

    Até a próxima!

  7. Paulo Vitor 05/02/2008 at 02:03 #

    A associaão do título do post com o livro “Os 7 hábitos das pessoas altamente eficazes” foi interessante ahuhua (aliás é um livro muito bom)..
    Queria fazer alguns comentários, que vão ser mais perguntas XD
    1º – Contexto de persistência extendido seria a mesma idéia da conversasão do webbeans(que se entendi bem, é poder ter o controle sobre entidades durante N requisições, podendo-se fazer, por exemplo, um cadastro com 5 telas)?
    2º – Sobre usar o Query Cache, isso não pode me causar problemas do tipo receber um EntityNotFoundException ao tentar editar um item de uma lista que foi gerada a partir de uma consulta que utiliza cache?
    3º – Quando eu utilizo transaction-type JTA no persistence.xml, pelo que sei, eu to delegando o controle das transações ao container…mas e caso eu queira tratar uma delas(dar um transaction.begin(), comitar, etc)? existe essa possibilidade?
    Agradeço a atenção e parabéns pelo post, vou procurar ler sobre Stateless Session, eu sequer imaginava que o Hibernate tinha esse recurso..

  8. Paulo Silveira 06/02/2008 at 02:29 #

    ola Paulo Vitor! Excelente perguntas/observacoes!

    1. Isso mesmo! É a mesma idéia… e ela também já existia antes no Hibernate, e eles chamavam de Extended Session Pattern for Long Conversations:
    http://www.hibernate.org/43.html#A5

    2. Nao pode causar esse problema não, pois o hibernate invalida aquela entrada do cache quando ha uma modificacao em alguma linha nas tabelas envolvidas naquela query

    3. Voce pode usar a anotacao @Resource em um atributo do tipo UserTransaction, que o EJB3 container se encarregara de injetar uma transacao para voce antes da invocacao de metodo, e ai voce faz o controle manual como queira!

    abracos

  9. Paulo Vitor 06/02/2008 at 03:05 #

    Hum, vou testar o @Resource + UserTransaction!
    Obrigado pelas explicações Paulo!
    Ah, e parabéns, o blog tá com um conteúdo bem legal, abraço!

  10. Germano Fronza 12/02/2008 at 17:16 #

    Post muitíssimo bom!
    Valeu Paulo, este blog é o melhor

  11. Diego Carrion 17/02/2008 at 18:21 #

    Muito bom mesmo o post Paulo. Somente falta colocar um feed por favor :D

  12. Davi 13/03/2008 at 13:32 #

    Muito legal o post, Paulo.

    Uma pergunta: qual tem sido a prática com relação a JPA e DAOs? Porque tenho visto algumas pessoas defendendo que, com o EntityManager, não é necessário criar DAOs, pois o EntityManager já funcionaria como uma abstração, e ainda é um padrão.

    Qual sua opinião a respeito?

  13. Fred 03/11/2008 at 06:29 #

    Tenho a mesma duvida do Davi

  14. Paulo Silveira 03/11/2008 at 07:07 #

    Essa é uma discussão que a gente aborda no treinamento de EJB na Caelum.
    Se voce estiver usando JPA fora do servidor, certamente é uma boa pratica encapsular o acesso ao EntityManager por um DAO ou alguma outra classe.

    Se voce esta usando JPA dentro de um servidor java ee, existem 3 opcoes:
    - Usar o EntityManager diretamente, sem nenhum outro layer
    - Criar um session bean, que age como DAO, e é injetado em outros beans atraves de @EJB
    - Criar um DAO que é um POJO e não é um EJB, da maneira habitual

    Cada um tem suas vantagens e desvantagens. Nos pessoalmente nao gostamos da 1a opcao, de acessar o EntityManager diretamente sempre, apesar de existirem recursos (como named queries) que possibilitam isso de uma maneira de certa forma organizada.

  15. Felipe 20/05/2010 at 18:39 #

    Excelente!

  16. Cristiano 04/11/2010 at 00:28 #

    Bacana! Tive um problema ao executar a inserção de grandes quantidades de registros na base de dados. Acredito q a dica sobre Stateless Session será util!

  17. Marcos Hanberas 07/02/2011 at 08:52 #

    Dizer que Open Session in View faz parte dos 7 hábitos altamente eficazes não faz sentido. Este é um dos maiores anti-patterns já criado, justamente pelo fato de passar desapercebido pela maioria das pessoas. Reveja esta parte, porque muitos programadores que chegam aqui na empresa seguem a risca o que leêm no blog da Caelum.

  18. Paulo Silveira 07/02/2011 at 11:41 #

    @Marcos Hanberas. Nao entendi sua argumentacao do open session in view ser um anti pattern. Por que motivo? Alguma referência a respeito? Se é por causa do excesso de queries lazy, basta ler _um_ paragrafo abaixo e vai ver que também é falado para tomar cuidado com o excesso de queries e o problema do n+1.

  19. Marcos Hanberas 12/02/2011 at 09:54 #

    @Paulo Silveira. Paulo, fui avisado pelo estágio que você respondeu a minha colocação, desculpe não ter respondido antes. A visão do OpenSessionInFilter como antipattern não é novidade. Zubin Wadia e Dennis Byrne já alertavam sobre esse antipattern faz tempo. Na própria comunidade do Hibernate ela não é vista com bons olhos. Se isso é algo que deve ser evitado, então, não deveria constar nos 7 hábitos dos desenvolvedores Hibernate e JPA altamente eficazes. Como todo desenvolvedor que não gosta de reinventar a roda, a explicação desse cara aqui para mim é satisfatória, mas faltou casos de uso para ficar mais clara: http://www.guj.com.br/java/231652-alguem-usa-algum-padrao-diferente-de-open-session-in-view-ao-trabalhar-com-jpa

    Eu estarei off durante duas semanas, mas posso voltar a tocar no assunto sempre quando necessário. Um abraço

  20. Paulo Silveira 12/02/2011 at 11:25 #

    @Marcos

    Acho a explicacao do Flavio muito boa. Realmente o problema do N+1 pode acontecer com o excesso de lazy, que esta relacionado nao exatamente ao uso do open session in view e sim do excesso de lazy. Mas como te falei, nesse mesmo post que escrevi, 1 paragrafo abaixo do OpenSessionInView, eu falo exatamente isso: para tomar cuidado com os lazy e o n+1.

    Nao encontrei outras referencias a isso a nao ser ao livro do MyFaces que voce citou. E, no JSF, realmente o lazy se complica ainda mais, ja que, em algumas implementacoes, sao disparados muitos dos seus getters, por varias vezes, durante uma requisicao, ativando seus atributos lazys e fazendo N queries.

    Respondi la no GUJ para o Flavio.

  21. Mayko 14/02/2011 at 18:02 #

    Parabéns pelo post! agora vi que preciso melhorar minha infraestrutura utilizando o Hibernate, já perdi as contas de quantas vezes tive que utilizar o método clear para realizar operações transacionais, talvez se tivesse lido isto antes já teria pensado em algo melhor. Agora só uma pergunta. Eu costumo utilizar a implementação do SessionFactory para trabalhar com o Hibernate, sei que poderia utilizar o EntityManager mas até onde sei este não possue a API Criteria que é um recurso que eu gosto bastante, então o que eu ganho utilizando o EntityManager? outra coisa: eu utilizo o JPA para realizar o mapeamento mas não utiilizo a unidade de persistência dele e sim a do hibernate diretamente, isto tem a ver com o que você falou no primeiro hábito?

    Obrigado, faz pouco tempo que começei com o Hibernate, pouco mais de um ano, e quero melhorar muito..

    valeu Caelum, valeu Paulo

  22. Paulo Silveira 14/02/2011 at 18:11 #

    @Mayko

    Sobre usar o EntityManager/JPA, a maior vantagem vai ser utilizar um padrão, e dessa forma não ter de reaprender outra api de ORM se um dia for trocar de vendor. Para alguns isso não é motivo suficiente para usar JPA. Sobre o Criteria, na JPA2 ela está presente mas gera mixed feelings por sua tipagem forte e seu StaticMetaModel. Falei um pouco disso aqui:
    http://blog.caelum.com.br/metaprogramacao-em-java-o-papel-do-apt/

    Fico contente que o post tenha ajudado.

  23. Danilo Miranda 17/03/2011 at 09:12 #

    Olá Paulo,

    Como faço para identificar num projeto se o programador utilizou Connection Pool ou não?

    Abraço.

  24. Paulo Silveira 17/03/2011 at 12:44 #

    @Danilo

    Basta voce ver como esta a configuracao do hibernate.cfg.xml. La fica definido o uso do c3p0, por exemplo.

  25. Danilo Miranda 19/03/2011 at 16:39 #

    @Paulo

    Não encontrei este arquivo em meu projeto.
    Fiz uma pesquisa e disseram que fica aqui: WEB-INF/classes/resources/hibernate.cfg.xml

    Mas no meu projeto nem tenho esta pasta resources dentro de classes.

  26. Paulo Silveira 19/03/2011 at 16:42 #

    Ola Danilo

    Pode ser que seu hibernate esteja funcionando com o estilo antigo de configuracao, que fica em hibernate.properties em vez de hibernate.cfg.xml. Pode estar tambem em outro diretorio, o mais comum seria em WEB-INF/classes em produção, mas pode variar também se você configurar de maneiras diferentes.

  27. Fabiano Conrado 03/08/2011 at 16:30 #

    Excelente material!

  28. ricardo wolosker 10/12/2011 at 15:35 #

    é possível fazer relacionamentos de 1:1 forçando entidade fraca?

  29. Bruno 13/08/2013 at 23:31 #

    Muito bom o post!

    A propósito, estou usando o OSIV e estou tendo problemas de N+1.

    Fiz configuração de fetchsize, batchsize e second level cache. Mas mesmo assim tenho muitos hits no banco.

    Tem alguma sugestão?

    Abraços!

  30. Rodrigo 15/09/2013 at 22:39 #

    Boa noite amigo, sei que seu tópico explica muita coisa e que ele foi feito anos atrás mas estou com um problema e talvez vc possa me ajudar pois realmente nunca tinha visto sequer algo parecido:
    “A Questão é a seguinte: tenho uma aplicação war, baseada na plataforma JSF 2.0, com hibernate (JPA) e primefaces e banco de dados mysql. No geral ela funciona normalmente, porém em algumas situações as transações funcionam de forma errada e o JPA insere metade das informações ou insere não relacionadas( mais comum). Deixa eu explicar melhor, tenho uma entidade alunos que tem relacionamento com outra endereço, na maioria das vezes ocorre normalmente a inserção do novo aluno com referência ao id do endereço criado junto com o aluno, porém em determinadas situações que não sei localizar ocorre a inserção do aluno porém com id=0 do endereço e ora tem o registro do endereço corretamente na tabela e hora não é criado nem o registro. Nunca vi este tipo de erro em 4 anos trabalhando com java e JPA, nem nas versões iniciais de JPA 1.2. O erro acontece não somente neste relacionamento mais em outras entidades como fornecedor que também tem endereço, cliente e até em contas e parcelas que nada tem a haver com a tabela endereços. “

  31. douglas 14/07/2014 at 14:07 #

    Nessa foto o Paulo tá parecendo o Jean Willys

Deixe uma resposta