Você está na página 1de 13

Pós-Graduação em

Desenvolvimento de Sistemas para Web – Disciplina:


Frameworks de Persistência de Dados para a Web

Herança de Entidades
Última revisão: 03/05/2008

1.1 - Introdução

A especificação JPA é compatível com a herança de entidades,


relacionamentos e associações polimórficas, e consultas polimórficas.

Neste capítulo, trabalharemos com a hierarquia de herança exibida na figura


abaixo:
Pós-Graduação em
Desenvolvimento de Sistemas para Web – Disciplina:
Frameworks de Persistência de Dados para a Web

A JPA fornece três maneiras de mapear uma herança para um banco de dados
relacional
Uma única tabela por toda 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 a ela, com todas as suas
propriedades e as propriedades de sua superclasse mapeadas para esta
tabela

Uma tabela por subclasse


Cada classe da hierarquia terá sua própria tabela. Entretanto,
diferentemente da estratégia “tabela por classe concreta”, cada tabela
terá apenas as propriedades definidas nessa classe particular. Essas
tabelas não terão propriedades de qualquer superclasse ou subclasse

1.2 - Tabela única por hierarquia de classe

Uma tabela do banco de dados representa todas as classes de uma dada


hierarquia

Utilizando essa estratégia, em nosso exemplo do diagrama de classes, as


entidades seriam representadas em uma única tabela que poderia ser
representada pelo Script SQL abaixo:

create table HIERARQUIA_PESSOA (


id integer primary key not null,
nome varchar(255),
sobrenome varchar(255),
salario double,
rua varchar(255),
cidade varchar(255),
cep varchar(255),
discriminador varchar(31) not null
);

O mapeamento de uma única tabela por hierarquia de classe também requer


uma coluna discriminadora adicional.
Essa coluna identifica o tipo de entidade sendo armazenado em uma linha
Pós-Graduação em
Desenvolvimento de Sistemas para Web – Disciplina:
Frameworks de Persistência de Dados para a Web

particular da HIERARQUIA_PESSOA no banco de dados.

Vejamos como anotar nossas classes para mapear esta estratégia de herança

@Entity
@Table(name="HIERARQUIA_PESSOA")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="tipoObjeto",
discriminatorType=DiscriminatorType.STRING)
@DiscriminatorValue("pessoa")
public class Pessoa implements Serializable {
protected int id;
protected String nome;
protected String sobrenome;

public void setId(int id) {


this.id = id;
}

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() {
return id;
}

public String getNome() {


return nome;
}

public void setNome(String nome) {


this.nome = nome;
}

public String getSobrenome() {


return sobrenome;
}

public void setSobrenome(String sobrenome) {


this.sobrenome = sobrenome;
}}
Pós-Graduação em
Desenvolvimento de Sistemas para Web – Disciplina:
Frameworks de Persistência de Dados para a Web

A anotação @javax.persistence.Inheritance é utilizada para definir a


estratégia de persistência para o relacionamento de herança
O atributo strategy() define o mapeamento de herança que estamos
utilizando
Anotação @Inheritance só tem de ser posicionada na raiz de hierarquia de
classes.

Como uma tabela representa toda a hierarquia de classes, o provedor de


persistência precisa de alguma maneira identificar para qual classe a linha no
banco de dados é mapeada.
Isso é determinado lendo o atributo name definido na anotação
@DiscriminatorColumn.
A anotação @javax.persistence.DiscriminatorColumn identifica qual
coluna na nossa tabela armazenará o valor do discriminador.
O atributo name() identifica o nome da coluna
O atributo discriminatorType() especifica qual será o tipo da coluna
discriminadora
Pode ser STRING, CHAR ou INTEGER onde o padrão é STRING

A anotação @javax.persistence.DiscriminatorValue define qual valor a


coluna discriminadora receberá para linhas que armazenam uma instância da
classe Pessoa. Você pode deixar este atributo indefinido, se preferir.
O nome da entidade é utilizado por padrão quando um tipo STRING é
especificado
O restante da hierarquia de classes é bem fácil. Os únicos metadados
específicos de herança que você tem de especificar é o valor do
discriminador, se você quiser um valor além do padrão.

@Entity
@DiscriminatorValue(“ASSALARIADO”) // opcional
public class Assalariado extends Pessoa {
protected double salario;

public double getSalario() {


return salario;
}
...
...

}
Pós-Graduação em
Desenvolvimento de Sistemas para Web – Disciplina:
Frameworks de Persistência de Dados para a Web

// O exemplo abaixo utiliza o valor padrão do discriminador, não usando


// @DiscriminatorValue

@Entity
public class Empregado extends Assalariado {
private String rua;
private String cidade;
private String cep;

public String getRua() {


return rua;
}

....
}

1.2.1 - Vantagens

A estratégia de mapeamento SINGLE_TABLE é a mais simples de


implementar e tem um desempenho melhor entre todas as estratégias de
herança. Há somente uma única tabela com a qual lidar e administrar. O
mecanismo de persistência não tem de fazer nenhuma junção complexa, união
ou sub-seleção ao carregar a entidade ou atravessar um relacionamento
polimórfico, porque todos os dados estão armazenados em uma única tabela.

1.2.2 - Desvantagens

Uma desvantagem enorme desta abordagem é que todas as colunas das


propriedades de sub-classe podem conter nulos. Portanto, se precisar ou
quiser definir quaisquer restrições NOT NULL nessas colunas, não há como.

1.3 - Tabela por classe concreta

Uma tabela de banco de dados é definida para cada classe concreta na


hierarquia.
Cada tabela tem colunas que representam suas propriedades e todas as
propriedades de quaisquer superclasses
Pós-Graduação em
Desenvolvimento de Sistemas para Web – Disciplina:
Frameworks de Persistência de Dados para a Web

create table PESSOA (


ID integer primary key not null,
NOME varchar(255),
SOBRENOME varchar(255)
);

create table ASSALARIADO (


ID integer primary key not null,
NOME varchar(255),
SOBRENOME varchar(255),
SALARIO double
);

create table Empregado (


ID integer primary key not null,
NOME varchar(255),
SOBRENOME varchar(255),
SALARIO double,
RUA varchar(255),
CIDADE varchar(255),
CEP varchar(255)
);

Uma diferença importante entre essa estratégia e a SINGLE_TABLE é que


nenhuma coluna discriminadora é necessária no esquema do banco de dados.
Também observe que cada tabela contém todas as propriedades persistentes na
hierarquia.

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

@Entity
public class Assalariado extends Pessoa {
...
}
Pós-Graduação em
Desenvolvimento de Sistemas para Web – Disciplina:
Frameworks de Persistência de Dados para a Web

@Entity
public class Empregado extends Assalariado {
...
}

Observe que o único metadado de herança necessário é o InheritanceType e


isso só é necessário na classe Pessoa.

1.3.1 - Vantagens

As vantagens dessa abordagem em relação a estratégia SINGLE_TABLE são


que você pode definir restrições nas propriedades das subclasses. Uma outra
vantagem é que talvez seja mais fácil mapear um esquema legado pré-existente
utilizando esta estratégia.

1.3.2 - Desvantagens

Esta estratégia não é normalizada, uma vez que ela tem colunas redundantes
em cada uma de suas tabelas para cada uma das propriedades da classe básica.

Outra desvantagem é não ser tão rápido quanto a estratégia SINGLE_TABLE,


mas tem uma um desempenho melhor do que uma implementação de múltiplas
seleções.

Não é inteligente selecionar esta estratégia ao desenvolver seus beans de


entidade, a menos que você realmente precise.
Aparentemente, esta estratégia não é suportada no Provider TopLink.

1.4 - Tabela por subclasse

Cada subclasse tem sua própria tabela, mas essa tabela só contém as
propriedades definidas nessa classe particular.
Semelhante a estratégia TABLE_PER_CLASS, exceto pelo esquema ser
normalizado
Também conhecida como estratégia JOINED
Pós-Graduação em
Desenvolvimento de Sistemas para Web – Disciplina:
Frameworks de Persistência de Dados para a Web

create table PESSOA (


ID integer primary key not null,
NOME varchar(255),
SOBRENOME varchar(255),
DISCRIMINADOR varchar(31)
);

create table ASSALARIADO (


ID integer primary key not null,
SALARIO double
);

create table EMPREGADO (


ID integer primary key not null,
RUA varchar(255),
CIDADE varchar(255),
CEP varchar(255)
);

Nesse mapeamento, deve haver uma coluna em cada tabela que possa ser
utilizada para unir todas elas. Em nosso exemplo, as tabela EMPREGADO,
ASSALARIADO e PESSOA compartilham os mesmos valores de chave
primária.

Também note que a superclasse da hierarquia possui um atributo discriminador

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

@Entity
public class Assalariado extends Pessoa {
...
}

Podemos sobreescrever o padrão e renomear a coluna de junção em uma tabela


específica
Pós-Graduação em
Desenvolvimento de Sistemas para Web – Disciplina:
Frameworks de Persistência de Dados para a Web

@Entity
@PrimaryKeyJoinColumn(name=” PK_EMPREGADO”)
public class Empregado extends Assalariado {
...
}
O gerenciador de persistência precisa saber quais colunas em cada tabela
serão utilizadas para realizar uma junção ao carregar uma entidade com uma
estratégia de herança JOINED.

A anotação @javax.persistence.PrimaryKeyJoinColumn pode ser utilizada


para descrever estes metadados, apesar de ser opcional.
O atributo name() refere-se à coluna em que você realizará uma junção que
está contida na tabela atual. Ele assume como padrão a coluna da chave
primária da tabela da superclasse.
Por exemplo, a entidade Assalariado não precisa da anotação
@PrimaryKeyJoinColumn pois o nome da coluna de chave primária é
idêntica ao da sua superclasse, Pessoa. Já no exemplo acima, a classe
Empregado tem um nome de coluna de chave primária diferente das
tabelas de suas superclasses, assim a anotação @PrimaryKeyJoinColumn
é necessária.

Alguns provedores de persistência exigem uma coluna discriminadora


(incluindo o TopLink) para este tipo de mapeamento. A maioria não exige.
Certifique-se de verificar a implementação de seu provedor de persistência
para saber se ela é exigida.

1.4.1 - Vantagens

Embora não seja tão rápida quanto a estratégia SINGLE_TABLE, você


consegue definir restrições NOT NULL em qualquer coluna de qualquer
tabela, e seu modelo é normalizado.

Esse mapeamento é melhor que a estratégia TABLE_PER_CLASS por duas


razões. Primeiro, o modelo do banco de dados relacional é completamente
normalizado. Segundo, ele tem um melhor desempenho que a estratégia
TABLE_PER_CLASS se SQL UNIONS não forem suportadas.
Pós-Graduação em
Desenvolvimento de Sistemas para Web – Disciplina:
Frameworks de Persistência de Dados para a Web

1.4.2 - Desvantagens

Ele não tem um desempenho tão bom quanto à estratégia SINGLE_TABLE.


O número de joins aumenta na medida que a profundidade da hierarqui de
classes se torna maior, afetando a performance de nossas consultas.

1.5 - Classes básicas que não serão persistidas

Às vezes, porém, você precisa herdar algo de uma superclasse que não seja
entidade. Essa superclasse pode ser uma classe existente em seu modelo de
domínio que você não quer transformar em uma entidade.
A anotação @javax.persistence.MappedSuperclass permite definir este
tipo de mapeamento.

@MappedSuperClass
public class Pessoa implements Serializable {
private int id;
private String nome;
private String sobrenome;

public void setId(int id) {


this.id = id;
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() {
return id;
}

public String getNome() {


return nome;
}

public void setNome(String nome) {


this.nome = nome;
}

public String getSobrenome() {


return sobrenome;
}
Pós-Graduação em
Desenvolvimento de Sistemas para Web – Disciplina:
Frameworks de Persistência de Dados para a Web

public void setSobrenome(String sobrenome) {


this.sobrenome = sobrenome;
}
}

@Entity
@Table(name=”ASSALARIADO”)
@Inheritance(strategy=InheritanceType.JOINED)
// A anotação @AttributeOverride é opcional
@AttributeOverride(name=”sobrenome”,
column=@Column(name=”SOBRENOME_ASSALARIADO”))
public class Assalariado extends Pessoa {
...
}

@Entity
@Table(name=”EMPREGADO”)
public class Empregado extends Assalariado {
...
}

Como Pessoa não é uma entidade, a superclasse mapeada não tem uma tabela
associada. Qualquer subclasse herda as propriedades de persistência da classe
básica. Você pode sobreescrever qualquer propriedade mapeada da classe
mapeada utilizando a anotação @javax.persistence.AttributeOverride

Eis o esquema gerado:

create table ASSALARIADO {


ID integer primary key not null,
NOME varchar(255),
SOBRENOME_ASSALARIADO varchar(255),
RUA varchar(255),
CIDADE varchar(255),
ESTADO varchar(255),
CEP varchar(255)
);
Pós-Graduação em
Desenvolvimento de Sistemas para Web – Disciplina:
Frameworks de Persistência de Dados para a Web

create table EMPREGADO {


ID integer primary key not null,
SALARIO double
);

Como você pode ver, a classe de entidade Assalariado herda as propriedades


id, nome e sobrenome. Como um @AttributeOverrides foi especificada, o
nome da coluna para sobrenome será SOBRENOME_ASSALARIADO.
Esta estratégia de mapeamento é útil quando você quer ter uma classe
básica para uma entidade e não pretende forçar que ela seja uma própria
entidade.
Pós-Graduação em
Desenvolvimento de Sistemas para Web – Disciplina:
Frameworks de Persistência de Dados para a Web

Exercícios – Mapeamento de Herança para o banco de dados

Neste exercício, cabe a você, aluno, mapear o diagrama de classes abaixo para um
banco de dados utilizando a estratégia de tabela única para toda a hierarquia de
classes.

Após mapear, crie uma classe com um método main() que teste a persistência de
todos os objetos da hierarquia acima. Também não esqueça de testar consultas com
todos os objetos desta hierarquia.