Встраиваемые типы
Некоторые поля связаны между собой: email и сайт составляют контакты автора, город и улица — адрес. Такую группу можно вынести в отдельный класс и встроить в сущность. У встраиваемого класса нет своей таблицы и своего id: его поля хранятся обычными столбцами в таблице сущности-владельца. Начиная с Hibernate 6.2 встраиваемым типом может быть запись (record).
Встраиваемый тип
// src/main/java/com/example/blog/Contact.java
package com.example.blog;
import jakarta.persistence.Embeddable;
@Embeddable
public record Contact(String email, String website) {
}@Embeddable отмечает класс, который можно встраивать. В отличие от сущности, запись здесь подходит: значение заменяют целиком, менять его по одному полю не нужно.
Сущность Author
// src/main/java/com/example/blog/Author.java
package com.example.blog;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
@Entity
public class Author {
@Id
@GeneratedValue
private Long id;
private String name;
@Embedded
private Contact contact;
protected Author() {
}
public Author(String name, Contact contact) {
this.name = name;
this.contact = contact;
}
public String getName() {
return name;
}
public Contact getContact() {
return contact;
}
public void setContact(Contact contact) {
this.contact = contact;
}
}Добавьте <class>com.example.blog.Author</class> в persistence.xml. Запись Contact указывать не нужно — это не сущность.
Сохранение и поиск
// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> em.persist(
new Author("Анна", new Contact("anna@example.com", "https://anna.example.com"))));
Author author = emf.callInTransaction(em -> em
.createQuery("select a from Author a where a.contact.email = :email", Author.class)
.setParameter("email", "anna@example.com")
.getSingleResult());
IO.println(author.getName() + ": " + author.getContact());В запросе к встроенным полям обращаются через точку: a.contact.email. Вывод:
Hibernate: create table Author (id bigint not null, email varchar(255), name varchar(255), website varchar(255), primary key (id))
Hibernate: insert into Author (email,website,name,id) values (?,?,?,?)
Hibernate: select a1_0.id,a1_0.email,a1_0.website,a1_0.name from Author a1_0 where a1_0.email=?
Анна: Contact[email=anna@example.com, website=https://anna.example.com]Столбцы email и website находятся прямо в таблице Author — отдельной таблицы для контактов нет.
Изменение значения
Запись менять нельзя, поэтому новое значение заменяет старое:
// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> {
Author author = em.find(Author.class, 1L);
author.setContact(new Contact("anna@example.org", author.getContact().website()));
});При фиксации Hibernate видит новый объект и выполняет update Author set email=?,website=?,name=? where id=?.
Переименование столбцов
Имена столбцов берутся из компонентов записи. Чтобы поменять какое-то из них, рядом с @Embedded ставят @AttributeOverride:
// src/main/java/com/example/blog/Author.java
@Embedded
@AttributeOverride(name = "email", column = @Column(name = "contact_email", unique = true))
private Contact contact;Теперь в таблице столбец contact_email varchar(255) unique. Тот же приём позволяет встроить один тип дважды, например адрес оплаты и адрес доставки.
Частая ошибка
Если все столбцы встроенного значения равны null, Hibernate читает всё значение как null:
// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> em.persist(new Author("Олег", new Contact(null, null))));
Author oleg = emf.callInTransaction(em -> em.find(Author.class, 1L));
IO.println(oleg.getContact().email());Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.example.blog.Contact.email()" because the return value of "com.example.blog.Author.getContact()" is nullПроверяйте встроенное значение на null, прежде чем им пользоваться.