Tecnologia

JPA/Hibernate e herança: os contras

Por: , dezembro 5, 2013


 
Olá, pessoal!
Dando continuidade a série de dicas sobre JPA/Hibernate, vamos falar hoje um pouco dos cuidados ao optar por utilizar herança entre entidades do JPA/Hibernate.


Apenas para contextualizar, o Hibernate oferece os seguintes tipos de estratégia para herança:

  • Single table
  • Joined
  • Table per class

Neste texto, vou especificamente do tipo de herança JOINED, com um caso comum de mapeamento de entidades.
Para apresentar os contras desta opção, vamos a um exemplo que deixará mais claro o entendimento. Considere as entidades Pessoa, PessoaFisica e PessoaJuridica:
[gist]https://gist.github.com/dherik/7804292[/gist]
Neste exemplo, podemos dizer que o sistema pode cadastrar as entidades PessoaFisica ou PessoaJuridica, sendo que ambas compartilham atributos da entidade Pessoa. Nesta configuração, teremos 3 tabelas: Pessoa, PessoaJuridica e PessoaFisica.
Se o sistema realizar uma pesquisa simples por PessoaFisica, usando o JPQL:
[gist]https://gist.github.com/dherik/7804299[/gist]
Teremos um SQL gerado pelo Hibernate e passado para o banco parecido com este (estou abstraindo os alias que o hibernate aplica para cada coluna das tabelas envolvidas):
[gist]https://gist.github.com/dherik/7804339[/gist]
Vamos observar o SQL. O Hibernate faz este JOIN de PessoaFisica com Pessoa pois ele tem certeza que a tabela PessoaFisica terá um registro correspondente em Pessoa. Naturalmente, o mesmo ocorrerá se pesquisar por uma PessoaJuridica.
Agora, vamos imaginar que temos uma tela que pesquisa por informações de Pessoa, independente se é PessoaJuridica ou PessoaFisica. No JPQL fazemos:
[gist]https://gist.github.com/dherik/7804379[/gist]
Agora chegamos no ponto interessante da discussão. Como será o SQL gerado pelo Hibernate? Veja:
[gist]https://gist.github.com/dherik/7804389[/gist]
Podemos observar 2 vezes a ocorrência de LEFT JOIN. Como uma Pessoa é necessariamente uma PessoaFisica ou PessoaJuridica, o Hibernate vai tentar criar a instância correta da classe no Java. Assim, por exemplo, se o desenvolvedor quiser fazer um “cast” de Pessoa (que foi retornada) para PessoaJuridica, sendo esta Pessoa uma PessoaJuridica, irá conseguir.
Este bônus de facilidade e transparência acaba também tendo os seus ônus, nos quais podemos destacar:

  • JOINs desnecessários no SQL, conforme observado no SQL anterior.
  • Se a entidade Pessoa, PessoaJuridica ou PessoaFisica tiver relacionamento EAGER com outras entidades, eles também serão acrescentados aos JOINs do SQL gerado. Mais um motivo para não usarmos EAGER.
  • Retorno de todas as informações das entidades envolvidas no SQL e não apenas de Pessoa.
  • Este problema fica mais grave com novas entidades estendendo Pessoa.

Embora faça certo sentido o Hibernate resolver desta maneira, os efeitos colaterais podem impactar negativamente na performance. O ideal era o Hibernate, tal como podemos fazer com relacionamentos, configurar um Proxy para resolver de forma LAZY esta situação, não precisando sempre fazer os JOINs e também retornar na consulta as informações das tabelas PessoaFisica e PessoaJuridica. Mas, tecnicamente isto é inviável.
A alternativa de mapeamento para este caso, onde desejamos criar um relacionamento de herança entre classes Java, é a mesma que a maioria dos desenvolvedores hoje defendem: usar composição ao invés de herança. Além das vantagens já conhecidas desta abordagem, no JPA/Hibernate temos uma simplificação do mapeamento sem as desavantagens citadas anteriormente.
Se a refatoração do mapeamento não for uma opção no momento e existirem problemas de performance, isto pode ser localmente resolvido se, ao invés de retornar a entidade Pessoa, retornar apenas os atributos desejados na consulta, conforme comentado em outro texto do blog da Dextra. Desta forma, o SQL gerado não realizará os LEFT JOINs e, naturalmente, nem retornar todos os atributos.
Agradecimentos ao Tiago Bento pela revisão do texto.
 

  • Receba nosso conteúdo em primeira mão.