JPA com Hibernate: Herança e Mapeamentos

Essas semanas postarei algumas dicas rápidas no uso da JPA com Hibernate. São alguns pontos que sempre aparecem no desenvolvimento, referentes a performance, elegância e facilidades. Alguns tópicos já são de amplo conhecimento dos usuários do Hibernate, porém na JPA alguns deles são utilizados diferentemente.

Cuidado com herança por InheritanceType.JOINED

Aqui na Caelum, conforme discutido anteriormente, usamos herança com muito critério. Herança na JPA é mapeada com @Inheritance(strategy=InheritanceType.SINGLE_TABLE) por padrão, isto é, ele vai utilizar uma única tabela para guardar todos os dados de todas as classes filhas: não há normalização e uma coluna (o discriminator, por default DTYPE no Hibernate) será utilizada para distinguir entre as possíveis subclasses.

Muitos administradores de banco de dados reclamam dessa estratégia, sendo que a mais elegante é a InheritanceType.JOINED, onde cada classe terá uma tabela, mas sem repetir colunas. As tabelas que representam as classes filhas possuem uma chave estrangeira para a tabela que representa a mãe, normalizando o banco nesse aspecto.

O grande problema dessa estratégia são as queries polimórficas: no caso de você possuir uma classe mãe Pessoa e duas filhas PessoaFisica e PessoaJuridica, ao procurar por uma Pessoa pela sua chave primária o hibernate vai gerar um join entre todas essas tabelas (pode variar um pouco de acordo com o dialeto usado), já que não possuímos o discriminator nesse caso.

Mapear resultados em vez de trabalhar com List<Object[]>

Se executamos uma Query em que nosso select não escolhe apenas um tipo de valor a ser retornado, recebemos uma List<Object[]> como resultado. Por exemplo, um select e1.atributoInteiro, e2.atributoString from Entidade1 as e1, Entidade2 as e2 where... retornaria uma List<Object[]> em que, para cada item da lista, a primeira posição da array é um Integer referente ao atributoInteiro da Entidade1 e a segunda posição é uma String referente ao atributoString da Entidade2.

Tanto a JPA quanto o Hibernate permitem retornar qualquer tipo de objeto através da clausula de select, utilizando um construtor. Podemos mudar a query anterior para select new br.com.caelum.Bean(e1.atributoInteiro, e2.atributoString) from Entidade1 as e1, Entidade2 as e2 where... para receber um List<AlgumBean> como resultado, desde que a classe br.com.caelum.AlgumBean possua um construtor que receba um Integer e uma String. Muito mais elegante que precisar percorrer uma List<Object[]> e encher nosso código de castings. Excelente parar gerar relatórios e já devolver os dados organizados em beans específicos.

Para mapeamentos mais avançados o Hibernate possui a interface ResultTransformer e sua factory Transformers. A JPA define a SQLResultSetMapping e outras anotações, mas para o uso de native queries.

21 Comentários

  1. Rafael de F. Ferreira 04/03/2007 at 13:12 #

    Como de costume, muito bom este post.
    Eu soh nao entendi um negocio, o resultado da query com construtor eh mesmo List ou eh List?

  2. Rafael de F. Ferreira 04/03/2007 at 13:14 #

    O wordpress comeu os (menor|maior)-que… Repetindo:

    Como de costume, muito bom este post.
    Eu soh nao entendi um negocio, o resultado da query com construtor eh mesmo List<AlgumBean[]> ou eh List<AlgumBean>

  3. Paulo Silveira 04/03/2007 at 13:46 #

    Oi Rafael

    Voce tem razao, devolve List<AlgumBean>, devolver List<AlgumBean[]> foi resultado do meu copy and paste :). Ja alterei no artigo, obrigado!

  4. Paulada 05/03/2007 at 07:25 #

    Olá,

    Me corrija se eu estiver errado, mas pelo que andei lendo de mapeamento de herança na JPA, TABLE_PER_CLASS é: cada classe é mapeada em uma tabela separada e não em uma única tabela com uma coluna discriminator como você disse.
    SINGLE_TABLE é a estratégia de uma única tabela.

    Valeu.
    Paulo.

  5. Paulo Silveira 05/03/2007 at 07:53 #

    Ola Paulo

    Você está absolutamente correto. Era para colocar SINGLE_TABLE, que alias é o default que falei. Quem sabe depois de mais algumas correções esse post fica bom :).

    Ja mudei, obrigado.

  6. Paulo Silveira 06/03/2007 at 15:49 #

    Só para ficar registrado: um atual cliente estava usando JOIN_TABLE, e como a mae tinha 6 filhas estava gerando um join que excedia 32767 caracteres! Incrivel! A solucao rapida é passar para SINGLE_TABLE, a solucao bonita eh passar para relociamento, e trocar heranca por composicao.

  7. Rafael Brugnollo 27/06/2007 at 18:34 #

    Estou querendo aprender sobre o Hibernate e já li as apostilas da Caelum…. alguem tem um bom material(em português) para me indicar para eu estudar?!?

  8. MARCELLO RIBEIRO 12/07/2007 at 20:22 #

    Pessoal, aproveitando a proficiência de vcs em JPA… Alguém saberia me dizer porque em algumas consultas o JPA tentar retirar campos do tipo varchar do resultset usando getDouble e ai exceção de número inválido?

  9. Ricardo Azevedo 26/10/2007 at 09:40 #

    Olá Paulo,

    Obrigado pelo post, ajudou muito em nossos testes de JPA.

    Estamos fazendo testes com Herança com JPA e surgiu uma dúvida sobre o mapeamento de Herança com sobreposição (overlapping) , onde eu possa ter mais de uma subclasse para uma mesma classe pai. Exemplo: Pessoa (pai) que pode ser um Cliente (filha), mas também um Fornecedor (filha), ou um Funcionário (filha) que também pode ser um Vendedor (filha).

    Problema: Quando tentamos mapear isto com o relacionamento joined-class, ao criar o segundo registro filho, ocorre um erro, pois ele tenta criar novamente o registro pai.

    Uma solução que me passaram seria forçar a exclusão do pai, antes de incluir um novo filho, mas existem problemas de restrição no próprio banco.

    Pergunta: Existe alguma anotação específica para determinar esta propriedade da herança em UML (overlapping) ou existe uma forma melhor de mapear esta herança com o Hibernate?

    Obrigado e um abraço,

  10. O problema é que (1) não de podem mapear classes e tabelas, (2) o modelo de dados tem de ser criado indepentemente do de classes, porque o ponto fulcral do sistema é a base de dados, não o programa aplicativo.

  11. Uma duvida 24/10/2009 at 17:21 #

    Imagina que eu tenho Classe Pessoa,PessoaFisica,PessoaJuridica,Cliente

    Como seria esta relação?Como fazer o JPA entender que quando gravar em Cliente,deve-se gravar em Pessoa e dependendo da opcao , gravar ou em pessoa juridica ou em pessoa fisica?

  12. vitor 15/03/2011 at 11:42 #

    seguinte: uma dúvida…

    tenho uma classe Pessoa, por exemplo, e essa classe possui uma lista de endereços. tenho muitos campos na classe pessoa que não são necessários para um relatório e eu não queria trazer eles em um select, mas preciso dos endereços.

    consigo criar um VO que receba no construtor por exemplo
    (string nome, string sobrenome, List enderecos) e na query fazer
    “select new valueObj(pessoa.nome, pessoa.sobrenome,pessoa.enderecos) from …. ???

  13. Carlos Amaral 25/07/2011 at 19:36 #

    Mas a normalização é obrigatória e necessária para não haver redundância de dados.
    Exemplo : Tabela Pessoa (id, nome, apelido, cnpj, tipo)
    Todas as Pessoas do aplicativo tem um tipo : alunos, fornecedores, professores, agencias bancarias, sindicatos, escritorios de contabilidade, etc, estão nesta tabela e eu quero separa-las conforme o caso de uso.

    No caso de agencia bancária, a única diferença é que tenho que guardar o número da agencia e o banco (tabela Banco).

    Haveria aí uma tabela AgenciaBancaria(idPessoa, idBanco, numeroAgencia)
    Como proceder neste caso ? Terei 50.000 Pessoas sendo que, no máximo, 5 agencias bancárias. Vou modelar a minha tabela Pessoa com o campo numeroAgencia e deixar 49.995 registros com este campo em branco ?

    Aí é necessário esta composição, mas também é complicado fazer a união citada, pois para cada tipo haverá uma tabela com a particularidade daquele tipo.

    Como proceder ?

  14. Halley 06/03/2013 at 08:16 #

    Olá,

    Tenho uma dúvida sobre o mapeamento Join no seguinte cenário. Pessoa e Funcionário.

    Ao utilizar Join, o iden de Funcionário será o mesmo iden de Pessoa. Mas tenho a seguinte regra: Toda vez que o funcionário possui um novo cadastro na empresa (vamos imaginar que ele entrou na empresa, saiu e voltou) , teria então dois registros do funcionário, mas somente um registro de Pessoa.

    Como ficaria isso, tenho uma única pessoa vinculada a duas filhas? Gostaria de utilizar a estrutura da herança, mas sem a necessidade de ter uma relação 1 para 1 com a classe superior. (E manter a normalização)

  15. Vítor Franco 07/06/2013 at 18:34 #

    Neste caso Halley, acredito que você terá que abrir mão da herança, já que você não poderá ter dois funcionários com o mesmo ID na tabela, e usar composição. Na classe Funcionario você terá um atributo do tipo Pessoa (ManyToOne). Dessa forma você poderá possuir quantos registros desejar usando a mesma pessoa.

  16. Manoel 21/01/2015 at 17:15 #

    Como vir no exemplo acima, não encontrei um exemplo com @Inheritance ( strategy = InheritanceType . TABLE_PER_CLASS ) o que estou querendo entender, pois fiz um exemplo aqui e deu erro,

    veja

    package Modelo;

    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.Id;
    import javax.persistence.Inheritance;
    import javax.persistence.InheritanceType;

    @Entity
    @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
    public class Pessoa {

    @Id
    //@GeneratedValue
    private Long id;

    private String nome;

    public Long getId() {
    return id;
    }
    public void setId(Long id) {
    this.id = id;
    }
    public String getNome() {
    return nome;
    }
    public void setNome(String nome) {
    this.nome = nome;
    }
    }

    package Modelo;

    import javax.persistence.Entity;

    @Entity
    public class PessoaJuridica extends Pessoa{

    private String cnpj;

    public String getCnpj() {
    return cnpj;
    }
    public void setCnpj(String cnpj) {
    this.cnpj = cnpj;
    }
    }

    package Modelo;

    import javax.persistence.Entity;

    @Entity
    public class PessoaFisica extends Pessoa{

    private String cpf;

    public String getCpf() {
    return cpf;
    }
    public void setCpf(String cpf) {
    this.cpf = cpf;
    }
    }

    package Testes;

    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.Persistence;

    import Modelo.Pessoa;
    import Modelo.PessoaFisica;
    import Modelo.PessoaJuridica;

    public class AdicionaPessoa {

    public static void main(String[] args) {
    EntityManagerFactory factory = Persistence.createEntityManagerFactory(“mapeamento_bd”);
    EntityManager manager = factory.createEntityManager();

    manager.getTransaction().begin();

    Pessoa p1 = new Pessoa();
    p1.setNome(“Marcelo”);

    PessoaFisica p2 = new PessoaFisica();
    p2.setNome(“Rafael”);
    p2.setCpf(“1234”);

    PessoaJuridica p3 = new PessoaJuridica();
    p3.setNome(“k19”);
    p3.setCnpj(“5588888”);

    manager.persist(p1);
    manager.persist(p2);
    manager.persist(p3);

    manager.getTransaction().commit();

    manager.close();
    factory.close();
    }
    }

    O erros eh este, eu usei o Single Table e deu certo , mas com o Table Per Class deu errado e apresentou os erros abaixo.

    0 [main] INFO org.hibernate.cfg.annotations.Version – Hibernate Annotations 3.5.1-Final
    16 [main] INFO org.hibernate.cfg.Environment – Hibernate 3.5.1-Final
    16 [main] INFO org.hibernate.cfg.Environment – hibernate.properties not found
    16 [main] INFO org.hibernate.cfg.Environment – Bytecode provider name : javassist
    32 [main] INFO org.hibernate.cfg.Environment – using JDK 1.4 java.sql.Timestamp handling
    156 [main] INFO org.hibernate.annotations.common.Version – Hibernate Commons Annotations 3.2.0.Final
    172 [main] INFO org.hibernate.ejb.Version – Hibernate EntityManager 3.5.1-Final
    453 [main] INFO org.hibernate.cfg.AnnotationBinder – Binding entity from annotated class: Modelo.Usuario
    500 [main] INFO org.hibernate.cfg.annotations.EntityBinder – Bind entity Modelo.Usuario on table Usuario
    582 [main] INFO org.hibernate.cfg.AnnotationBinder – Binding entity from annotated class: Modelo.Fatura
    582 [main] INFO org.hibernate.cfg.annotations.EntityBinder – Bind entity Modelo.Fatura on table Fatura
    628 [main] INFO org.hibernate.cfg.AnnotationBinder – Binding entity from annotated class: Testes.PessoaFisica1
    628 [main] INFO org.hibernate.cfg.annotations.EntityBinder – Bind entity Testes.PessoaFisica1 on table PessoaFisica1
    Exception in thread “main” javax.persistence.PersistenceException: [PersistenceUnit: mapeamento_bd] Unable to configure EntityManagerFactory
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:371)
    at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:55)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:48)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:32)
    at Testes.AdicionaPessoa1.main(AdicionaPessoa1.java:11)
    Caused by: org.hibernate.AnnotationException: No identifier specified for entity: Testes.PessoaFisica1
    at org.hibernate.cfg.InheritanceState.determineDefaultAccessType(InheritanceState.java:272)
    at org.hibernate.cfg.InheritanceState.getElementsToProcess(InheritanceState.java:227)
    at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:705)
    at org.hibernate.cfg.AnnotationConfiguration.processArtifactsOfType(AnnotationConfiguration.java:636)
    at org.hibernate.cfg.AnnotationConfiguration.secondPassCompile(AnnotationConfiguration.java:359)
    at org.hibernate.cfg.Configuration.buildMappings(Configuration.java:1206)
    at org.hibernate.ejb.Ejb3Configuration.buildMappings(Ejb3Configuration.java:1449)
    at org.hibernate.ejb.EventListenerConfigurator.configure(EventListenerConfigurator.java:193)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:1077)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:275)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:359)
    … 4 more

  17. Robson Gomes Junior 07/10/2017 at 15:54 #

    Respondendo o:
    Manoel
    21/01/2015 at 17:15 #

    Muito tarde para responder ao Manoal, mas 2017 tive agora o mesmo problema e sofri um pouco para entender, principalmente a gente que trabalha estuda e tal… Dando uma olhada nos fóruns mais recentes encontrei a seguinte situação que resolveu o problema.

    Não há nada de errado nas classes filhas, ai tudo continua como está, apenas na Classe Pessoa você que está lendo substitua o @Entity por @MappedSuperclass

    @MappedSuperclass
    @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
    public abstract class Pessoa implements Serializable{

Deixe uma resposta