Hibernate ORM 6.6 - Embeddable Inheritance

Posted by    |      

The first release candidate for Hibernate ORM 6.6 has already been released, and a final version will be available very soon. In today’s blog post, I’m going to delve into a feature that has been requested for a long time but has never made it into our framework until now: Embeddable Inheritance.

What are Embeddable values

Embeddable properties have been a part of Hibernate ORM for a long time, but let me give you a quick rundown of their basic functionalities. If you already know all about embeddables and just want to hear the news, feel free to skip to the next section.

Embeddable types, identified by the @Embeddable annotation, are a composition of values that, unlike entities, do not directly correspond to database tables. Their state is tied to the entities they’re used in as @Embedded properties, and they are reusable across different mappings. Embeddables can also be used as composite primary keys through the @EmbeddedId or @IdClass annotations, and as @ElementCollection values.

Here’s an example of a very simple embeddable type consisting of two properties:

@Embeddable
class Animal {
    private int age;

    private String name;
}

And here’s an entity mapping that uses it:

@Entity
class Owner {
    @Id
    private Long id;

    @Embedded
    private Animal pet;
}

The database table corresponding to the Owner entity will include both the properties for the Animal embeddable:

create table Owner (
    id bigint not null,
    age integer,
    name varchar(255),
    primary key (id)
)

Note that embeddables can be made up of basic values as well as associations. For more in-depth information about all the possibilities embeddables offer, you can refer to this chapter of the Hibernate user guide.

Introducing Embeddable Inheritance

Inheritance support in Hibernate ORM, i.e. the ability to take advantage of the object-oriented fundamental concept of superclasses and subclasses with inherited properties in the domain model, has historically been restricted to entity types. After a long wait (the original feature request dates back to November 2005!), we have introduced support for discriminator-column-based @Embeddable types inheritance.

How Embeddable Inheritance Works

Embeddable inheritance works similarly to Single Table Entity inheritance: an @Embeddable annotated class, which we’ll call the root type, may be extended by other @Embeddable annotated classes, which will become the subtypes.

embeddable inheritance

When this is the case, the @Embedded properties using that type will create an additional column in the entity mappings in which they are contained that will store information about the composite value’s specific subtype (unless you use a formula-based discriminator, see how in the following chapters). When retrieving the inherited property, we will read the discriminator value and instantiate the correct @Embeddable subtype with its corresponding properties.

Here’s an example of how you can enable embeddable inheritance in your mappings. Taking from the previous chapter’s example, let’s say the Animal type is extended by other embeddables:

@Embeddable
class Mammal extends Animal {
    private String mother;
}

@Embeddable
class Cat extends Mammal {
    // [...]
}

@Embeddable
class Dog extends Mammal {
    // [...]
}

@Embeddable
class Fish extends Animal {
    private int fins;
}

This will enable embeddable inheritance and, on the corresponding Owner entity, we’ll see the columns for all the subtypes properties as well as the discriminator:

create table Owner (
    id bigint not null,
    pet_DTYPE varchar(31) not null,
    age integer,
    name varchar(255),
    mother varchar(255),
    fins integer,
    primary key (id)
)

Embeddable inheritance IS also supported for components used in an @ElementCollection. Embeddable inheritance is NOT supported for @EmbeddedId, embeddable types used as @IdClass and embedded properties using a custom @CompositeType.

Why Use Embeddable Inheritance?

Embeddable inheritance is useful when you want to embed a polymorphic structure within your mapped tables, instead of relying on the entire entity mapping itself to handle inheritance. It allows for a clean and modular design, enabling code reuse and maintaining a clear separation of concerns.

Customizing the discriminator column

By default, the discriminator column will be STRING typed and named <property_name>_DTYPE, where property_name is the name of the @Embedded property attribute in the respective entity mapping. It’s possible to customize the discriminator column mapping:

  • For the whole @Embeddable type, by using either a dedicated column via the @DiscriminatorColumn annotation or a native SQL expression based on existing properties with @DiscriminatorFormula on the root class of the inheritance hierarchy (NOTE: if using the same inheritance-enabled embeddable type for two different properties in the same entity mapping, this might cause a column name conflict);

  • For a specific @Embedded property, by using the @AttributeOverride annotation with a special value as name: {discriminator}.

Finally, to specify custom discriminator values for each subtype, one can annotate the inheritance hierarchy’s classes with @DiscriminatorValue.

Dedicated discriminator column

Storing the type of your polymorphic embedded properties in a dedicated column is the simplest solution, that allows to easily understand what embeddable subtype is contained in an entity by directly reading its value.

Here’s a couple of examples of customizing the discriminator column and values stored in it:

@Embeddable
@DiscriminatorColumn( name = "animal_type", length = 1 )
static class Animal {
    // [...]
}

@Embeddable
@DiscriminatorValue( "C" )
static class Cat extends Mammal {
    // [...]
}

And finally, here’s an example of how an insert statement triggered by persisting an instance of Owner with a Cat embeddable instance looks like:

insert
into
    Owner
    (age, fins, mother, name, animal_type, id)
values
    (3, null, 'Gatta', 'Ariel', 'C', 1)

Discriminator formula

You can also base your polymorphic @Embedded properties type on existing columns through the @DiscriminatorFormula annotation: by specifying a native SQL expression that will result in the subtype discriminator values, you won’t need an additional discriminator-column to enable embeddable inheritance in your mappings.

Here’s a very simple example usage of formula-based embeddable polymorphism:

@Embeddable
@DiscriminatorFormula( "case when name like 'cat_%' then 'C' when name like 'dog_%' then 'D' [...] end" )
static class Animal {
    // [...]
}

With this mapping, no additional column will be needed to store the embedded property’s subtype. Here’s how a query that reads the embedded property value looks like:

select
    o1_0.id,
    case
        when o1_0.name like 'cat_%' then 'C'
        when o1_0.name like 'dog_%' then 'D'
        -- [...]
    end,
    o1_0.age,
    o1_0.name,
    o1_0.mother,
    o1_0.fins
from
    Owner o1_0

Discriminator formulas are very flexible, allowing the discriminator value to be derived from any native SQL expression.

Support for type() and treat() operators

Of course, the type() and treat() functions are also supported for embeddable inheritance and can serve to explicitly refer to the type information of @Embedded properties in queries.

type()

The function type() evaluates to the concrete type, that is, the Java Class, of the referenced entity or embeddable:

Class<?> petType = entityManager.createQuery( (1)
    "select type(o.pet) " +
    "from Owner o " +
    "where o.id = 1",
    Class.class)
.getSingleResult();

List<Owner> catOwners = entityManager.createQuery( (2)
    "select o " +
    "from Owner o " +
    "where type(o.pet) = Cat",
    Owner.class)
.getResultList();
1 Retrieve the type of an embeddable property
2 Restrict the embeddable property to a specific subtype

This allows you to interact with polymorphic embeddable types directly in queries.

treat()

The function treat() may be used to narrow the type of an identification variable:

List<Owner> owners = entityManager.createQuery(
    "select o " +
    "from Owner o " +
    "where treat(o.pet as Cat).mother = :mother",
    Owner.class)
.setParameter( "mother", mother )
.getResultList();

Once again, see the types and typecasts user guide chapter for more details.

Benefits and Limitations

In conclusion, I’ve listed here a couple benefits and limitations that characterize this new feature:

Benefits:

  • Code reusability: Common fields are defined in the parent embeddable classes, promoting reuse and reducing redundancy.

  • Polymorphic queries: You can use the type() and treat() functions to handle polymorphic queries effectively.

Limitations:

  • Not supported for composite ids: inheritance is not supported for embeddables used as primary keys.

  • Complexity: Managing discriminator columns can add complexity to your database schema, especially when dealing with multiple embeddable properties.

Outlooks

Embeddable inheritance in Hibernate ORM provides an additional tool for designing clean, modular, and reusable data models. By leveraging discriminator columns, our framework allows you to map complex inheritance hierarchies to relational database structures seamlessly. While it adds a layer of complexity, the benefits in terms of code maintainability and query capabilities often outweigh the downsides.

As always, we’re open to improvement requests and discussions about the new features we implement. If you want to let us know what you think of this topic or if you have any questions, please reach us through the usual channels.


Back to top