Embeddable types
Historically Hibernate called these components. JPA calls them embeddables. Either way the concept is the same: a composition of values.
For example we might have a Publisher
class that is a composition of name
and country
,
or a Location
class that is a composition of country
and city
.
Usage of the word embeddable
To avoid any confusion with the annotation that marks a given embeddable type, the annotation will be further referred as Throughout this chapter and thereafter, for brevity sake, embeddable types may also be referred as embeddable. |
@Embeddable
public static class Publisher {
private String name;
private Location location;
public Publisher(String name, Location location) {
this.name = name;
this.location = location;
}
private Publisher() {}
//Getters and setters are omitted for brevity
}
@Embeddable
public static class Location {
private String country;
private String city;
public Location(String country, String city) {
this.country = country;
this.city = city;
}
private Location() {}
//Getters and setters are omitted for brevity
}
An embeddable type is another form of value type, and its lifecycle is bound to a parent entity type, therefore inheriting the attribute access from its parent (for details on attribute access, see Access strategies).
Embeddable types can be made up of basic values as well as associations, with the caveat that, when used as collection elements, they cannot define collections themselves.
Component / Embedded
Most often, embeddable types are used to group multiple basic type mappings and reuse them across several entities.
@Entity(name = "Book")
public static class Book {
@Id
@GeneratedValue
private Long id;
private String title;
private String author;
private Publisher publisher;
//Getters and setters are omitted for brevity
}
@Embeddable
public static class Publisher {
@Column(name = "publisher_name")
private String name;
@Column(name = "publisher_country")
private String country;
public Publisher(String name, String country) {
this.name = name;
this.country = country;
}
private Publisher() {}
//Getters and setters are omitted for brevity
}
create table Book (
id bigint not null,
author varchar(255),
publisher_country varchar(255),
publisher_name varchar(255),
title varchar(255),
primary key (id)
)
JPA defines two terms for working with an embeddable type:
|
So, the embeddable type is represented by the Publisher
class and
the parent entity makes use of it through the book#publisher
object composition.
The composed values are mapped to the same table as the parent table. Composition is part of good Object-oriented data modeling (idiomatic Java). In fact, that table could also be mapped by the following entity type instead.
@Entity(name = "Book")
public static class Book {
@Id
@GeneratedValue
private Long id;
private String title;
private String author;
@Column(name = "publisher_name")
private String publisherName;
@Column(name = "publisher_country")
private String publisherCountry;
//Getters and setters are omitted for brevity
}
The composition form is certainly more Object-oriented, and that becomes more evident as we work with multiple embeddable types.
Multiple embeddable types
Although from an object-oriented perspective, it’s much more convenient to work with embeddable types, this example doesn’t work as-is. When the same embeddable type is included multiple times in the same parent entity type, the JPA specification demands setting the associated column names explicitly.
This requirement is due to how object properties are mapped to database columns. By default, JPA expects a database column having the same name with its associated object property. When including multiple embeddables, the implicit name-based mapping rule doesn’t work anymore because multiple object properties could end-up being mapped to the same database column.
We have a few options to handle this issue.
Overriding Embeddable types
JPA defines the @AttributeOverride
annotation to handle this scenario.
This way, the mapping conflict is resolved by setting up explicit name-based property-column type mappings.
If an Embeddabe type is used multiple times in some entity, you need to use the
@AttributeOverride
and
@AssociationOverride
annotations
to override the default column names definied by the Embeddable.
Considering you have the following Publisher
embeddable type
which defines a @ManyToOne
association with the Country
entity:
@ManyToOne
association@Embeddable
public static class Publisher {
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Country country;
public Publisher(String name, Country country) {
this.name = name;
this.country = country;
}
private Publisher() {}
//Getters and setters are omitted for brevity
}
@Entity(name = "Country")
public static class Country {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String name;
//Getters and setters are omitted for brevity
}
create table Country (
id bigint not null,
name varchar(255),
primary key (id)
)
alter table Country
add constraint UK_p1n05aafu73sbm3ggsxqeditd
unique (name)
Now, if you have a Book
entity which declares two Publisher
embeddable types for the ebook and paperback version,
you cannot use the default Publisher
embeddable mapping since there will be a conflict between the two embeddable column mappings.
Therefore, the Book
entity needs to override the embeddable type mappings for each Publisher
attribute:
@Entity(name = "Book")
@AttributeOverrides({
@AttributeOverride(
name = "ebookPublisher.name",
column = @Column(name = "ebook_publisher_name")
),
@AttributeOverride(
name = "paperBackPublisher.name",
column = @Column(name = "paper_back_publisher_name")
)
})
@AssociationOverrides({
@AssociationOverride(
name = "ebookPublisher.country",
joinColumns = @JoinColumn(name = "ebook_publisher_country_id")
),
@AssociationOverride(
name = "paperBackPublisher.country",
joinColumns = @JoinColumn(name = "paper_back_publisher_country_id")
)
})
public static class Book {
@Id
@GeneratedValue
private Long id;
private String title;
private String author;
private Publisher ebookPublisher;
private Publisher paperBackPublisher;
//Getters and setters are omitted for brevity
}
create table Book (
id bigint not null,
author varchar(255),
ebook_publisher_name varchar(255),
paper_back_publisher_name varchar(255),
title varchar(255),
ebook_publisher_country_id bigint,
paper_back_publisher_country_id bigint,
primary key (id)
)
alter table Book
add constraint FKm39ibh5jstybnslaoojkbac2g
foreign key (ebook_publisher_country_id)
references Country
alter table Book
add constraint FK7kqy9da323p7jw7wvqgs6aek7
foreign key (paper_back_publisher_country_id)
references Country
Embeddables and ImplicitNamingStrategy
This is a Hibernate specific feature.
Users concerned with JPA provider portability should instead prefer explicit column naming with |
Hibernate naming strategies are covered in detail in Naming. However, for the purposes of this discussion, Hibernate has the capability to interpret implicit column names in a way that is safe for use with multiple embeddable types.
@Entity(name = "Book")
public static class Book {
@Id
@GeneratedValue
private Long id;
private String title;
private String author;
private Publisher ebookPublisher;
private Publisher paperBackPublisher;
//Getters and setters are omitted for brevity
}
@Embeddable
public static class Publisher {
private String name;
@ManyToOne(fetch = FetchType.LAZY)
private Country country;
public Publisher(String name, Country country) {
this.name = name;
this.country = country;
}
private Publisher() {}
//Getters and setters are omitted for brevity
}
@Entity(name = "Country")
public static class Country {
@Id
@GeneratedValue
private Long id;
@NaturalId
private String name;
//Getters and setters are omitted for brevity
}
To make it work, you need to use the ImplicitNamingStrategyComponentPathImpl
naming strategy.
metadataBuilder.applyImplicitNamingStrategy(
ImplicitNamingStrategyComponentPathImpl.INSTANCE
);
Now the "path" to attributes are used in the implicit column naming:
create table Book (
id bigint not null,
author varchar(255),
ebookPublisher_name varchar(255),
paperBackPublisher_name varchar(255),
title varchar(255),
ebookPublisher_country_id bigint,
paperBackPublisher_country_id bigint,
primary key (id)
)
You could even develop your own naming strategy to do other types of implicit naming strategies.
Collections of embeddable types
Collections of embeddable types are specifically value collections (as embeddable types are a value type). Value collections are covered in detail in Collections of value types.
Embeddable types as Map key
Embeddable types can also be used as Map
keys.
This topic is converted in detail in Map - key.
Embeddable types as identifiers
Embeddable types can also be used as entity type identifiers. This usage is covered in detail in Composite identifiers.
Embeddable types that are used as collection entries, map keys or entity type identifiers cannot include their own collection mappings. |