Сущности Panache: активная запись
Сущность (entity) — Java-класс, объекты которого хранятся в таблице базы данных: класс — таблица, поле — столбец, объект — строка. Сущностями управляет Hibernate ORM. Panache — надстройка Quarkus над Hibernate, которая убирает шаблонный код. Её первый стиль называется активная запись (active record): методы для работы с базой есть у самого класса сущности — Post.listAll(), post.persist().
Сущность поста
Запись Post из прошлой главы заменяем классом-сущностью. Удалите Post.java и PostService.java и создайте новый Post.java:
// src/main/java/com/example/blog/Post.java
package com.example.blog;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import java.time.LocalDateTime;
@Entity
public class Post extends PanacheEntity {
public String title;
@Column(length = 5000)
public String body;
public LocalDateTime createdAt = LocalDateTime.now();
}@Entity делает класс сущностью, таблица получит имя Post. От PanacheEntity класс наследует поле id с автоматической нумерацией и методы работы с базой. Поля публичные — так принято в Panache: геттеры и сеттеры Quarkus создаст сам при сборке. @Column(length = 5000) разрешает длинный текст, по умолчанию строка ограничена 255 символами.
Методы активной записи
| Вызов | Что делает |
|---|---|
Post.listAll() | Все строки таблицы |
Post.findById(id) | Одна строка или null |
Post.findByIdOptional(id) | То же, но в Optional |
Post.count() | Число строк |
post.persist() | Сохранить новый объект |
post.delete() | Удалить объект |
Post.deleteById(id) | Удалить по id, вернуть true, если строка была |
Ресурс на базе данных
// src/main/java/com/example/blog/PostResource.java
@Path("/posts")
public class PostResource {
@GET
public List<Post> list() {
return Post.listAll();
}
@GET
@Path("/{id}")
public Post get(@PathParam("id") long id) {
return Post.<Post>findByIdOptional(id)
.orElseThrow(() -> new PostNotFoundException(id));
}
@POST
@Transactional
public Response create(@Valid NewPost newPost) {
Post post = new Post();
post.title = newPost.title();
post.body = newPost.body();
post.persist();
return Response.created(URI.create("/posts/" + post.id)).entity(post).build();
}
@DELETE
@Path("/{id}")
@Transactional
public Response delete(@PathParam("id") long id) {
if (!Post.deleteById(id)) {
throw new PostNotFoundException(id);
}
return Response.noContent().build();
}
}Методы, которые меняют данные, помечены @Transactional (импорт jakarta.transaction.Transactional) — подробнее о транзакциях в отдельном уроке. Запись в Post.<Post>findByIdOptional подсказывает компилятору тип результата. Запись NewPost с проверками остаётся прежней.
Начальные данные
Таблицы в dev mode пересоздаются при каждом старте. Чтобы база не была пустой, добавим бин, который слушает событие запуска приложения:
// src/main/java/com/example/blog/Startup.java
package com.example.blog;
import io.quarkus.runtime.StartupEvent;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.transaction.Transactional;
@ApplicationScoped
public class Startup {
@Transactional
void onStart(@Observes StartupEvent event) {
if (Post.count() > 0) {
return;
}
Post first = new Post();
first.title = "Привет, Quarkus";
first.body = "Первый пост блога.";
first.persist();
Post second = new Post();
second.title = "Режим разработки";
second.body = "Меняйте код и обновляйте страницу.";
second.persist();
}
}@Observes StartupEvent — метод вызовется один раз при старте. Проверка count() > 0 не даст дублировать посты в базе, которая хранит данные между запусками.
curl http://localhost:8080/posts[{"id":1,"title":"Привет, Quarkus","body":"Первый пост блога.","createdAt":"2026-10-04T11:55:10.403195"},
{"id":2,"title":"Режим разработки","body":"Меняйте код и обновляйте страницу.","createdAt":"2026-10-04T11:55:10.430728"}]
Создание через POST /posts и удаление через DELETE /posts/1 работают так же, как раньше, только теперь данные лежат в таблице. В Dev UI на карточке Hibernate ORM видна сущность Post и её таблица.
Частая ошибка
Если убрать @Transactional с метода create, сохранение упадёт с ответом 500, а в логе будет:
jakarta.persistence.TransactionRequiredException: Transaction is not active, consider adding @Transactional to your method to automatically activate one.
Чтение работает и без транзакции, а любое изменение — persist, delete, правка полей — требует её. Сообщение прямо подсказывает решение.