JPA¶
- JPA (Java Persistence API) - стандарт Java EE для объектно-реляционного отображения (ORM). Определяет набор
аннотаций и интерфейсов (
jakarta.persistence.*), которые позволяют описывать маппинг сущностей (POJO-классов) на таблицы базы данных и управлять ими черезEntityManager.
Конфигурирование¶
pom.xml:
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.0.0.Final</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.1</version>
</dependency>
src/main/resources/META-INF/persistence.xml:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence
https://jakarta.ee/xml/ns/persistence/persistence_3_1.xsd"
version="3.1">
<!-- Идентификатор persistence-unit -->
<persistence-unit name="myPersistenceUnit" transaction-type="RESOURCE_LOCAL">
<!-- Провайдер JPA (Hibernate) -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- Список управляемых сущностей -->
<!-- Можно не указывать, тогда Hibernate сам отсканирует аннотированные классы -->
<!--<class>com.example.domain.User</class>-->
<!--<class>com.example.domain.Order</class>-->
<!-- JDBC-соединение -->
<properties>
<!-- Драйвер -->
<property name="jakarta.persistence.jdbc.driver" value="org.postgresql.Driver"/>
<!-- URL -->
<property name="jakarta.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/mydb"/>
<!-- Пользователь/пароль -->
<property name="jakarta.persistence.jdbc.user" value="myuser"/>
<property name="jakarta.persistence.jdbc.password" value="mypassword"/>
<!-- Dialect Hibernate -->
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
<!-- Формирование DDL (validate | update | create | create-drop) -->
<property name="hibernate.hbm2ddl.auto" value="update"/>
<!-- Логирование SQL -->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<!-- Вторичный кэш (опционально) -->
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.region.factory_class"
value="org.hibernate.cache.jcache.JCacheRegionFactory"/>
<property name="hibernate.javax.cache.provider"
value="org.ehcache.jsr107.EhcacheCachingProvider"/>
<property name="hibernate.javax.cache.uri"
value="ehcache.xml"/>
<!-- (Опционально) Пул соединений HikariCP -->
<property name="hibernate.connection.provider_class"
value="com.zaxxer.hikari.hibernate.HikariConnectionProvider"/>
<property name="hibernate.hikari.maximumPoolSize" value="10"/>
<property name="hibernate.hikari.minimumIdle" value="2"/>
<property name="hibernate.hikari.idleTimeout" value="30000"/>
</properties>
</persistence-unit>
</persistence>
JPA Аннотации классов (Class-Level Annotations)¶
@EntityПомечает класс как сущность, т.е. объект, который может быть сохранён в базе данных. Обязателен.
Обязательные требования:
- Класс должен быть public (не private или final).
- Должен иметь конструктор без аргументов (public или protected).
- Не должен быть final.
-
Поля должны быть доступны через геттеры/сеттеры.
-
@TableОписывает соответствующую таблицу в базе. Атрибуты:nameимя таблицы. Если не указано, JPA использует имя класса.schema(опционально) схема БД.uniqueConstraints(опционально) рантайме они ничего не делают (это инструкция для генерации констеинтов), но если констреинт создан уже в бд, то они ничего не делаютindexes(опционально) рантайме они ничего не делают (это инструкция для генерации схемы), но если схема создана уже в бд, то они ничего не делают |name- имя индекса в БД,columnList- колонки, по которым строится индекс (через запятую, если составной).
-
@Inheritance(опционально) Стратегия наследования (для иерархий сущностей). Атрибуты:strategyЗначения:InheritanceType.SINGLE_TABLE(по умолчанию).InheritanceType.JOINEDInheritanceType.TABLE_PER_CLASS
-
@Cacheable(опционально) Помечает сущность как кэшируемую вторичным уровнем (JPA-спецификация). -
@Embeddableдля встраиваемых value-объектов (не сущность)
JPA Аннотации полей (Field-Level Annotations)¶
@Id Единственная обязательная аннотация в любой сущности (указывает первичный ключ)
@GeneratedValue
(GenerationType.AUTO0) дать ORM провайдеру самому решить, что лучше для этой БД (На PostgreSQL Hibernate обычно
выберет
SEQUENCE)
(GenerationType.IDENTITY) id генерится только при реальном INSERT в БД, ORM не знает id на момент вставки
Как это работает:
- При INSERT Hibernate не передаёт id - колонка сама генерит значение.
- После вставки Hibernate делает:
- либо SELECT LAST_INSERT_ID() / SELECT @@IDENTITY,
- либо использует JDBC API getGeneratedKeys() и узнаёт сгенерированный id.
Особенности:
- id генерится только при реальном INSERT в БД.
- Hibernate не может сделать батч-вставку нормально с IDENTITY (у него нет id заранее).
- Нельзя “провернуть” вставку без доступа к БД.
Плюсы:
- Простейшая настройка, идеально для MySQL.
- Всё делает база, надёжно и понятно.
Минусы:
- Плохая поддержка batch insert.
- id появляется только после INSERT -> ORM сложнее оптимизировать работу.
(GenerationType.SEQUENCE) ORM заранее запрашивает пачку ID, раздать их объектам ещё до INSERT
Как это работает:
- Есть отдельный объект БД - sequence, который умеет говорить:
- nextval('user_seq') -> 1, 2, 3, 4..
- Hibernate может:
- заранее запросить пачку ID,
- раздать их объектам ещё до INSERT.
Про allocationSize:
- allocationSize = 50 значит: Hibernate один раз дернёт sequence (получит “блок”) и сам выдает 50 id в памяти.
- Это сильно уменьшает число обращений к sequence.
- Но важно: реальная последовательность в БД тоже должна быть согласована (шаги, границы), чтобы не было дыр/конфликтов.
Плюсы:
- Отлично работает с batch insert: id известно до вставки.
- Гибкая оптимизация (pooled, hi/lo).
- Правильный и рекомендуемый способ для PostgreSQL.
Минусы:
- Нужна поддержка sequence в БД.
- Нужно аккуратно настроить allocationSize, чтобы не встрять.
CREATE SEQUENCE user_seq START 1 INCREMENT 1;
CREATE TABLE users
(
id BIGINT PRIMARY KEY DEFAULT nextval('user_seq'),
name TEXT
);
@Entity
@SequenceGenerator(
name = "user_seq_gen",
sequenceName = "user_seq",
allocationSize = 50
)
class User {
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_seq_gen")
private Long id;
}
(GenerationType.TABLE) самый костыльный способ, когда БД не умеет ни identity, ни sequence. Идея: симулируем
sequence через обычную таблицу
Как это работает:
- Перед вставкой Hibernate:
- делает SELECT next_val FROM id_generator WHERE id_name = 'user' FOR UPDATE;
- увеличивает next_val;
- использует старое значение как id или блок значений (с учётом allocationSize).
- Таблица id_generator играет роль псевдо-sequence.
Плюсы:
- Работает на любой нормальной SQL-БД.
- Не требует identity/sequence.
Минусы:
- Медленнее: каждый блок id -> отдельная запись в таблице, блокировка.
- Может быть узким местом по конкурентности.
- Сейчас почти не актуально, т.к. почти все БД умеют identity/sequence.
CREATE TABLE id_generator
(
id_name VARCHAR(50) PRIMARY KEY,
next_val BIGINT
);
INSERT INTO id_generator (id_name, next_val)
VALUES ('user', 1);
@Entity
@TableGenerator(
name = "user_table_gen",
table = "id_generator",
pkColumnName = "id_name",
valueColumnName = "next_val",
pkColumnValue = "user",
allocationSize = 50
)
class User {
@Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "user_table_gen")
private Long id;
}
@ColumnОсновная аннотация для маппинга поля на столбец. Атрибуты:nameString Имя столбца в БД (если отличается от имени поля)nullableboolean Может ли поле быть NULL (по умолчанию true)uniqueboolean Должно ли поле быть уникальным (аналог UNIQUE в SQL)lengthint Максимальная длина для строк (аналог VARCHAR(255))precisionint Общее количество цифр (для DECIMAL)scaleint Количество цифр после запятой (для DECIMAL)insertableboolean Участвует ли поле в INSERT (по умолчанию true)updatableboolean Участвует ли поле в UPDATE (по умолчанию true)columnDefinitionString Позволяет задать точный SQL-тип столбца
@BasicУказывает, что поле - обычный примитив/строка/обёртка. Атрибуты:fetchEAGER (по умолчанию) или LAZY (для больших типов, @Lob).optionalто же, что nullable (для примитивов всегда false).
@LobПомечает поле как CLOB или BLOB. Работает с String, Clob, byte[], Blob@TemporalДля java.util.Date или java.util.Calendar:TemporalType.DATE(только дата).TemporalType.TIME(только время).TemporalType.TIMESTAMP(дата+время).
@EnumeratedДля enum-полей. По умолчанию ORDINAL, но рекомендуется:EnumType.STRING- хранить строковое значение.
@TransientПоле не будет маппиться на БД.
JPA Связи¶
@JoinColumnиспользуется на стороне отношения, которая хранит внешний ключ (FK)nameИмя колонки в текущей таблице, которая хранит FKnullable, uniqueограничения в DDL.updatable/insertableесли столбец заполняется триггером/вьюхой и т.п.referencedColumnNameстолбец в целевой таблице (обычно id).foreignKey = @ForeignKey(name = "...")имя констрейнта.
@ManyToOne/@OneToMany
mappedBycascadeorphanRemoval
FK (внешний ключ) хранит сторона @ManyToOne -- она владельческая. Рекомендованная модель: двусторонняя связь: Child (владелец) <-> Parent (обратная, mappedBy).
бд:
parent(id PK, ...)
child(id PK, parent_id FK -> parent.id NOT NULL, ...)
/* индекс нужен: */ INDEX(child.parent_id)
Базовая (рекомендованная) двусторонняя связь:
// Parent - обратная сторона, коллекция детей
@Entity
class Parent {
@Id @GeneratedValue Long id;
@OneToMany(
mappedBy = "parent", // FK в Child
cascade = CascadeType.ALL, // сохраняем/обновляем через Parent
orphanRemoval = true, // удаление ребёнка при исключении из коллекции
fetch = FetchType.LAZY
)
private List<Child> children = new ArrayList<>();
// Хелперы: всегда синхронизируют обе стороны!
public void addChild(Child c) {
children.add(c);
c.setParent(this);
}
public void removeChild(Child c) {
children.remove(c);
c.setParent(null);
}
}
// Child - владельческая сторона, FK здесь
@Entity
class Child {
@Id @GeneratedValue Long id;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "parent_id", nullable = false, foreignKey = @ForeignKey(name = "fk_child_parent"))
private Parent parent;
}
Нет лишней join-table; простая и быстрая схема. orphanRemoval = true + хелперы = безопасное удаление «детей» из БД при удалении из коллекции. LAZY минимизирует «жирные» запросы. если бы было бы Односторонне (@OneToMany без mappedBy) -- JPA создаст join-table. Обычно не нужно.
ОШИБКИ:
Создалась лишняя join-table Причина: односторонний @OneToMany без mappedBy. Фикс: сделайте связь двусторонней (mappedBy = "parent" у Parent и FK в Child). Либо (Hibernate-only) используйте @JoinColumn на @OneToMany.
Дети не сохраняются/обновляются вместе с родителем Причина: нет каскадов PERSIST/MERGE. Фикс: cascade = { PERSIST, MERGE } (или ALL, если вы уверены). Лайфхак: если сохраняете через Parent, добавляйте детей только через addChild.
N+1 запросов Фикс: используйте fetch join / @EntityGraph / @BatchSize.
equals/hashCode, toString и коллекции Не включайте ленивые коллекции в toString - получите рекурсию/тонны SQL. Для equals/hashCode: безопаснее по уникальному бизнес-ключу или по id (только после присвоения). Set + неустойчивый equals до присвоения id = загадочные дубликаты. Проще начинать с List.
@OneToOne
nameимя FK-колонки у владельца.fetchТИП:FetchType. Загрузка связанной сущности:EAGER(по умолчанию) илиLAZY.mappedByИмя поля во владельческой сущности (у кого FK). Используется на обратной стороне. Если задано, эта сторона не владеет FK.cascadeТИП:CascadeType. Массовые операции:PERSIST,MERGE,REMOVE,REFRESH,DETACH,ALL. Позволяет прокинуть операции из родителя на зависимую сущность.optionalЕслиfalse, связь обязательна: в DDL для FK будетNOT NULL. Синхронизировать с@JoinColumn(nullable = false).orphanRemovalЕслиtrue, при снятии связи (присвоенииnull) зависимая сущность удалится из БД автоматически.
Владелец - где FK (там ставим @JoinColumn). Обратная сторона (inverse) - та, что через mappedBy ссылается на владеющую Делайте LAZY. Когда ставить orphanRemoval = true? Когда жизнь зависимой сущности полностью подчинена родителю.
@Entity
class Order {
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "invoice_id", unique = true, nullable = false)
private Invoice invoice;
}
@Entity
class Invoice {
@OneToOne(mappedBy = "invoice", fetch = FetchType.LAZY)
private Order order;
}
@ManyToMany
nameимя FK-колонки у владельца.fetch
Используется редко, когда между сущностями нет своей таблицы-сущности. Нужна join-table с двумя FKs. Одна сторона - владелец (без mappedBy).
бд:
a(id PK, ...)
b(id PK, ...)
a_b(
a_id FK -> a.id NOT NULL,
b_id FK -> b.id NOT NULL,
PRIMARY KEY (a_id, b_id)
)
CREATE INDEX idx_a_b__a ON a_b(a_id);
CREATE INDEX idx_a_b__b ON a_b(b_id);
Базовая двусторонняя модель (рекомендуется):
@Entity
class A {
@Id @GeneratedValue Long id;
// Владеющая сторона (нет mappedBy) - она пишет в a_b
@ManyToMany
@JoinTable(
name = "a_b",
joinColumns = @JoinColumn(name = "a_id", foreignKey = @ForeignKey(name="fk_ab_a")),
inverseJoinColumns = @JoinColumn(name = "b_id", foreignKey = @ForeignKey(name="fk_ab_b"))
)
private Set<B> bs = new HashSet<>();
// хелперы держат обе стороны в синхроне
public void addB(B b) {
if (bs.add(b)) b.getAs().add(this);
}
public void removeB(B b) {
if (bs.remove(b)) b.getAs().remove(this);
}
}
@Entity
class B {
@Id @GeneratedValue Long id;
// Обратная сторона
@ManyToMany(mappedBy = "bs")
private Set<A> as = new HashSet<>();
public Set<A> getAs() { return as; }
}
Нет лишних сущностей - только join-таблица. Оба направления навигации есть. Set предотвращает дубликаты на уровне коллекции (и всё равно держим UNIQUE в БД!).
Односторонняя модель (нормальный вариант, если обратная навигация не нужна):
@Entity
class A {
@Id @GeneratedValue Long id;
@ManyToMany
@JoinTable(
name = "a_b",
joinColumns = @JoinColumn(name = "a_id"),
inverseJoinColumns = @JoinColumn(name = "b_id")
)
private Set<B> bs = new HashSet<>();
}
@Entity
class B {
@Id @GeneratedValue Long id;
// нет обратной коллекции
}
Проще код, меньше синхронизации в памяти. Всё равно нужна уникальность пары в БД, индексы и т.д.
Как только у связи появляются свои поля (роль, статус, позиция, даты, комментарий) - не @ManyToMany. Делайте link-сущность:
@Entity
class AB {
@EmbeddedId
private ABId id;
@ManyToOne(fetch = FetchType.LAZY) @MapsId("aId")
@JoinColumn(name = "a_id")
private A a;
@ManyToOne(fetch = FetchType.LAZY) @MapsId("bId")
@JoinColumn(name = "b_id")
private B b;
// дополнительные поля связи
private String role;
private Integer position;
private LocalDate since;
}
@Embeddable
class ABId implements Serializable {
private Long aId;
private Long bId;
// equals/hashCode по обоим полям
}
Плюсы: можно хранить доп. атрибуты, уникальные ключи, порядок, статусы. Минусы: на один класс больше, но это правильная модель.
PERSIST, MERGE - часто на aggregate-границах (родитель -> дети). REMOVE - только если дочке нельзя жить без родителя (и вы реально хотите физическое удаление). orphanRemoval = true - при parent.getChildren().remove(x) или child.setParent(null) запись удалится. Ставьте LAZY везде, где это разумно (особенно @ManyToOne, @OneToOne). Борьба с N+1: join fetch в JPQL, @EntityGraph, батч-фетч (@BatchSize). Не ставьте много EAGER - тяжёлые запросы и неожиданные циклические загрузки. equals/hashCode: не используйте ленивые коллекции; безопасно - по бизнес-ключу или по id (после присвоения).
Где физически FK? -> там владелец и @JoinColumn. Нужна ли обратная навигация? -> mappedBy на обратной стороне. Связь обязательна? -> optional=false + nullable=false. Нужен каскад? -> минимум, только то, что реально нужно. Ожидаются пачки чтения? -> LAZY + EntityGraph/join fetch.
Persistence Unit¶
Persistence Unit (PU) - это конфигурация JPA:
- какая БД
- какие сущности
- какие настройки Hibernate
- имя юнита (name="myPu" в persistence.xml или spring.jpa.*)
Из Persistence Unit создаётся:
- EntityManagerFactory
- через него уже создается EntityManager
1 Persistence Unit = 1 БД
Persistence Context¶
@PersistenceContext - аннотация Jakarta EE, которая говорит контейнеру инжектировать сюда EntityManager, связанный с
Persistence Unit + Это набор управляемых сущностей в памяти + их состояние.
PersistenceContext - это внутренняя структура, которую EntityManager использует, чтобы хранить и отслеживать все Entity-объекты, находящиеся в состоянии Managed.
Он живёт ровно столько, сколько живёт EntityManager (обычно - одну транзакцию или HTTP-запрос).
- Следит, какие объекты уже загружены из БД.
- Проверяет, изменились ли поля (dirty checking).
- Знает, какие объекты нужно INSERT, UPDATE, DELETE при flush().
- Следит за связями (@OneToMany, @ManyToOne), кэшем и каскадами.
- Отслеживает идентичность - если ты дважды вызовешь em.find(Admin.class, 1L), ты получишь тот же самый объект, а не новую копию.
Состояния сущностей в PersistenceContext
| Состояние | Где находится | Что делает JPA |
|---|---|---|
| New | вне контекста | объект ещё не сохранён |
| Managed | внутри PersistenceContext | JPA отслеживает изменения |
| Detached | вышел из контекста (например, после em.clear() или em.close()) |
больше не отслеживается |
| Removed | помечен на удаление | будет удалён при flush() |
EntityManagerFactory¶
EntityManagerFactory - фабрика ентити менеджеров
Создаётся один раз на всё приложение при старте (на основе persistence.xml). Она живёт весь runtime Это тяжёлый объект: он открывает JDBC-пулы, кэширует метаданные, маппинги сущностей, SQL-генерацию и т.п. Не создаёт соединения к БД сам - это делает EntityManager, который из неё берётся. Потокобезопасен и общий для всех потоков/запросов.
EntityManager¶
Это основной интерфейс JPA, через который приложение общается с базой данных.
Он управляет жизненным циклом Entity сущностей и выполняет операции CRUD, JPQL-запросы, транзакции и синхронизацию с БД.
На каждый HTTP-запрос (или транзакцию) контейнер создаёт новый экземпляр EntityManager, связанный с одним PersistenceContext.
Жизненный цикл EntityManager обычно совпадает с жизненным циклом запроса или JTA-транзакции. Он не потокобезопасен. После коммита или rollback - автоматически закрывается контейнером.
В Jakarta EE/Spring, EntityManager создаётся контейнером (WildFly, Payara, TomEE...) на основе Persistence Unit, описанной в persistence.xml
Когда контейнер поднимает приложение:
- читает persistence.xml
- создаёт EntityManagerFactory
- управляет EntityManager для каждого запроса или транзакции.
Контейнер внедряет управляемый EntityManager из пула. Этот EntityManager связан с текущим контекстом транзакции (или с @RequestScoped контекстом в CDI).
| Тип | Когда используется | Пример |
|---|---|---|
| Container-managed | Jakarta EE, Spring, Quarkus | @PersistenceContext - контейнер сам управляет транзакциями |
| Application-managed | Standalone-приложения | EntityManagerFactory emf = ...; EntityManager em = emf.createEntityManager(); |
Пример ручного использования (application-managed)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("studsPU");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Admin a = new Admin();
a.setLogin("root");
em.persist(a);
em.getTransaction().commit();
em.close();
emf.close();
EntityManager CRUD-операции¶
| Метод | Описание |
|---|---|
persist(entity) |
Добавить новый объект в контекст (INSERT при commit) |
find(EntityClass, id) |
Найти по PK (SELECT) |
merge(entity) |
Обновить detached объект (UPDATE) |
remove(entity) |
Удалить объект (DELETE) |
contains(entity) |
Проверить, управляется ли объект контекстом |
flush() |
Синхронизировать контекст с БД (выполнить все накопленные SQL) |
clear() |
Очистить контекст (все entity становятся detached) |
detach(entity) |
Отсоединить один объект от контекста |
Запросы (JPQL, Criteria, Native)
| Метод | Описание |
|---|---|
createQuery(String jpql, Class<T>) |
Создать JPQL-запрос (объектный SQL) |
createNamedQuery(String name, Class<T>) |
Использовать @NamedQuery из Entity |
createNativeQuery(String sql, Class<T>) |
Выполнить нативный SQL и замаппить на Entity |
createNativeQuery(String sql) |
Нативный SQL без маппинга |
createQuery(CriteriaQuery<T>) |
Типобезопасные запросы через Criteria API |
Транзакции (если не контейнер, а вручную)
| Метод | Описание |
|---|---|
getTransaction() |
Возвращает объект EntityTransaction |
joinTransaction() |
Присоединяет EM к текущей JTA-транзакции |
EntityManager Lifecycle Callbacks¶
Это хуки жизненного цикла сущности - точки, в которые JPA/Hibernate сам зайдёт и вызовет метод, когда что-то происходит с entity.
@PrePersistперед INSERT, Вызывается, когда сущность переходит transient → persistent и готовится к вставке@PostPersistсразу после INSERT, Вызывается после того, как сущность уже вставлена в БД@PreRemoveперед DELETE, Вызывается, когда сущность помечают на удаление@PostRemoveпосле DELETE, Срабатывает после удаления@PreUpdateперед UPDATE, Срабатывает перед тем, как Hibernate собирается обновить сущность@PostUpdateпосле UPDATE, Срабатывает после успешного UPDATE в БД@PostLoadпосле загрузки из БД, Срабатывает каждый раз, когда ORM загружает сущность из БД
Criteria API¶
Это объектная (типобезопасная) альтернатива JPQL.
Даёт возможность строить динамические запросы к базе данных не строками через типобезопасный Java-код в runtime.
Вместо строк "SELECT v FROM Vehicle v WHERE v.type = :t" создавать запрос с помощью Java-классов (CriteriaBuilder,
CriteriaQuery, Root и т.д.)
| Проблема с JPQL | Как решает Criteria |
|---|---|
| Строки, ошибки видны только в runtime | Компиляция проверяет типы и имена полей |
| Сложно строить динамические фильтры (if … then add condition) | Легко добавлять условия программно |
| Переименование полей ломает запросы | IDE-рефакторинг автоматически обновит |
| Разные поля и условия по разным параметрам | Можно строить условия в цикле |
Основные участники API
| Класс / интерфейс | Назначение |
|---|---|
CriteriaBuilder |
Точка входа. Создаёт запросы и выражения (equal, like, and, or, sum, и т.д.). |
CriteriaQuery<T> |
Сам запрос. Хранит select, from, where, orderBy, groupBy, и т.д. |
Root<T> |
Корневой объект выборки (FROM Vehicle v). |
Predicate |
Логическое выражение (условие). Возвращается из cb.equal(), cb.greaterThan(), cb.and() и др. |
Path<T> |
Абстракция пути к полю (root.get("admin").get("login")). |
Join<X,Y> |
JOIN-связи между сущностями (root.join("admin")). |
TypedQuery<T> |
Выполняет критерий-запрос (em.createQuery(criteriaQuery)). |
Архитектура запроса Criteria API
EntityManager
│
├─> getCriteriaBuilder()
│
▼
CriteriaBuilder -> CriteriaQuery<Vehicle>
│
├── FROM -> Root<Vehicle>
├── WHERE -> Predicate
├── SELECT -> Projection
└── ORDER BY / GROUP BY
Root<Vehicle> (алияс v)
├── Path: v.engine_power
├── Path: v.creation_time
├── Join admin (алияс a)
│ └── Path: a.id
└── Join coordinates (алияс c)
├── Path: c.x
└── Path: c.y
-
Root
Это корень запроса - то, что стоит в FROM ... в SQL Всегда создаётся через CriteriaQuery.from(EntityClass) это частный случай From (т.е. Root наследуется от From) -
From
Это источник строк в запросе (таблица или присоединённая таблица) Это “толстый” путь (path), который умеет делать JOIN и хранит уже сделанные JOIN-ы -
Path
Это любой путь к атрибуту: столбцу, embedded полю и даже к join’у как к узлу С Path вы строите предикаты: cb.equal(path, ...), cb.lessThan(path, ...) и т.д.