Encodera

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

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

Оглавление

Сущности 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, правка полей — требует её. Сообщение прямо подсказывает решение.

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

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