Mapeamento de herança de hibernação

Mapeamento de herança de hibernação

1. Visão geral

Bancos de dados relacionais não têm uma maneira direta de mapear hierarquias de classes em tabelas de banco de dados.

Para resolver isso, a especificação JPA fornece várias estratégias:

  • MappedSuperclass - as classes pai, não podem ser entidades

  • Tabela única - as entidades de diferentes classes com um ancestral comum são colocadas em uma única tabela

  • Tabela unida - cada classe tem sua tabela e a consulta de uma entidade de subclasse exige a junção das tabelas

  • Tabela por classe - todas as propriedades de uma classe estão em sua tabela, portanto, nenhuma associação é necessária

Cada estratégia resulta em uma estrutura de banco de dados diferente.

Herança de entidade significa que podemos usar consultas polimórficas para recuperar todas as entidades da subclasse ao consultar uma superclasse.

Como o Hibernate é uma implementação JPA, ele contém todos os itens acima, além de alguns recursos específicos do Hibernate relacionados à herança.

Nas próximas seções, examinaremos as estratégias disponíveis com mais detalhes.

2. MappedSuperclass

Usando a estratégiaMappedSuperclass, a herança só é evidente na classe, mas não no modelo de entidade.

Vamos começar criando uma classePerson que representará uma classe pai:

@MappedSuperclass
public class Person {

    @Id
    private long personId;
    private String name;

    // constructor, getters, setters
}

Notice that this class no longer has an @Entity annotation, já que não será persistido no banco de dados por si só.

A seguir, vamos adicionar uma subclasseEmployee:

@Entity
public class MyEmployee extends Person {
    private String company;
    // constructor, getters, setters
}

No banco de dados, isso corresponderá a uma tabela“MyEmployee” com três colunas para os campos declarados e herdados da subclasse.

Se estivermos usando essa estratégia, os ancestrais não podem conter associações com outras entidades.

3. Mesa Única

The Single Table strategy creates one table for each class hierarchy. Esta também é a estratégia padrão escolhida pelo JPA se não especificarmos uma explicitamente.

Podemos definir a estratégia que queremos usar adicionando a anotação@Inheritance à superclasse:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class MyProduct {
    @Id
    private long productId;
    private String name;

    // constructor, getters, setters
}

O identificador das entidades também é definido na superclasse.

Em seguida, podemos adicionar as entidades da subclasse:

@Entity
public class Book extends MyProduct {
    private String author;
}
@Entity
public class Pen extends MyProduct {
    private String color;
}

3.1. Valores Discriminadores

Como os registros de todas as entidades estarão na mesma tabela,Hibernate needs a way to differentiate between them.

By default, this is done through a discriminator column called DTYPE que tem o nome da entidade como valor.

Para personalizar a coluna discriminadora, podemos usar a anotação@DiscriminatorColumn:

@Entity(name="products")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="product_type",
  discriminatorType = DiscriminatorType.INTEGER)
public class MyProduct {
    // ...
}

Aqui, escolhemos diferenciar as entidades da subclasseMyProduct por uma colunainteger chamadaproduct_type.

Em seguida, precisamos informar ao Hibernate qual valor cada registro de subclasse terá para a colunaproduct_type:

@Entity
@DiscriminatorValue("1")
public class Book extends MyProduct {
    // ...
}
@Entity
@DiscriminatorValue("2")
public class Pen extends MyProduct {
    // ...
}

O Hibernate adiciona dois outros valores pré-definidos que a anotação pode assumir: “null” e “not null“:

  • @DiscriminatorValue(“null”) - significa que qualquer linha sem um valor discriminador será mapeada para a classe de entidade com esta anotação; isso pode ser aplicado à classe raiz da hierarquia

  • @DiscriminatorValue(“not null”) - qualquer linha com um valor discriminador que não corresponda a nenhum dos associados às definições de entidade será mapeada para a classe com esta anotação

Em vez de uma coluna, também podemos usar a anotação@DiscriminatorFormula específica do Hibernate para determinar os valores de diferenciação:

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorFormula("case when author is not null then 1 else 2 end")
public class MyProduct { ... }

This strategy has the advantage of polymorphic query performance since only one table needs to be accessed when querying parent entities. Por outro lado, isso também significa quewe can no longer use NOT NULL constraints on sub-class propriedades da entidade.

4. Mesa junta

Using this strategy, each class in the hierarchy is mapped to its table. A única coluna que aparece repetidamente em todas as tabelas é o identificador, que será usado para juntá-las quando necessário.

Vamos criar uma superclasse que use esta estratégia:

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Animal {
    @Id
    private long animalId;
    private String species;

    // constructor, getters, setters
}

Então, podemos simplesmente definir uma subclasse:

@Entity
public class Pet extends Animal {
    private String name;

    // constructor, getters, setters
}

Ambas as tabelas terão uma coluna de identificadoranimalId. A chave primária da entidadePet também tem uma restrição de chave estrangeira para a chave primária de sua entidade pai. Para personalizar esta coluna, podemos adicionar a anotação@PrimaryKeyJoinColumn:

@Entity
@PrimaryKeyJoinColumn(name = "petId")
public class Pet extends Animal {
    // ...
}

The disadvantage of this inheritance mapping method is that retrieving entities requires joins between tables, o que pode resultar em desempenho inferior para um grande número de registros.

O número de junções é maior ao consultar a classe pai, pois ela se unirá a todos os filhos relacionados - portanto, é mais provável que o desempenho seja afetado quanto maior a hierarquia que desejamos recuperar os registros.

5. Tabela por classe

A estratégia Tabela por classe mapeia cada entidade para sua tabela, que contém todas as propriedades da entidade, incluindo as herdadas.

O esquema resultante é semelhante ao que usa@MappedSuperclass,, mas ao contrário dele, a tabela por classe realmente definirá entidades para as classes pai, permitindo associações e consultas polimórficas como resultado.

Para usar essa estratégia, precisamos apenas adicionar a anotação@Inheritance à classe base:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Vehicle {
    @Id
    private long vehicleId;

    private String manufacturer;

    // standard constructor, getters, setters
}

Então, podemos criar as subclasses da maneira padrão.

Isso não é muito diferente de apenas mapear cada entidade sem herança. A distinção é aparente ao consultar a classe base, que também retornará todos os registros da subclasse usando uma instruçãoUNION em segundo plano.

The use of UNION can also lead to inferior performance when choosing this strategy. Outro problema é que não podemos mais usar a geração de chave de identidade.

6. Consultas polimórficas

Como mencionado, a consulta de uma classe base recuperará todas as entidades da subclasse também.

Vamos ver esse comportamento em ação com um teste JUnit:

@Test
public void givenSubclasses_whenQuerySuperclass_thenOk() {
    Book book = new Book(1, "1984", "George Orwell");
    session.save(book);
    Pen pen = new Pen(2, "my pen", "blue");
    session.save(pen);

    assertThat(session.createQuery("from MyProduct")
      .getResultList()).hasSize(2);
}

Neste exemplo, criamos dois objetosBookePen e, em seguida, consultamos sua superclasseMyProduct para verificar se vamos recuperar dois objetos.

O Hibernate também pode consultar interfaces ou classes base que não são entidades, mas são estendidas ou implementadas por classes de entidade. Vamos ver um teste JUnit usando nosso exemplo@MappedSuperclass:

@Test
public void givenSubclasses_whenQueryMappedSuperclass_thenOk() {
    MyEmployee emp = new MyEmployee(1, "john", "example");
    session.save(emp);

    assertThat(session.createQuery(
      "from com.example.hibernate.pojo.inheritance.Person")
      .getResultList())
      .hasSize(1);
}

Observe que isso também funciona para qualquer superclasse ou interface, seja ela@MappedSuperclass ou não. A diferença de uma consulta HQL usual é que precisamos usar o nome completo, pois eles não são entidades gerenciadas pelo Hibernate.

Se não quisermos que uma subclasse seja retornada por este tipo de consulta, precisamos apenas adicionar a anotação@Polymorphism do Hibernate à sua definição, com o tipoEXPLICIT:

@Entity
@Polymorphism(type = PolymorphismType.EXPLICIT)
public class Bag implements Item { ...}

Nesse caso, ao consultarItems,, os registros deBag não serão retornados.

7. Conclusão

Neste artigo, mostramos as diferentes estratégias para mapear a herança no Hibernate.

O código-fonte completo dos exemplos pode ser encontradoover on GitHub.