O perigo do relacionamento bidirecional com JPA e Hibernate

Eu já bati muito a cabeça com JPA e Hibernate para aprender que relacionamento bidirecional no geral atrapalha mais do que ajuda. A verdade é que deveríamos evitar relacionamento bidirecional (o tal do mappedBy) sempre que possível. Ele só deveria ser utilizado em último caso, quando realmente não tiver para onde correr…

Por exemplo, será que o código abaixo funcionaria em um relacionamento bidirecional?

Item curso = new Item("Curso de Java e Orientação a Objetos", 1);

Carrinho carrinho = dao.buscaPor(id);

carrinho.getItens().add(curso);

dao.atualiza(carrinho);

E se eu te dissesse que NÃO? Esse código não funcionaria!

Quando trabalhamos com bidirecional somos obrigados a escrever um código mais complicado para manter a consistência entre as entidades, o que nem sempre é fácil. Para entender do que estou falando dá uma lida no novo post do blog da TriadWorks:

>> JPA: por que você deveria evitar relacionamento bidirecional

Se você ainda insistir em usá-lo nos post acima eu te dou algumas dicas de orientação a objetos (ENCAPSULAMENTO, tan dãn!!) para manter a consistência do relacionamento. Dessa forma você diminui as chances de ter dor de cabeça.

E aí, o que achou do post?

6 hábitos com Hibernate para melhorar a performance da sua aplicação

Em Setembro tive a oportunidade de palestrar sobre Hibernate Efetivo em Maceió-AL a convite do fundador do ALJUG (Grupo de Usuários Java de Alagoas), Miguel Lima. Esta palestra também foi ministrada no evento do QCONSP 2012.

A idéia da palestra é discutir 6 hábitos/práticas que podemos ter com Hibernate para melhorar a performance e escalabilidade da aplicação. Hábitos simples como configurar um pool de conexões e até hábitos mais delicados, como configurar o cache de segundo nível e maneiras de lidar com o pior problema de performance ao trabalhar com Hibernate: Select N+1.

Para não deixar o assunto apenas na palestra e nos slides, acabamos blogando sobre as 6 práticas no blog da TriadWorks, onde fizemos um pequeno resumo de cada prática para ajudar desenvolvedores iniciantes e experientes a lidarem com estes problemas que são tão comuns e prejudiciais em muitos sistemas.

Hibernate Efetivo: Logs

Hibernate Efetivo: Logs

É uma prática muito comum quando se está aprendendo ou desenvolvendo com Hibernate exibir o SQL gerado pelo framework a cada consulta ou atualização no banco de dados. Existem inúmeros motivos para se habilitar os logs do Hibernate, mas independente do motivo, é sempre uma boa prática habilita-lo durante o desenvolvimento.

Através dos logs é possível encontrar bugs e gargalos em consultas, validar mapeamentos das entidades, determinar o número de queries para completar uma tarefa do ORM, sem falar que ver o SQL é importante para otimizar as queries através de indíces e configurações específicas do banco de dados.

Não é difícil descobrir que para habilitar os logs do SQL basta uma pequena configuração na SessionFactory do Hibernate através do arquivo hibernate.cfg.xml ou, se você estiver usando JPA, do persistence.xml:



	
		
		true
	

A saída no console seria semelhante ao SQL abaixo, o que não ajuda muito na hora de ler e entender o código:

Hibernate: select conta0_.ID as ID6_0_, conta0_.DESCRICAO as DESCRICAO6_0_, conta0_.FRANQUIA_ID as 
FRANQUIA6_6_0_, conta0_.NOME as NOME6_0_, conta0_.SALDO_INICIAL as SALDO4_6_0_, conta0_.tipo as tipo6_0_ 
from CONTA conta0_ where conta0_.ID=?

Indo um pouco mais longe, ainda é possível exibir o SQL formatado para facilitar a leitura das queries e até embutir comentários para saber exatamente o que o Hibernate está tentando fazer. Novamente, através de uma simples configuração:



	
		
		true
		true
		true
	

A saída no console melhora consideravelmente:

Hibernate:
/* load br.com.triadworks.financeiro.model.Conta */ select
        conta0_.ID as ID19_0_,
        conta0_.DESCRICAO as DESCRICAO19_0_,
        conta0_.FRANQUIA_ID as FRANQUIA6_19_0_,
        conta0_.NOME as NOME19_0_,
        conta0_.SALDO_INICIAL as SALDO4_19_0_,
        conta0_.tipo as tipo19_0_ 
    from
        CONTA conta0_ 
    where
        conta0_.ID=?

O problema é que habilitar os logs através da propriedade hibernate.show_sql=true é uma solução simples, rápida mas pouco eficaz. O SQL gerado é enviado diretamente para o System.out, o que é pouco flexível, pois todo o log ficará embaralhado com o restante dos logs das aplicações no servidor. Em desenvolvimento o problema não é tão aparente, mas em produção é onde percebemos sua limitação.

Uma solução mais apropriada para desenvolvimento e produção é utilizar um framework de logging. O Hibernate adotou o SLF4J como façade para frameworks de logging já faz um tempo, o que permite utilizar a maioria dos frameworks de mercado, como Log4j, LogBack entre outros.

Considerando que estamos usando o Log4j e que queremos exibir os logs no console, podemos configurar os logs do Hibernate no arquivo log4j.properties na raiz do classpath da sua aplicação:

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss} %5p [%-20c{1}] %m%n

log4j.rootLogger=warn, stdout

log4j.logger.org.hibernate.SQL=DEBUG  # É equivalente ao hibernate.show_sql=true

A principal vantagem de se habilitar os logs através do Log4j em vez da hibernate.show_sql é a sua flexibilidade. Com um framework de logging podemos, além de formatar e definir os níveis dos logs, direcionar a saída para onde quisermos, como o console, arquivos, sockets, enviar os logs por e-mail ou até mesmo todos de uma vez. Também é possível configurar a política para rotacionar os arquivos de logs, como quebrar os arquivos por data, tamanho e até remover arquivos antigos. Indo um pouco mais além, podemos ligar, desligar e reconfigurar os logs em runtime quando necessário, o que não possível com uso do hibernate.show_sql.

Outra dica ainda interessante, é que podemos logar os parâmetros que passamos para as queries, que ajuda bastante durante o debug. Quando utilizamos um framework de logging, no caso, o Log4j, isso se torna uma tarefa simples:

log4j.logger.org.hibernate.SQL=DEBUG  
log4j.logger.org.hibernate.type=TRACE   # Exibe os parâmetros JDBC

Certamente que isso ajuda muito em momentos de desespero para descobrir a causa de um bug na aplicação:

18:32:47 DEBUG [SQL                 ] 
    /* insert br.com.triadworks.financeiro.model.Conta
        */ insert 
        into
            CONTA
            (DESCRICAO, FRANQUIA_ID, NOME, SALDO_INICIAL, tipo) 
        values
            (?, ?, ?, ?, ?)
18:32:47 TRACE [BasicBinder         ] binding parameter [1] as [VARCHAR] - Conta nova
18:32:47 TRACE [BasicBinder         ] binding parameter [2] as [BIGINT] - 1
18:32:47 TRACE [BasicBinder         ] binding parameter [3] as [VARCHAR] - BANCO DO BRASIL
18:32:47 TRACE [BasicBinder         ] binding parameter [4] as [NUMERIC] - 5000.55
18:32:47 DEBUG [EnumType            ] Binding 'CONTA_POUPANCA' to parameter: 5

O time do Hibernate tem investido muito nas mensagens de log, tanto é que ele possui mensagens de logs bem detalhadas, legíveis e categorizadas para ajudar os desenvolvedores. É possível logar mensagens para vários de seus componentes, como instruções DDL, transações, cache de segundo nível, HQL, entre outras.

Desligue os logs em produção

Independente de qual estratégia você use para logar o SQL do Hibernate, elas devem ser utilizadas somente em desenvolvimento. É de suma importância desligar os logs do Hibernate em ambiente de produção.

Caso contrário, a aplicação irá logar um número excessivo de mensagens, o que pode gerar I/O desnecessário em produção e, consequentemente, problemas de performance. Sem falar na dificuldade para rastrear e identificar erros. Imagine, por exemplo, ter que encontrar um erro específico de negócio que ocorreu no final de semana passado em um arquivo de log com mais de 10gb de tamanho? Parece impossível de acontecer, mas é bastante comum, principalmente com o log de SQL do Hibernate habilitado.

Existem diversas estratégias para desligar os logs em produção, a mais simples, com o Log4j, é passar um argumento para JVM no startup da sua aplicação ou servidor, apontando para outro arquivo de configuração do Log4j localizado no classpath. Algo como:

-Dlog4j.configuration=log4j-producao.properties

Outra estratégia comumente usada, é substituir o arquivo log4j.properties durante o build do projeto, ou seja, ao gerar o .war da aplicação. Normalmente você terá um arquivo para cada ambiente, como produção, homologação e testes. O arquivo de desenvolvimento não difere muito do que vimos antes, já o de produção pode ser algo semelhante a este:

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss} %5p [%-20c{1}] %m%n

log4j.rootLogger=error, stdout, DAILY

# loga diariamente em um arquivo no formata myapp_errors.log.2013-12-10
log4j.appender.DAILY.Threshold=error
log4j.appender.DAILY=org.apache.log4j.DailyRollingFileAppender
log4j.appender.DAILY.File=${catalina.base}/logs/myapp/myapp_errors.log
log4j.appender.DAILY.DatePattern='.'yyyy-MM-dd
log4j.appender.DAILY.layout=org.apache.log4j.PatternLayout
log4j.appender.DAILY.layout.conversionPattern=%d{HH:mm:ss} %5p [%-20c{1}] %m%n
log4j.appender.DAILY.Encoding=UTF-8

# hibernate
log4j.logger.org.hibernate=error
log4j.logger.org.hibernate.SQL=error
# outros frameworks
log4j.logger.org.springframework=error
log4j.logger.br.com.caelum.vraptor=error
# logs da app
log4j.logger.br.com.triadworks.financeiro=info

A configuração acima irá logar somente erros no console e em arquivos, além de rotacionar os arquivos de logs diariamente, ou seja, no final de cada dia será gerado um arquivo de log com o padrão myapp_errors.log.2013-12-10. Uma simples configuração deste tipo permite rastrear erros mais facilmente, pois além de o arquivo ser datado ele também possuirá um conteúdo muito menor.

Enfim, o modus operandi em produção é logar apenas o estritamente necessário, ou seja, erros e informações que possam ajudar na rastreabilidade e identificação de problemas.

Concluindo

Desligar e gerenciar de maneira simples os logs do Hibernate em produção é importante e traz melhorias de performance. Adotar e configurar bem um framework de logging em um projeto é mais importante ainda, pois certamente todos os frameworks no projeto permitem ajustes finos nos logs, o que pode facilitar absurdamente a entender e encontrar problemas em desenvolvimento e produção.

Tirar proveito destes frameworks é muito mais simples do que você pode imaginar e pode influenciar consideravelmente na performance da sua aplicação. No curso de Persistência com JPA 2 e Hibernate da TriadWorks é abordado vários detalhes e práticas na utilização do Hibernate, entre elas o uso efetivo dos logs.

E você, como está habilitando os logs do Hibernate na sua aplicação? Está lembrando de desligar os logs em produção?