Divisions com Hibernate: uso avançado da Criteria API

Por Lucas Cavalcanti em 11/09/08

Existe uma operação, não muito conhecida, mas muitas vezes necessária, em bancos de dados chamada divisão (division). Essa operação representa o seguinte tipo de consulta: Selecione os alunos que fizeram todos os cursos. Selecione os autores em que todos os seus livros têm mais de 200 páginas. E assim por diante.

Esse tipo de consulta precisa de alguns recursos avançados do SQL, então antes de mostrar como implementá-la vamos ver como implementar consultas um pouco mais simples, usando a Subqueries e a DetachedCriteria, que nos possibilitam consultas bastante poderosas usando a api da Criteria.

Bom, vamos começar com três entidades: Aluno, Curso, e um relacionamento de muitos pra muitos entre eles representado pela entidade Matrícula.

Vamos pensar um pouquinho como fazer a seguinte consulta: “Selecionar todos os alunos que estejam cursando Matemática ou Português“. Pensando em banco de dados, podemos fazer um join entre Alunos e Matrículas, e selecionar as linhas em que o curso é matemática ou é português. Precisamos também evitar que a busca retorne alunos repetidos. Vamos fazer isso com Criteria, recebendo a lista dos cursos que eu quero que o aluno esteja cursando algum deles:


public List<Aluno> alunosCursandoAlgumDessesCursos(List<Curso> cursos) {
  Criteria criteria = session.createCriteria(Aluno.class);
  //join com as matrículas
  criteria.createCriteria("matriculas""m");
  
  //usando a disjunction para fazer um ’ou’ entre vários elementos
  Disjunction ou = Restrictions.disjunction();
  for (Curso curso : cursos) {
    ou.add(Restrictions.eq("m.curso", curso);
  }
  criteria.add(ou);

  //eliminando resultados repetidos
  criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
  return criteria.list();
}

Ou podemos fazer algo bem mais interessante, que é usar a restrição in, que retorna verdadeiro se a propriedade dada é igual a algum dos elementos da coleção que passarmos pra ela. Nesse caso trocaríamos o Disjunction por simplesmente:

criteria.add(Restrictions.in("m.curso", cursos));

Bem fácil! Agora vamos mudar só um pouquinho a consulta para: “Selecione todos os alunos que estiverem cursando Português E Matemática“. Poderíamos inocentemente mudar a Disjunction para Conjunction no método anterior. Mas isso não funciona! Por quê? Porque se fizermos isso, estaríamos mudando a consulta para algo do tipo: “Selecione os alunos que tenham uma matrícula que é em Português e em Matemática ao mesmo tempo“. E isso não é possível. Temos que mudar essa consulta para algo do tipo: “Selecione todos os alunos para os quais exista uma matricula no curso Português e exista uma matrícula no curso Matemática“.

Existe uma operação em SQL que faz exatamente isso: o exists. Ela retorna verdadeiro se a subconsulta que estiver depois dela retornar algum resultado. Para fazer isso precisamos então criar subconsultas em Criteria, e o jeito de fazer isso é usando a classe Subqueries, que fabrica Criterions que envolvem a criação de subconsultas.

Para usar qualquer método da Subqueries precisamos de uma DetachedCriteria. Essa DetachedCriteria é um tipo especial de Criteria que não precisa da session do hibernate para ser criada. Dentro dela temos acesso a todos os alias e propriedades da Criteria principal, e o uso é o mesmo que faríamos para Criterias normais.

Já que temos a Subqueries na mão, vamos implementar a consulta, recebendo a lista dos cursos que queremos que o aluno esteja matriculado em todos eles:


public List<Aluno> alunosCursandoTodosEssesCursos(List<Curso> cursos) {
  Criteria criteria = session.createCriteria(Aluno.class, "a");
  Conjunction e = Restrictions.conjunction();
  for (Curso c : cursos) {
    e.add(Subqueries.exists(
      DetachedCriteria.forClass(Matricula.class, "m")
        .setProjection(Projections.id())
        .add(Restrictions.eqProperty("a.id""m.aluno.id"))
        .add(Restrictions.eq("m.curso",c))));
  }
  criteria.add(e);
  return criteria.list();
}

Ou seja, queremos que exista uma matrícula do aluno da Criteria principal para cada curso da lista passada.

Mas vamos pensar no seguinte: Essa lista de cursos provavelmente veio de outra consulta no banco, por que não usar essa consulta, ao invés da lista de cursos?! O jeito de fazer isso é usando o operador division que falamos no começo do post. Ele é meio complicado de implementar, pois você tem que pensar meio ao contrário do normal. Por exemplo, para implementar a consulta “Selecione os alunos que estão matriculados em todos os cursos” precisamos transformá-la para: “Selecione os alunos para os quais não exista nenhum curso para o qual não exista matrícula desse aluno para esse curso“, ou seja: um aluno que não exista nenhum curso em que ele não esteja matriculado. É estranho mas é assim mesmo que é feito. A Subqueries também possui o método notExists, então podemos fazer a seguinte consulta, que traz os alunos que fazem todos os cursos:


public List<Aluno> alunosCursandoTodosOsCursos() {
  Criteria criteria = session.createCriteria(Aluno.class, "a");
  criteria.add(Subqueries.notExists(
      DetachedCriteria.forClass(Curso.class, "c")
        .setProjection(Projections.id())
        .add(Subqueries.notExists(
            DetachedCriteria.forClass(Matricula.class, "m")
              .setProjection(Projections.id())
              .add(Restrictions.eqProperty("m.curso.id""c.id"))
              .add(Restrictions.eqProperty("m.aluno.id""a.id")                
        ))
      ));
  return criteria.list();
}

Não é um bicho de sete cabeças, mas também não é nada trivial. O código fica meio poluído por causa das chamadas estáticas, mas se você fizer o import static dos métodos a coisa melhora um pouquinho.

As restrições que você tinha colocado para buscar a lista de cursos dos métodos anteriores, você pode colocar na DetachedCriteria de Cursos, que vai funcionar do jeito que é esperado. Por exemplo: “Selecione os alunos que estejam matriculados em um curso noturno” vira “Selecione os alunos para os quais não exista algum curso noturno em que ele não esteja matriculado“. Mais ainda: você pode colocar restrições pertinentes na DetachedCriteria da matrícula, que também vai funcionar da forma esperada. Por exemplo: “Selecione os alunos que estejam com a matricula paga em todos os cursos” vira “Selecione os alunos para os quais não existe algum curso em que não exista matrícula paga nesse curso“.

Existem muitos casos em que o operador division salva sua vida então, mesmo que ele seja meio complicadinho, é bom saber que ele existe e ter uma boa referência de como implementá-lo =).

Além da Subqueries, existe outra classe muito útil que fabrica Criterions e Projections relacionados a uma propriedade fixa: a Property. Vale a pena olhar o javadoc do hibernate e ver a quantidade de opções de consultas que temos disponíveis. Existe um bug no hibernate que te obriga a setar uma Projection nas DetachedCriterias quando usadas dentro das Subqueries, se isso não é feito o hibernate nos presenteia com uma NullPointerException.

Vazamento de memória e de conexões

Por Guilherme Silveira em 02/09/08

Descrevo aqui a minha aventura junto com diversos desenvolvedores da Caelum: Cauê Guerra, Filipe Sabella, Anderson Leite, Pedro Mariano e o Pedro Matiello para resolver um grande problema que ocorria em um dos nossos projetos.

Assim como quando um avião cai, não é um bug que gera um grande problema, mas sim uma série de fatores que contribuem para a dificuldade na hora de encontrar complicações estruturais de um projeto.

Vamos as fatos, que incluem bugs e vazamentos em bibliotecas famosas, profiling e open call hierarchy de eclipse.

Sintoma: o servidor web parava de responder para qualquer página, exceto a tela de login

Nesse caso, o servidor entrava na tela de login mas ao tentar submete-la ou acessar qualquer outra funcionalidade o browser não recebia resposta alguma do servidor web.

A primeira coisa a pensar era que o processador estaria a 100%, que é o caso comum em laços infinitos ou threads gulosas além da conta. Mas um comando rápido no servidor (top) mostrava que o processador estava a 0%. Claro, se estivesse a 100%, teríamos duas opções:

  1. rodar o profiler e detectar o método que estaria consumindo 100% do cpu
  2. imprimir todas as threads do sistema (com Thread.métodos estáticos) e ver o stack trace de cada uma delas… detectaríamos o mesmo ponto.

Mas como nosso problema não era esse, uma possível causa levantada pelo Rafael Cosentino foi de que poderia haver algum deadlock, travando todas as requisições. Pensando na dificuldade de detectar tal falha, e acreditando que o problema estava relacionado com o consumo de memóriaapesar de não haver OutOfMemoryErrors), decidimos usar o JProfiler.

O primeiro passo foi rodar o profiler no Stella Boleto para verificar se ele estava com algum leak, pois sabíamos que o mesmo consome uma memória razoável durante o processo de geração do boleto, devido a imagem de background de alta resolução ser carregada num BufferedImage. Memória a qual deveria ser liberada. Ao rodar o profiler, o Caue Guerra, executou diversas threads em paralelo com invocações complexas do Stella Boletos. Durante e após as execuções, não houve memory leak algum, e o garbage collector consumia os objetos corretamente.

Sem nenhum ponto especial, teríamos que fazer o profiling da aplicação inteira.

Foram várias tentativas com profiling remoto offline, o código java invocava a api de profiling para depois analisarmos os resultados em nossas máquinas, porém um bug do JProfiler com o Apache Tomcat 6.0.16 inviabilizou tal análise. Fomos então rodar a aplicação no Jetty 7 (apontando para a mesma configuração do tomcat) sem que nenhum usuário da aplicação live notasse a mudança: uma migração transparente de servlet containers executada com êxito, assim como ocorreu no GUJ.

Em paralelo, o Tomcat foi atualizado para 6.0.18, que funciona com a última versão do JProfiler corretamente e levantamos o mesmo com apenas 64 megas de ram (para estressar o garbage collector mais frequentemente). Rodando o Tomcat em outra porta, mas acessando o mesmo banco de produção, permitiu que testássemos junto ao cliente exatamente as funcionalidade que ele julgava “lentas”.

Em algum ponto durante o uso do JProfiler, o mesmo indicou que não havia nenhum deadlock de threads, bastando para ele verificar se existem threads esperando por monitores de maneira cruzadas.

Primeiro sinal de leak: Executamos algumas tarefas no sistema e vimos que o número de objetos crescia de maneira adequada, porém, ao rodar o GC, alguns objetos ficavam para trás… objetos que são entidades do projeto! É comum o número de Strings, arrays de char e de bytes crescerem, mas entidades do nosso próprio sistema? E agora? Por que isso acontece? Alguém continuou mantendo referências para objetos do projeto que não deveriam estar referenciados. Novamente o profiler vai ajudar.

Segundo sinal de leak: Cutucando mais um pouco percebemos que algumas Sessions do hibernate tambem não estavam sendo fechadas nem coletadas! Essa costuma ser uma forte indicação de vazamento de conexão. E no caso do Hibernate, isso poderia vir a gerar mais leak ainda, dado que muitas entidades ficariam presas no cache de primeiro nível, já que a sessão continuava aberta.

Objetos do hibernate apos o gc

Mas olhando a fundo, percebemos que a maior parte dessas sessões, exceto uma, estava injetada em objetos de modelo na sessao web. Isto é, são sessões usadas pelos nossos Repositories, que faz todo o sentido para o projeto e não é um memory leak, pois a sessão que era referenciada deveria estar fechada e com isso os objetos de cache de primeiro nível dele, coletados.

Sendo assim, partimos para testar funcionalidades que acreditavamos ter consumo grande de memória.

Primeiro sinal de consumo excessivo de memória: Encontramos uma funcionalidade que levantava cerca de 40 mil objetos na memória. Com isso, o consumo era de cerca de 40 megas de objetos só nesse select… mas o Tomcat estava configurado para 64 megas? O que acontecia? O consumo de memória aumentava, o garbage collector era invocado de sua maneira parcial, até que o consumo chegava ao topo, quando garbage collector full era rodado, pedindo mais memória para o sistema operacional.

Mas 64 ram não era espaço suficiente, então rodava o garbage collector full novamente, passando por todos os objetos da memória novamente, liberando mais um pouco e deixando um pouco mais de espaço, mas ainda não o suficiente. Cada vez mais, em intervalos de poucos segundos, o garbage collector era rodado e liberava menos espaço novo do que antes, tudo isso ainda na mesma requisição que usaria 40 megas… até que os espaços são tão pequenos que o processador fica a 100% de processamento, até acabar a memória e lançar o OutOfMemoryError. Nesse meio tempo, o servidor fica praticamente sem dar respostas, dado o alto uso de CPU.

Garbage collector rodando apos requisicao que criava diversos objetos

Garbage collector rodando apos requisicao que criava diversos objetos: note o garbage collector full rodado no final.

 Ok, encontramos um problema. Solução: filtrar esses 40 mil objetos para trazer menos objetos para a memória de uma vez só. No nosso caso uma simples busca por esses objetos foi o suficiente. O cliente não queria paginação e já havia recusado a paginação que havia sido feita no Sprint anterior.

Em outros casos, poderiamos usar resultados do hibernate que armazenam uma referencia somente para o objeto atual da lista, podendo garbage coletar os anteriores (uma StatelessSession, conforme discutido nos 7 hábitos dos desenvolvedores Hibernate altamente eficazes).

Esse ainda não era o grande problema, pois o nosso sintoma travava o servidordeixando o processador a 0%, e não a 100%.

Memory leak temporário encontrado: Voltando ao problema de leak anterior: o profiler mostrou que alguns objetos que estavam sobrando e não sendo garbage coletados. Com um pouco mais de procura, um heap dump mostra todas as referencias de um determinado objeto e, procurando quem referenciava aqueles que não iam para o garbage collector, encontramos que a implementação padrão do grupo Apache para a JSTL na tag c:forEach mantinha uma referencia para o último objeto iterado. Oops. Como assim?
 

c:forEach leak

c:forEach leak

A tag c:forEach, para seguir a especificação da JSTL, mantem uma referência para o último objeto iterado, mesmo após a iteração (as tags ficam em um pool do servidor de aplicação e são recicladas). Por isso, a referência era fixa e todo aquele pedaco da árvore de referÊncia dos objetos não poderia ser coletado até o proximo c:forEach usar aquela instância do pool novamente.

E agora o que isso pode causar?

Primeiro, as instâncias da tag só vão manter a referência por um tempo determinado. Quando utilizadas novamente, aquela árvore de objetos poderá ser coletada… o único problema aconteceria com um número muito grande de usuários executando uma query no servidor ao mesmo tempo. Isso não costuma acontecer em projetos de 1000 usuários, portanto não é uma preocupação no momento.

Novamente não resolvemos o problema principal, mas achamos mais um detalhe importante sobre uma biblioteca que utilizamos no dia a dia sem medo. Solução: não se preocupar pois o projeto não terá um número tão grande de usuários simultaneos para causar algum problema. Continuar usando a JSTL.

Por fim, as estatísticas do Hibernate Session Factory mostravam que raramente uma conexão se perdia. Após mais de 5000 conexões estabelecidas, três estavam abertas. Isto é, a conexão atual, e mais duas… As estatísticas do hibernate é uma das maneira mais simples de obter resultados sobre leaks de conexões, queries mal escritas ou possíveis pontos de bom uso do cache de segundo nível. Oolhando as estatísticas, confirmamos o session leak (connection leak):

c:forEach leak enquanto não apontar para outro objeto

session leak

Session leak é um dos erros mais comuns em projetos com o Hibernate. Infelizmente encontramos muitos projetos que acessam conexões em métodos estáticos, sem controle algum de quem invoca esse método e de quem libera essa sessão. Nesse projeto, o acesso a camada ORM era feita através de um Interceptador, como no Open Session in View, que é a maneira que mais gostamos de trabalhar, pois dificulta session leaks e facilita a criação de testes para seu código, já que será usado alguma maneira de inversão de controle.

Causa encontrada: Usando o Open Call Hieranchy do Eclipse, encontramos rapidamente 3 referências para abertura de sessões que não fecham ela de maneira adequada (com try/finally mal escritos)…

Solução: Abrir e fechar sessões corretamente!

Mas o que um connection leak pode causar no meu projeto? Tudo depende da configuração do seu connection pool. No caso do c3p0, o mais famoso e amplamente utilizado, existem algumas configurações que você pode fazer como, por exemplo, o número minimo de conexões no pool, c3p0.minPoolSize.

Porem, ao usar hibernate essa propriedade muda para hibernate.c3p0.min_size. Note que o hibernate
decidiu não seguir o padrão! Em uma conversa com o Diego Plentz, commiter do Hibernate e colaborador da Caelum, não encontramos rapidamente um motivo pelo qual o padrão não foi seguido. Talvez algum problema de conflito de propriedades configuradas em um mesmo arquivo, ou talvez para manter compatibilidade com o Hibernate 2.

O que acontecia nesse caso específico? A aplicação usava algumas configurações corretamente, como o minimo e máximo de conexões (nos testes de integração e de aceite, o máximo era 3). Mas uma outra configuração importantíssima estava escrita de maneira errada dentro de uma String do XML: configurar o tempo máximo que uma conexão que não está sendo usada seja eliminada, justamente para ignorar leaks. Algo como: se a conexão está idle, por, digamos, 1 minuto, considere que foi um leak, e traga ela de volta para o pool.

Causa encontrada: Como exatamente essa configuração estava sendo usada no valor errado, junto com o memory leak da implementação da JSTL, após a funcionalidade com session leak acontecer 3 vezes nos testes de integração, não existia mais nenhuma conexão disponível. O mesmo acontecia em produção com um número maior de vezes. Então as threads ficavam esperando uma conexao do connection pool.

Isso podia ser visto na visualização de threads do profiler: a cada nova requisição com o servidor sem responder, uma nova thread era criada pelo tomcat para atender a requisição, mas essa thread ficava em WAITING, isto é, ela estva esperando uma conexão ser liberada. Pela view de monitor era possível
verificar que ela aguardava algo do c3p0… a conexão… pronto! 

Threads aguardando monitores/sleeping etc

Threads aguardando monitores/sleeping etc

Encontramos o problema… as threads ficavam paradas (0% de processador) esperando por conexões que nunca chegariam (WAITING)! Um problema que veio por causa da configuração do c3p0 com o problema do connection leak. A solução é composta por duas tarefas: corrigir o session/connection leak e corrigir a configuração do c3p0. Encontramos facilmente o erro graças aos testes de integração.

Ufa! Prontos para o próximo desafio…

Caelum Stella - o cinto de utilidades para o desenvolvedor brasileiro

Por Fabio Kung em 21/05/08

stellaDurante o Falando em Java 2008 do último fim de semana (18/05/2008), anunciamos o lançamento do novo Caelum Stella.

O projeto vem para auxiliar os desenvolvedores brasileiros, suprindo algumas das necessidades comumente encontradas em aplicações desenvolvidas aqui no Brasil. Atualmente, o Caelum Stella fornece uma biblioteca de validadores, formatadores e conversores para documentos brasileiros, tais como CPF, CNPJ e PIS/PASEP.

 String cpf = "867.554.707-24";
 CPFValidator vld = new CPFValidator();
 for(ValidationMessage error : vld.invalidMessagesFor(cpf)) {
   System.out.println(error.getMessage());
 }

Há uma alternativa que lança uma exceção caso ocorra algum problema de validação:

 new CPFValidator().assertValid("867.554.707-24");

O Stella também inclui módulos extras, como o de geração de boletos bancários, adaptadores para JSF, VRaptor, JBoss Seam e Hibernate Validator. Veja um exemplo de validação para CPFs usando o Caelum Stella junto ao Hibernate Validator:

  @Entity
  public class Modelo {
    @CPF
    private String cpf;
  
    public String getCpf() {
      return cpf;
    }
  }

O módulo Stella Faces conta com alguns validadores compatíveis com a especificação JSF, que você pode adicionar aos seus componentes:


<h:inputText id="cpf" value="#{usuarioBean.cpf}">
  <stella:validateCPF/>
</h:inputText>

O Stella Boleto procura fornecer um idioma mais fluente para a geração de boletos, através do encadeamento de métodos, gerando PDFs, PNGs e em breve TXT, RTF e HTML:

  Boleto boleto = Boleto.newBoleto()
      .withBanco(banco).withDatas(datas)
      .withDescricoes("descricao 1""descricao 2""descricao 3")
      .withEmissor(emissor).withSacado(sacado)
      .withValorBoleto("200.00").withNoDocumento("1234")
      .withInstrucoes("instrucao 1""instrucao 2""instrucao 3")
      .withLocaisDePagamento("local 1""local 2");

  new BoletoGenerator(boleto).toPNG("teste.png");

Estão ainda previstas no roadmap do projeto funcionalidades como JSP taglibs, rotinas JavaScript para máscaras, validação e suporte a formulários, seleção de cidades dependente da seleção de estados, suporte a mais documentos, geração da nota fiscal eletrônica, webservices para busca de endereços através de CEP, entre muitas outras. A lista vem sendo constantemente atualizada e você pode conferi-la através deste link.

Todas estas funcionalidades estão divididas em diversos módulos dentro do Stella. Atualmente são quatro: Stella Core, Stella Hibernate, Stella Faces e Stella Boleto. Cada um com um propósito diferente, mas todos relacionados aos problemas do dia a dia recorrentes no mercado brasileiro.

Além de facilitar a vida dos desenvolvedores brasileiros, o projeto prima pela alta qualidade (extensa quantidade de testes unitários, cobertura e documentação) e facilidade de uso. Você pode conferir diversas características técnicas do projeto e dos vários módulos na página técnica, gerada pelo maven. Lá você encontra a lista de responsáveis (e respectivos emails), a ótima cobertura dos testes para cada um dos módulos e o código fonte navegável para cada um dos módulos.

Como de costume em qualquer projeto open-source, o código fonte está disponível em um repositório SVN (subversion) no sourceforge.net. Para baixar os fontes basta usar seu cliente preferido:

svn checkout http://caelum-stella.svn.sourceforge.net/svnroot/caelum-stella/trunk

Você também pode navegar pelo repositório neste link.

Para tirar as suas dúvidas, sugerir funcionalidades, apontar bugs e discutir sobre o projeto, não deixe de assinar as listas de discussão, que podem ser encontradas aqui. Se preferir, pode postar também no GUJ.

Visite, use, comente e participe do desenvolvimento do projeto!

stella.caelum.com.br

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

Por Paulo Silveira em 28/01/08

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 possues 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.