Quando se trata de herança o Hibernate trata deste assunto muito bem e com eficácia, diga-se de passagem. Ocorre que não há apenas 1 método para realizar a Herança de classes no Hibernate, o que confunde a maioria dos desenvolvedores. Antes de dar início é importante salientar que o uso inapropriado de herança no Hibernate pode causar uma lentidão incalculável na sua aplicação, por isso pense se a melhor alternativa é composição ou herança.
Enfim, nosso foco não é mostrar se o melhor é composição ou herança, mas sim as formas de se implementar uma herança no Hibernate. Estas formas são 3:
- Tabela Única por Hierarquia de Classes
- Tabela por Subclasse
- Tabela por Classe Concreta
Cada uma dessas tem sua peculiaridade, e não podemos simplesmente dizer: A solução X é melhor que Y, pois isso depende de inúmeras variáveis, tais como: suas regras de negócio.
Tabela Única por Hierarquia de Classes
Este é o famoso “tabelão” com milhares de campos, onde é gerado 1 tabela para toda a hierarquia de classes. Se você tiver uma Classe Pessoa como Superclasse, e outras 2 classes PessoaFisica e PessoaJuridica estendendo de Pessoa, o Hibernate irá gerar apenas 1 tabela Pessoa com os dados de Pessoa + PessoaFisica + PessoaJuridica.
E como é feita a divisão de quem é Pessoa Física e quem é Pessoa Jurídica ? Através de um atributo chamado discriminator. No seu “tabelão” deverá ter um campo que faça a discriminação entre PessoaFisica (F) e PessoaJuridica (J). Veja abaixo na listagem 1 a implementação da classe Pessoa.
Listagem 1: mplementação da Classe Pessoa
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "tipo", length = 1, discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue("P")
public class Pessoa implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idPessoa;
private String nome;
@Column(insertable=false, updatable=false)
private String tipo;
//getters and setters omitidos
}
O que vai nos importar no código acima são 3 linhas:
- @Inheritance(strategy = InheritanceType.SINGLE_TABLE): Identifica que a estratégia de herança será de uma única tabela, ou se preferir, um “tabelão” para tudo.
- @DiscriminatorColumn(name = "tipo", length = 1, discriminatorType = DiscriminatorType.STRING): Identifica que o campo que armazenará o discriminator é o “tipo”, de tamanho 1, do tipo String.
- @DiscriminatorValue("P"): Identifica que a Classe Pessoa será identifica com o atributo “P” na tabela Pessoa, ou seja, onde tiver “P” é porque aquele registro é uma pessoa.
Vamos ver agora na listagem 2 a implementação da pessoa física:
Listagem 2: Implementação da Pessoa Física
@Entity
@DiscriminatorValue(value = "F")
public class PessoaFisica extends Pessoa {
private String cpf;
//getters and setters omitidos
}
Bem simples, o que nos interessa saber é que seu DiscriminatorValue é “F”, ou seja, onde tiver um “F” no campo tipo da tabela, significa que é uma Pessoa Física.
Listagem 3: Implementação da Pessoa Jurídica
@Entity
@DiscriminatorValue(value = "J")
public class PessoaJuridica extends Pessoa {
private String cnpj;
//getters and setters omitidos
}
Dispensa explicações, mas é importante ser redundante para fixação do conteúdo. Onde tiver o valor “J” no campo tipo, significa que é uma Pessoa Jurídica.
Na figura 1, veja qual será o resultado da tabela:
Figura 1: Tabela Única por Hierarquia de Classes
Tabela por Subclasse
Nessa estratégia nós teremos a Classe Pai e todas suas filhas geradas no banco de dados fisicamente, sendo que em todas as classes filhas teremos uma chave estrangeira que apontará para a classe pai.
Isto é, se tivermos 1 Classe Pessoa com 5 atributos, 1 Classe Pessoa Física com 10 atributos e 1 Classe Pessoa Jurídica com 15 atributos, o gerado será: 1 tabela pessoa com 5 atributos, 1 tabela pessoa_fisica com 10 atributos e 1 tabela pessoa_juridica com 15 atributos. Vamos a exemplos.
Listagem 4: Implementação da Pessoa
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Pessoa implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idPessoa;
private String nome;
//getters and setters omitidos
}
A linha que nos interessa aqui é:
@Inheritance(strategy = InheritanceType.JOINED): Identifica que a estratégia de herança será “JOINED”, ou seja, será feita uma junção através de chaves estrangeiras.
Listagem 5. Implementação PessoaFisica
@Entity
@PrimaryKeyJoinColumn(name="idPessoa")
public class PessoaFisica extends Pessoa {
private String cpf;
//getters and setters omitidos
}
A linha importante aqui é:
@PrimaryKeyJoinColumn(name="idPessoa"): Identifica qual campo fará essa “junção” entre a tabela pessoa_fisica e a tabela pessoa.
Listagem 6: Implementação PessoaJuridica
@Entity
@PrimaryKeyJoinColumn(name="idPessoa")
public class PessoaJuridica extends Pessoa {
private String cnpj;
//getters and setters omitidos
}
Este código acima segue a mesma lógica da Listagem 5, a coluna idPessoa faz a “junção” entre a tabela pessoa e a tabela pessoa_juridica. Veja como ficou as tabelas geradas.
Figura 2: Tabela por Subclasse
Tabela por Classe Concreta
Não menos importante, essa estratégia define que apenas as classes concretas geram suas respectivas tabelas, ou seja, em nosso exemplo apenas pessoaFisica e pessoaJuridica criarão tabelas, isso porque pessoa é uma classe abstrata.
Listagem 7: Implementação de Pessoa
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Pessoa implements Serializable {
@Id
private Long idPessoa;
private String nome;
//getters and setters omitidos
}
A nossa estratégia agora é “TABLE_PER_CLASS”, que identifica que apenas as classes concretas serão geradas.
Listagem 8: Implementação PessoaFisica
@Entity
public class PessoaFisica extends Pessoa {
private String cpf;
//getters and setters omitidos
}
Nada de diferente aqui já que a estratégia é TABLE_PER_CLASS.
Listagem 9: Implementação PessoaJuridica
@Entity
public class PessoaJuridica extends Pessoa {
private String cnpj;
//getters and setters omitidos
}
Ainda nada de novo. Enfim, o resultado da sua tabela será visto na figura 3:
Figura 3: Tabela por Classe Concreta
CONCLUSÃO
Vimos neste artigo 3 tipos de estratégia de herança no Hibernate: SINGLE_TABLE, JOINED e TABLE_PER_CLASS. Cada um com sua peculiaridade e seus prós e contras. É difícil definir qual é o melhor, pois não há melhor, o melhor é você saber definir quando usar cada um destes.