Наследование
Реляционные таблицы ничего не знают о наследовании, а Java-классы часто образуют иерархии. Допустим, к статьям блога можно прикреплять медиафайлы: изображения с шириной и высотой и видео с длительностью. И то и другое — «медиа» с названием. Hibernate умеет хранить такую иерархию несколькими способами — их называют стратегиями наследования.
Иерархия
// src/main/java/com/example/blog/Media.java
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Media {
@Id
@GeneratedValue
private Long id;
private String title;
protected Media() {
}
protected Media(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
public abstract String describe();
}// src/main/java/com/example/blog/Image.java
@Entity
public class Image extends Media {
private int width;
private int height;
protected Image() {
}
public Image(String title, int width, int height) {
super(title);
this.width = width;
this.height = height;
}
@Override
public String describe() {
return "изображение " + width + "x" + height;
}
}Video пишется так же, с полем int seconds. Все три класса перечислены в persistence.xml.
Полиморфный запрос
// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> {
em.persist(new Image("Обложка", 1200, 630));
em.persist(new Video("Демо", 95));
});
List<Media> media = emf.callInTransaction(em ->
em.createQuery("select m from Media m order by m.id", Media.class).getResultList());
for (Media m : media) {
IO.println(m.getTitle() + ": " + m.describe());
}Запрос к Media возвращает объекты нужных подклассов:
Hibernate: create table Media (height integer, seconds integer, width integer, id bigint not null, DTYPE varchar(31) not null check ((DTYPE in ('Image','Video'))), title varchar(255), primary key (id), ...)
Hibernate: insert into Media (title,height,width,DTYPE,id) values (?,?,?,'Image',?)
Hibernate: insert into Media (title,seconds,DTYPE,id) values (?,?,'Video',?)
Hibernate: select m1_0.id,m1_0.DTYPE,m1_0.title,m1_0.height,m1_0.width,m1_0.seconds from Media m1_0 order by m1_0.id
Обложка: изображение 1200x630
Демо: видео, 95 сПри SINGLE_TABLE вся иерархия живёт в одной таблице. Столбец DTYPE (дискриминатор) хранит имя класса, а столбцы других подклассов остаются пустыми. Это стратегия по умолчанию и самая быстрая: никаких соединений таблиц.
JOINED
Замените стратегию на InheritanceType.JOINED — и каждый класс получит свою таблицу, связанную по id:
Hibernate: create table Image (height integer not null, width integer not null, id bigint not null, primary key (id))
Hibernate: create table Media (id bigint not null, title varchar(255), primary key (id))
Hibernate: create table Video (seconds integer not null, id bigint not null, primary key (id))
Hibernate: insert into Media (title,id) values (?,?)
Hibernate: insert into Image (height,width,id) values (?,?,?)
Hibernate: select ... from Media m1_0 left join Image m1_1 on m1_0.id=m1_1.id left join Video m1_2 on m1_0.id=m1_2.id order by m1_0.id| Стратегия | Таблицы | Плюсы и минусы |
|---|---|---|
SINGLE_TABLE | Одна | Быстро; столбцы подклассов должны допускать null |
JOINED | По одной на класс | Чистая структура; каждый запрос соединяет таблицы |
TABLE_PER_CLASS | По одной на конкретный класс | Полиморфные запросы через union; используется редко |
Общие поля без иерархии
Часто нужно просто, чтобы у всех сущностей были одинаковые id и дата создания. Для этого есть @MappedSuperclass:
// src/main/java/com/example/blog/BaseEntity.java
@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue
private Long id;
private LocalDateTime createdAt = LocalDateTime.now();
public Long getId() {
return id;
}
}Класс Tag extends BaseEntity получит таблицу Tag (createdAt timestamp(6), id bigint not null, name varchar(255), ...). Своей таблицы у суперкласса нет.
Частая ошибка
@MappedSuperclass — не сущность, поэтому запрашивать его нельзя:
java.lang.IllegalArgumentException: org.hibernate.query.sqm.UnknownEntityException: Could not resolve root entity 'BaseEntity'Если нужны запросы сразу по всем подклассам, сделайте базовый класс @Entity со стратегией наследования.