Hibernate Efetivo – erros comuns e soluções
Conheça 6 práticas que podem melhorar a performance e escalabilidade da sua aplicação
Nesta última semana, dias 25, 26 e 27 de Setembro, ocorreu a sexta edição do evento COALTI em Maceió-AL, na qual tive a oportunidade de palestrar sobre Hibernate Efetivo a convite do ALJUG (Grupo de Usuários Java de Alagoas), além de ter sido super bem recebido pelo grande Miguel Lima, fundador do ALJUG.
O evento foi muito proveitoso e deu para compartilhar um pouco sobre alguns dos principais hábitos que podemos ter e erros que podemos evitar ao trabalhar com Hibernate para melhorar consideravelmente a performance e escalabilidade da aplicação. Os slides já estão no Slideshare e podem ser visualizados neste link.
Para quem não teve a chance de assistir a palestra no COALTI ou mesmo no QCONSP-2012, eu pretendo usar este espaço para resumir um pouco sobre as 6 práticas que apresento durante a palestra.
Conhecer e dominar o framework que você trabalha no dia a dia é fundamental para evitar problemas na aplicação. Isso se aplica bem ao Hibernate, pois utilizá-lo de forma indiscriminada pode acarretar problemas de performance e escalabilidade que são apresentados na forma de consultas muito lentas, muitos hits ao banco de dados, excesso de objetos em memória, OutOfMemoryError
, vazamento de conexões, o famigerado LazyInitializationException
e muitos outros.
Por este motivo, se você adotar algumas destas 6 práticas é possível que muitos dos problemas que sua aplicação tem hoje sejam evitados:
Use um pool de conexões: Um dos principais motivos para problemas de escalabilidade numa aplicação que acessa um banco de dados relacional é a ausência de um pool de conexões. Sem muito esforço é possível configurar um pool com o Hibernate e ter quase que de imediato melhorias na aplicação. Sabendo fazer ajustes finos na configuração de um pool é possível descobrir componentes do sistema que estão vazando conexões ou que seguram uma conexão por muito tempo, por exemplo;
Saiba lidar com LazyInitializationException: É importante entender como e porquê o erro
LazyInitializationException
ocorre na aplicação, além disso ter ciência que simplesmente configurar os relacionamentos para EAGER nem sempre é a melhor alternativa. Numa aplicação Web o erro se intensifica e normalmente leva a um código menos orientado a objetos e dirigido a consultas. Dessa forma, uma solução bastante difundida e recomendada é o pattern Open Session In View, na qual mantém a sessão do Hibernate aberta durante toda uma requisição;Habilite o Cache de Segundo Nível: Uma das maneiras de melhorar a performance de uma consulta é simplesmente não executá-la. Isso é possível ao habilitarmos o second level cache do Hibernate. Basicamente definimos quais entidades precisam ser mantidas em cache, depois disso após uma entidade ser carregada do banco pela primeira vez o Hibernate se encarrega de colocá-la em cache, e nas consultas seguintes por esta mesma entidade o Hibernate carregará ela do cache e não do banco. Obviamente que esta é uma visão simplista de como o cache de segundo nível funciona, portanto é importante ler a documentação do Hibernate e analisar como suas entidades e seus relacionamentos são usados na sua aplicação;
Use o Cache de Consultas: O cache de segundo nível só funciona em enquanto buscamos entidades por suas primary keys, a partir do momento que escrevemos consultas com HQL ou Criteria tendo outros atributos da entidade como filtro o cache de segundo nível não entra em ação. Para que o Hibernate coloque em cache o resultado de uma consulta nós precisamos habilitar o query cache. O mais interessante é que o Hibernate armazena apenas os IDs das entidades resultantes e não os objetos. No momento que ele precisar executar novamente aquela mesma consulta, 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;
Evitando o problema "Select N+1": Quando carregamos uma entidade que possui um relacionamento lazy e trabalhamos seu relacionamento o Hibernate dispara 2 consultas, a primeira para carregar a entidade em si e a segunda para carregar o seu relacionamento. Mas se carregarmos uma lista de entidades e acessarmos o relacionamento lazy de cada uma delas nós acabamos com várias consultas disparadas: 1 para buscar a lista de entidades e N para carregar seus relacionamentos. Nesse momento caímos no pior problema de performance ao trabalhar com Hibernate: o Select N+1. Para lidar com este problema nós temos algumas opções:
- mudar o mapeamento para EAGER ou usarmos consultas planejadas com join fetch;
- configurar o carregamento em lote das entidades/relacionamentos com
@BatchSize
. É o meio termo entre EAGER e LAZY. Com ela diminuímos o problema de N+1 para N/<batch-size> + 1; - ou podemos resumir tudo em 2 consultas com o uso do recurso
FetchMode.SUBSELECT
. Temos somente 2 consultas, mas em contrapartida a segunda consulta pode ser bem pesada;
Processamento em lote: É muito comum que aplicações precisem fazer processamentos em lotes ou inserir uma grande quantidade de objetos no banco de dados. O problema é que se usarmos a
Session
do Hibernate nós acabaremos com uma grande quantidade de objetos na first level cache, o que leva normalmente aOutOfMemoryError
na aplicação. Podemos até fazer o flush e limpar o cache em lote, mas para não termos esta preocupação, podemos usar a APIStatelessSession
, na qual não usa o cache de primeiro nível nem gerencia os objetos carregados - ela funciona como JDBC puro, mas sem abrir mão do mapeamento do nosso modelo;
Existem ainda muitas dicas e práticas que podemos seguir para melhorar a performance e escalabilidade da aplicação ao trabalhar com Hibernate, mas no geral estas 6 práticas já trarão uma melhoria significativa. O capítulo de performance do Hibernate é um bom começo para quem está tendo problemas na aplicação ou quer se aprofundar no assunto.
Você pode conhecer e aprender a contornar 5 destas práticas com mais profundidade e exemplos práticos no nosso curso de Persistência com JPA 2 e Hibernate. Além disso, tentaremos entrar em mais detalhes sobre cada uma destas práticas aqui no blog, então fique ligado e não perca mais nenhum post se inscrevendo no link "Subscribe by Email and Never Miss a Post" no rodapé do nosso blog!
E você, está tendo ou já teve problemas de performance com Hibernate ou mesmo JPA? Que práticas tem utilizado para contornar alguns dos problemas citados aqui?
Desenvolvedor e instrutor na TriadWorks