Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Наследование

Реляционные таблицы ничего не знают о наследовании, а 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 со стратегией наследования.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.