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 entidadeEndereco
.
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)
)