Herança de entidades

As entidades podem possuir, além de relacionamentos, hierarquias de classes entre si. Na especificação JPA existem três formas de mapeamento de herança hierárquica:

  • uma única tabela por hierarquia de classe: Uma única tabela terá todas as propriedades de cada classe na hierarquia;
  • uma tabela por classe concreta: Cada classe terá uma tabela dedicada, com todas as suas propriedades e as propriedades de sua superclasse mapeadas para essa tabela;
  • uma tabela por subclasse: Cada classe terá a sua própria tabela. Cada tabela terá apenas as propriedades definidas nessa classe particular. Essas tabelas não terão propriedades de qualquer superclasse ou subclasse.

Nesses casos utilizamos na entidade, hierarquicamente superior, a anotação @Inheritance. Esta anotação permite configurarmos a propriedade strategy, que define qual estratégia será utilizada. É possível usar um valor definido na enumeração javax.persistence.InheritanceType, a estratégia default é a SINGLE_TABLE.

@Target(TYPE) 
@Retention(RUNTIME) 
public @interface Inheritance {
    InheritanceType strategy() default SINGLE_TABLE;
}
public enum InheritanceType {
    SINGLE_TABLE,
    JOINED,
    TABLE_PER_CLASS
};

Tabela única

Esta estratégia é a mais simples, porque uma única tabela no banco de dados terá todas as propriedades das classes da hierarquia. Contudo, é necessário uma coluna que distinga as entidades.

Para exemplificar vamos utilizar o modelo de classes que define as classes Carro, CarroDeMao e Fusca.

Neste exemplo, temos a entidade Carro como entidade hierarquicamente superior. O mapeamento referente a herança deve acontecer nesta entidade. Devemos utilizar a anotação @Inheritance com a propriedade strategy atribuida a InheritanceType.SINGLE_TABLE, conforme o código a seguir.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "TIPO", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue("Carro")
public class Carro implements Serializable {
    @Id
    private int id;
    private String modelo;
    private String ano;
    private String marca;
}

Como podemos observar, estamos definindo, também, que a tabela Carro tem uma coluna TIPO, que distingue qual o tipo a ser armazenado. No momento que persistirmos uma entidade Carro será atribuído a esta coluna o valor Carro, que definimos com a anotação @DiscriminatorValue.

Nas entidades que estão hierarquicamente inferior, as subclasses, precisamos definir qual valor será atribuído a essa coluna. Na entidade, CarroDeMao, definimos que o valor será mao. Na entidade Fusca, definimos Fusca86. Caso seja especificada a anotação @DiscriminatorValue, o valor default será o nome da classe.

@Entity
@DiscriminatorValue("mao")
public class CarroDeMao extends Carro {
    private int numeroDeRodas;
}
@Entity
@DiscriminatorValue("Fusca86")
public class Fusca extends Carro {
    private int nivelDeBeleza;
}

No banco de dados, a tabela será criada conforme o código sql a seguir. Podemos perceber que todas as propriedades pertencentes as classes da hierarquia estão na tabela carro.

CREATE TABLE carro(
  id int primary key,
  tipo character varying(31),
  ano character varying(255),
  marca character varying(255),
  modelo character varying(255),
  numeroderodas integer,
  niveldebeleza integer
)

Nota:

Vantagens:

  • Mais simples de todas.
  • Melhor desempenho.

Desvantagens:

  • As colunas precisam ser anuláveis.
  • Cuidado com o uso de NOT NULL.

Tabela por classe concreta

Nesta estratégia, todas as entidades da hierarquia possuem tabelas no banco de dados que representam suas classes concretas. Nenhuma coluna discriminatória é necessária.

Para exemplificar vamos utilizar o modelo de classes que define as classes Animal, Cachorro e Gato.

Neste exemplo, temos a entidade Animal como entidade hierarquicamente superior. O mapeamento referente a herança deve acontecer nesta entidade. Devemos utilizar a anotação @Inheritance com a propriedade strategy atribuida a InheritanceType.TABLE_PER_CLASS, conforme o código a seguir.

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Animal implements Serializable {
    @Id
    private int id;
    private String raca;
    private boolean bonito;

Para as entidades que estão hierarquicamente inferior, as subclasses, as propriedades que são herdadas também fazem parte da tabela. Por exemplo, a tabela que representa a entidade Cachorro contém as seguintes colunas: id, bonito, raca e dono. Porém, apenas a propriedade dono está definida na classe Cachorro as demais estão sendo herdadas da classe Animal.

@Entity
public class Cachorro extends Animal {
    private String dono;
}

@Entity
public class Gato extends Animal {
    private int nivelDePreguica;
}

Nota: Como podemos observar, não é preciso definir nas subclasses o atributo id.

No banco de dados, as tabela serão criadas conforme o código sql a seguir. Podemos perceber que todas as propriedades pertencentes as classes superior da hierarquia, a tabela Animal, estão nas tabelas que representam as classes inferiores da hierarquia, Cachorro e Gato.

CREATE TABLE animal(
  id int primary key,
  bonito boolean,
  raca character varying(255)
)

CREATE TABLE cachorro(
  id int primary key,
  bonito boolean,
  dono character varying(255),
  raca character varying(255)
)

CREATE TABLE gato(
  id int primary key,
  bonito boolean,
  niveldepreguica integer,
  raca character varying(255)
)

Nota:

Vantagens:

  • Pode restringir as propriedades da classe com NOT NULL.
  • Talvez seja mais fácil o mapeamento com um sistema legado.

Desvantagens:

  • Mantém colunas redundantes.
  • Desempenho não tão bom como o SINGLE_TABLE.

Tabela por subclasse

Essa estratégia é parecida com TABLE_PER_CLASS, exceto pelo fato de as tabelas concretas só possuem os dados que a subclasse possui.

Para exemplificar vamos utilizar o modelo de classes que define as classes Empregado, Professor e Tecnico.

Neste exemplo, temos a entidade Empregado como entidade hierarquicamente superior. O mapeamento referente a herança deve acontecer nesta entidade. Devemos utilizar a anotação @Inheritance com a propriedade strategy atribuida a InheritanceType.JOINED, conforme o código a seguir.

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "PAPEL", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue("Funcionario")
public class Empregado implements Serializable {
    @Id
    private int id;
    private String nome;
    private String cpf;
}

Para as entidades que estão hierarquicamente inferior, as subclasses, apenas as propriedades definidas na classe fazem parte da tabela. Por exemplo, a tabela que representa a entidade Professor contém as seguintes colunas: id, matricula, numeroDeAulas. Porém, quando persistirmos uma entidade do tipo Professor, as informações que estão definidas na entidade Empregado serão persistidas na tabela correspondente a essa entidade.

@Entity
@DiscriminatorValue("Prof")
public class Professor extends Empregado {
    private String matricula;
    private int numeroDeAulas;
}    

@Entity
@DiscriminatorValue("TAE")
public class Tecnico extends Empregado {
    private int numeroDeHoras;
    private String setor;
}

Nota: Como podemos observar, não é preciso definir nas subclasses o atributo id.

No banco de dados, as tabela serão criadas conforme o código sql a seguir. Podemos perceber que todas as propriedades pertencentes as classes superior da hierarquia, a tabela Animal, estão nas tabelas que representam as classes inferiores da hierarquia, Cachorro e Gato.

CREATE TABLE empregado(
  id int primary key,
  papel character varying(31),
  cpf character varying(255),
  nome character varying(255)
)

CREATE TABLE tecnico(
  id int primary key,
  numerodehoras integer,
  setor character varying(255),
  FOREIGN KEY (id) REFERENCES empregado (id) ON DELETE CASCADE ON UPDATE CASCADE 
)

CREATE TABLE professor(
  id int primary key,
  matricula character varying(255),
  numerodeaulas integer,
  FOREIGN KEY (id) REFERENCES empregado (id) ON DELETE CASCADE ON UPDATE CASCADE 
)

Nota:

Vantagens:

  • Podemos definir restrições NOT NULL.
  • Melhor que TABLE_PER_CLASS, pois o banco de dados fica normalizado.

Desvantagens:

  • Desempenho não tão bom como o SINGLE_TABLE.

Herança de Não-Entidades

Em algumas situações precisamos herdar informaçaões de uma superclasse, mas ela não é uma entidade. Por exemplo, uma classe de domínio, Medico que não desejamos transformá-la em entidade.

Neste exemplo, temos a classe Medico não é definida como uma entidade. Desta forma, os três casos de mapeamentos não podem ser aplicados. Nesses cenários, podemos utilizar a anotação @MappedSuperclass, conforme o código a seguir.

@MappedSuperclass
public class Medico implements Serializable{
    @Id
    private int id;
    private String nome;
    @OneToOne
    private Endereco endereco;
}

Nota:

A classe Medico podem conter relacionamentos unidirecionais com entidades. Nesse exemplo, a entidade Endereco.

Para as entidades que estão hierarquicamente inferior, as subclasses, as propriedades que são herdadas também fazem parte da tabela. Por exemplo, a tabela que representa a entidade Dentista contém as seguintes colunas: id, horas e endereco_rua. Porém, apenas a propriedade horas está definida na classe Dentista as demais estão sendo herdadas da classe Medico.

@Entity
public class Dentista extends Medico {
    private int horas;
}

No banco de dados, as tabela serão criadas conforme o código sql a seguir. Podemos perceber que todas as propriedades pertencentes as classes superior da hierarquia,Medico, estão nas tabelas que representam as classes inferiores da hierarquia, Dentista.

CREATE TABLE dentista(
  id int primary key,
  horas integer,
  nome character varying(255),
  endereco_rua character varying(255),
  FOREIGN KEY (endereco_rua) REFERENCES endereco(rua) ON DELETE CASCADE ON UPDATE CASCADE 
)

CREATE TABLE endereco(
  rua character varying(255) primary key,
  bairro character varying(255),
  cidade character varying(255)
)

results matching ""

    No results matching ""