@Immutable в Hibernate
Аннотация @Immutable помечает сущность или коллекцию как неизменяемую, что означает, что Hibernate игнорирует любые попытки обновления или удаления таких объектов. Это полезно для оптимизации производительности и предотвращения случайных изменений.
Пакет: org.hibernate.annotations.
1. Применение и параметры
@Immutable не имеет параметров — это маркерная аннотация.
Где может применяться:
На уровне класса (сущности):
На уровне коллекции:
2. Жизненный цикл и поведение
При загрузке:
Объекты загружаются как обычно, но Hibernate не отслеживает изменения (не добавляет их в PersistenceContext для dirty-checking).
При попытке обновления:
Выбрасывается исключение UnsupportedOperationException (если попытаться изменить поле или вызвать entityManager.merge()).
При удалении:
Игнорируется (если только не используется @SQLDelete с кастомным запросом).
Кэширование:
Сущности с @Immutable идеально подходят для кэширования (например, @Cacheable), так как никогда не меняются.
Механизмы Hibernate и Spring Boot
1. Как Hibernate обрабатывает @Immutable
Оптимизации:
Пропускает dirty-checking для таких сущностей, что ускоряет flush().
Не создает прокси для ленивых загрузок (если @Immutable применен к коллекции).
Исключения:
Session.update(), Session.merge(), Session.delete() игнорируются или выбрасывают исключение.
2. Интеграция с JPA и Spring Boot
JPA-аналоги:
Стандарт JPA не имеет прямой замены @Immutable, но можно эмулировать через:
Примеры использования
1. Справочные данные (Countries, Currencies)
Запросы только на чтение:
2. Логи и аудит (неизменяемые записи)
3. Неизменяемые коллекции
Ограничения и обходные пути
1. Что нельзя сделать с @Immutable
Обновлять поля: Даже через нативный SQL (entityManager.createNativeQuery("UPDATE...")), если только не отключить проверки Hibernate.
Каскадные операции: CascadeType.PERSIST работает, но MERGE, DELETE — нет.
2. Альтернативы
Read-only транзакции (Spring):
DTO и проекции:
#Java #Training #Hard #Spring #Hibernate #Immutable
Аннотация @Immutable помечает сущность или коллекцию как неизменяемую, что означает, что Hibernate игнорирует любые попытки обновления или удаления таких объектов. Это полезно для оптимизации производительности и предотвращения случайных изменений.
Пакет: org.hibernate.annotations.
1. Применение и параметры
@Immutable не имеет параметров — это маркерная аннотация.
Где может применяться:
На уровне класса (сущности):
@Entity
@Immutable
public class LogEntry { ... }
Все экземпляры LogEntry становятся read-only.
На уровне коллекции:
@OneToMany
@Immutable
private List<LogEntry> logs; // Элементы коллекции нельзя изменить
2. Жизненный цикл и поведение
При загрузке:
Объекты загружаются как обычно, но Hibernate не отслеживает изменения (не добавляет их в PersistenceContext для dirty-checking).
При попытке обновления:
Выбрасывается исключение UnsupportedOperationException (если попытаться изменить поле или вызвать entityManager.merge()).
При удалении:
Игнорируется (если только не используется @SQLDelete с кастомным запросом).
Кэширование:
Сущности с @Immutable идеально подходят для кэширования (например, @Cacheable), так как никогда не меняются.
Механизмы Hibernate и Spring Boot
1. Как Hibernate обрабатывает @Immutable
Оптимизации:
Пропускает dirty-checking для таких сущностей, что ускоряет flush().
Не создает прокси для ленивых загрузок (если @Immutable применен к коллекции).
Исключения:
Session.update(), Session.merge(), Session.delete() игнорируются или выбрасывают исключение.
2. Интеграция с JPA и Spring Boot
JPA-аналоги:
Стандарт JPA не имеет прямой замены @Immutable, но можно эмулировать через:
@Entity
@DynamicUpdate(false) // Отключает генерацию UPDATE
@org.hibernate.annotations.OptimisticLocking(type = NONE) // Отключает версионирование
public class LogEntry { ... }
Примеры использования
1. Справочные данные (Countries, Currencies)
@Entity
@Immutable
public class Currency {
@Id
private String code; // USD, EUR
private String name;
// Нет сеттеров
}
Запросы только на чтение:
List<Currency> currencies = entityManager
.createQuery("SELECT c FROM Currency c", Currency.class)
.getResultList();
2. Логи и аудит (неизменяемые записи)
@Entity
@Immutable
public class AuditLog {
@Id @GeneratedValue
private Long id;
private String action;
@Column(updatable = false)
private LocalDateTime createdAt;
}
3. Неизменяемые коллекции
@Entity
public class User {
@Id private Long id;
@OneToMany
@Immutable
private List<LoginHistory> history; // История логинов только для чтения
}
Ограничения и обходные пути
1. Что нельзя сделать с @Immutable
Обновлять поля: Даже через нативный SQL (entityManager.createNativeQuery("UPDATE...")), если только не отключить проверки Hibernate.
Каскадные операции: CascadeType.PERSIST работает, но MERGE, DELETE — нет.
2. Альтернативы
Read-only транзакции (Spring):
@Transactional(readOnly = true) // Оптимизация на уровне JDBC
public List<Currency> getCurrencies() { ... }
DTO и проекции:
public interface CurrencyView {
String getCode();
String getName();
}
Плюс: не требуют Hibernate-сущностей.
#Java #Training #Hard #Spring #Hibernate #Immutable
@Index в Hibernate
Аннотация @Index в Hibernate используется для определения индекса на одном или нескольких столбцах таблицы базы данных. Индексы ускоряют поиск и сортировку данных, но могут замедлять операции вставки и обновления.
Пакет: org.hibernate.annotations
Применяется к: классу (на уровне @Table), полю или свойству.
Параметры и настройки
Аннотация @Index имеет следующие атрибуты:
name (String):
Название индекса в БД.
Если не указано, Hibernate сгенерирует имя автоматически (например, IDX_<TABLE>_<COLUMN>).
columnList (String):
Обязательный параметр.
Список столбцов, входящих в индекс, через запятую (например, "username, email").
Поддерживает порядок сортировки: "username ASC, email DESC".
unique (boolean, по умолчанию false):
Если true, создает уникальный индекс (аналог UNIQUE INDEX в SQL).
options (String):
Дополнительные SQL-опции для индекса (например, "USING HASH" для MySQL).
Жизненный цикл и обработка
Во время компиляции:
Hibernate анализирует аннотацию и включает её в метаданные ORM.
При старте приложения (Hibernate bootstrap):
Индекс добавляется в DDL-скрипт при автоматической генерации схемы (hibernate.hbm2ddl.auto=create/update).
Если БД уже существует, Hibernate не изменяет индексы при update (только создает новые).
Во время выполнения:
Индекс используется СУБД для оптимизации запросов, но Hibernate напрямую его не контролирует.
Механизмы Hibernate и Spring Boot, влияющие на @Index
Генерация DDL (hibernate.hbm2ddl.auto):
create – создает таблицы и индексы при старте.
update – добавляет новые индексы, но не удаляет старые.
validate – проверяет соответствие модели и БД (включая индексы).
none – отключает управление схемой.
Настройки Spring Boot (application.properties):
Альтернативные способы определения индексов:
Через @Table(indexes = @Index(...)):
Через schema.sql (ручное управление индексами).
Кастомизация имен индексов:
Глобальное именование через ImplicitNamingStrategy (редко используется для индексов).
Особенности и ограничения
СУБД-специфичные индексы (например, частичные индексы в PostgreSQL) требуют @Index(options = "WHERE condition") или ручного SQL.
Составные индексы (columnList = "col1, col2") работают только в указанном порядке.
Миграции: Для сложных изменений индексов лучше использовать Flyway/Liquibase.
#Java #Training #Hard #Spring #Hibernate #Index
Аннотация @Index в Hibernate используется для определения индекса на одном или нескольких столбцах таблицы базы данных. Индексы ускоряют поиск и сортировку данных, но могут замедлять операции вставки и обновления.
Пакет: org.hibernate.annotations
Применяется к: классу (на уровне @Table), полю или свойству.
Параметры и настройки
Аннотация @Index имеет следующие атрибуты:
name (String):
Название индекса в БД.
Если не указано, Hibernate сгенерирует имя автоматически (например, IDX_<TABLE>_<COLUMN>).
columnList (String):
Обязательный параметр.
Список столбцов, входящих в индекс, через запятую (например, "username, email").
Поддерживает порядок сортировки: "username ASC, email DESC".
unique (boolean, по умолчанию false):
Если true, создает уникальный индекс (аналог UNIQUE INDEX в SQL).
options (String):
Дополнительные SQL-опции для индекса (например, "USING HASH" для MySQL).
Жизненный цикл и обработка
Во время компиляции:
Hibernate анализирует аннотацию и включает её в метаданные ORM.
При старте приложения (Hibernate bootstrap):
Индекс добавляется в DDL-скрипт при автоматической генерации схемы (hibernate.hbm2ddl.auto=create/update).
Если БД уже существует, Hibernate не изменяет индексы при update (только создает новые).
Во время выполнения:
Индекс используется СУБД для оптимизации запросов, но Hibernate напрямую его не контролирует.
Механизмы Hibernate и Spring Boot, влияющие на @Index
Генерация DDL (hibernate.hbm2ddl.auto):
create – создает таблицы и индексы при старте.
update – добавляет новые индексы, но не удаляет старые.
validate – проверяет соответствие модели и БД (включая индексы).
none – отключает управление схемой.
Настройки Spring Boot (application.properties):
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.default_schema=public
spring.jpa.show-sql=true
Альтернативные способы определения индексов:
Через @Table(indexes = @Index(...)):
@Entity
@Table(indexes = @Index(name = "idx_user_email", columnList = "email", unique = true))
public class User { ... }
Через schema.sql (ручное управление индексами).
Кастомизация имен индексов:
Глобальное именование через ImplicitNamingStrategy (редко используется для индексов).
Особенности и ограничения
СУБД-специфичные индексы (например, частичные индексы в PostgreSQL) требуют @Index(options = "WHERE condition") или ручного SQL.
Составные индексы (columnList = "col1, col2") работают только в указанном порядке.
Миграции: Для сложных изменений индексов лучше использовать Flyway/Liquibase.
#Java #Training #Hard #Spring #Hibernate #Index
@LazyCollection в Hibernate
Аннотация @LazyCollection управляет стратегией ленивой загрузки для коллекций (например, @OneToMany, @ManyToMany) в Hibernate. Определяет, когда и как должны загружаться связанные сущности.
Пакет: org.hibernate.annotations
Применяется к: полям-коллекциям (List, Set, Map и др.) с аннотациями @OneToMany или @ManyToMany.
Параметры и настройки
Аннотация принимает один обязательный параметр — value, который может быть одним из следующих вариантов:
LazyCollectionOption.TRUE (по умолчанию для @OneToMany и @ManyToMany):
Коллекция загружается лениво (при первом обращении).
Используется прокси-объект (PersistentBag, PersistentSet).
LazyCollectionOption.EXTRA:
"Сверхленивая" загрузка: Hibernate загружает только необходимые данные при вызове методов коллекции (например, size(), contains()).
Полезно для больших коллекций, чтобы избежать полной загрузки.
LazyCollectionOption.FALSE:
Коллекция загружается жадно (eager loading), аналогично fetch = FetchType.EAGER.
Не рекомендуется для больших коллекций из-за риска N+1 проблемы.
Жизненный цикл и обработка
Инициализация прокси:
При загрузке родительской сущности коллекция заменяется прокси (реализацией Hibernate).
Прокси перехватывает вызовы методов (например, getItems()) и загружает данные.
Загрузка данных:
Для TRUE: при первом обращении к коллекции выполняется SQL-запрос.
Для EXTRA: методы вроде size() генерируют SELECT COUNT(*), а не загружают все элементы.
Сессия и LazyInitializationException:
Если коллекция запрашивается после закрытия сессии, возникнет исключение (если не используется OpenSessionInView).
Механизмы Hibernate и Spring Boot
Глобальная настройка ленивой загрузки:
Параметр spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true (не рекомендуется, приводит к N+1).
spring.jpa.open-in-view=true (держит сессию открытой до конца HTTP-запроса).
Интеграция с FetchType:
@LazyCollection переопределяет поведение fetch в @OneToMany:
Оптимизация запросов:
Для EXTRA Hibernate использует:
SELECT COUNT(*) FROM orders WHERE user_id = ? — для collection.size().
SELECT id FROM orders WHERE user_id = ? LIMIT 1 — для collection.isEmpty().
Примеры использования
Ленивая загрузка с проверкой размера без загрузки элементов:
Отключение ленивой загрузки (редкий случай):
Ограничения и лучшие практики
N+1 проблема: Даже с LazyCollectionOption.TRUE при обходе коллекции (for (Order o : user.getOrders())) выполняется один запрос для загрузки всех элементов. Решение: @EntityGraph или JOIN FETCH.
EXTRA не поддерживает List.get(index): Для этого требуется полная загрузка.
Тестирование: В тестах с закрытой сессией используйте Hibernate.initialize(collection).
#Java #Training #Hard #Spring #Hibernate #LazyCollection
Аннотация @LazyCollection управляет стратегией ленивой загрузки для коллекций (например, @OneToMany, @ManyToMany) в Hibernate. Определяет, когда и как должны загружаться связанные сущности.
Пакет: org.hibernate.annotations
Применяется к: полям-коллекциям (List, Set, Map и др.) с аннотациями @OneToMany или @ManyToMany.
Параметры и настройки
Аннотация принимает один обязательный параметр — value, который может быть одним из следующих вариантов:
LazyCollectionOption.TRUE (по умолчанию для @OneToMany и @ManyToMany):
Коллекция загружается лениво (при первом обращении).
Используется прокси-объект (PersistentBag, PersistentSet).
LazyCollectionOption.EXTRA:
"Сверхленивая" загрузка: Hibernate загружает только необходимые данные при вызове методов коллекции (например, size(), contains()).
Полезно для больших коллекций, чтобы избежать полной загрузки.
LazyCollectionOption.FALSE:
Коллекция загружается жадно (eager loading), аналогично fetch = FetchType.EAGER.
Не рекомендуется для больших коллекций из-за риска N+1 проблемы.
Жизненный цикл и обработка
Инициализация прокси:
При загрузке родительской сущности коллекция заменяется прокси (реализацией Hibernate).
Прокси перехватывает вызовы методов (например, getItems()) и загружает данные.
Загрузка данных:
Для TRUE: при первом обращении к коллекции выполняется SQL-запрос.
Для EXTRA: методы вроде size() генерируют SELECT COUNT(*), а не загружают все элементы.
Сессия и LazyInitializationException:
Если коллекция запрашивается после закрытия сессии, возникнет исключение (если не используется OpenSessionInView).
Механизмы Hibernate и Spring Boot
Глобальная настройка ленивой загрузки:
Параметр spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true (не рекомендуется, приводит к N+1).
spring.jpa.open-in-view=true (держит сессию открытой до конца HTTP-запроса).
Интеграция с FetchType:
@LazyCollection переопределяет поведение fetch в @OneToMany:
@OneToMany(fetch = FetchType.EAGER) // Игнорируется, если есть @LazyCollection
@LazyCollection(LazyCollectionOption.TRUE)
private List<Order> orders;
Оптимизация запросов:
Для EXTRA Hibernate использует:
SELECT COUNT(*) FROM orders WHERE user_id = ? — для collection.size().
SELECT id FROM orders WHERE user_id = ? LIMIT 1 — для collection.isEmpty().
Примеры использования
Ленивая загрузка с проверкой размера без загрузки элементов:
@OneToMany
@LazyCollection(LazyCollectionOption.EXTRA)
private Set<Comment> comments; // Для comments.size() не загружает все комментарии
Отключение ленивой загрузки (редкий случай):
@OneToMany
@LazyCollection(LazyCollectionOption.FALSE) // Аналог FetchType.EAGER
private List<Post> posts;
Ограничения и лучшие практики
N+1 проблема: Даже с LazyCollectionOption.TRUE при обходе коллекции (for (Order o : user.getOrders())) выполняется один запрос для загрузки всех элементов. Решение: @EntityGraph или JOIN FETCH.
EXTRA не поддерживает List.get(index): Для этого требуется полная загрузка.
Тестирование: В тестах с закрытой сессией используйте Hibernate.initialize(collection).
#Java #Training #Hard #Spring #Hibernate #LazyCollection
@LazyToOne в Hibernate
Аннотация @LazyToOne управляет стратегией ленивой загрузки для ассоциаций @ManyToOne и @OneToOne в Hibernate. В отличие от стандартного FetchType.LAZY, она позволяет более гибко настраивать механизм проксирования и инициализации связанных сущностей.
Пакет: org.hibernate.annotations
Применяется к: полям или методам с аннотациями @ManyToOne или @OneToOne.
1. Параметры и настройки
Аннотация @LazyToOne принимает один обязательный параметр — value, который может быть одним из следующих вариантов:
Доступные варианты загрузки:
LazyToOneOption.PROXY (по умолчанию, если FetchType.LAZY):
Создается прокси-объект, который загружает данные только при первом обращении.
Если связанная сущность уже удалена (и @ManyToOne nullable), может возникнуть LazyInitializationException.
LazyToOneOption.NO_PROXY:
Использует байткодовую модификацию (требует инструментирования, например, через javassist).
Позволяет избежать некоторых проблем с прокси, но сложнее в настройке.
LazyToOneOption.FALSE:
Эквивалентно FetchType.EAGER — загружает сущность сразу.
2. Жизненный цикл и обработка
При загрузке сущности:
Если PROXY → Hibernate подставляет прокси-объект, не загружая данные из БД.
Если NO_PROXY → генерируется специальный подкласс (требует hibernate.bytecode.provider=javassist).
При первом обращении:
Прокси инициализируется и выполняет SQL-запрос.
При закрытии сессии:
Если сессия закрыта до обращения к полю → LazyInitializationException.
Отличие от стандартного FetchType.LAZY
Обычный LAZY в @ManyToOne/@OneToOne не всегда работает (особенно если optional=false).
@LazyToOne дает больше контроля над механизмом ленивой загрузки.
3. Механизмы Hibernate и Spring Boot
Настройки, влияющие на @LazyToOne
hibernate.bytecode.provider (для NO_PROXY):
Открытие сессии в View (OpenSessionInView)
``
spring.jpa.open-in-view=true # Держит сессию открытой до конца HTTP-запроса
Позволяет избежать LazyInitializationException в веб-приложениях.
```
Глобальная настройка ленивой загрузки
4. Примеры использования
Пример 1: Ленивая загрузка с прокси (PROXY)
Пример 2: Ленивая загрузка без прокси (NO_PROXY)
#Java #Training #Hard #Spring #Hibernate #LazyToOne
Аннотация @LazyToOne управляет стратегией ленивой загрузки для ассоциаций @ManyToOne и @OneToOne в Hibernate. В отличие от стандартного FetchType.LAZY, она позволяет более гибко настраивать механизм проксирования и инициализации связанных сущностей.
Пакет: org.hibernate.annotations
Применяется к: полям или методам с аннотациями @ManyToOne или @OneToOne.
1. Параметры и настройки
Аннотация @LazyToOne принимает один обязательный параметр — value, который может быть одним из следующих вариантов:
Доступные варианты загрузки:
LazyToOneOption.PROXY (по умолчанию, если FetchType.LAZY):
Создается прокси-объект, который загружает данные только при первом обращении.
Если связанная сущность уже удалена (и @ManyToOne nullable), может возникнуть LazyInitializationException.
LazyToOneOption.NO_PROXY:
Использует байткодовую модификацию (требует инструментирования, например, через javassist).
Позволяет избежать некоторых проблем с прокси, но сложнее в настройке.
LazyToOneOption.FALSE:
Эквивалентно FetchType.EAGER — загружает сущность сразу.
2. Жизненный цикл и обработка
При загрузке сущности:
Если PROXY → Hibernate подставляет прокси-объект, не загружая данные из БД.
Если NO_PROXY → генерируется специальный подкласс (требует hibernate.bytecode.provider=javassist).
При первом обращении:
Прокси инициализируется и выполняет SQL-запрос.
При закрытии сессии:
Если сессия закрыта до обращения к полю → LazyInitializationException.
Отличие от стандартного FetchType.LAZY
Обычный LAZY в @ManyToOne/@OneToOne не всегда работает (особенно если optional=false).
@LazyToOne дает больше контроля над механизмом ленивой загрузки.
3. Механизмы Hibernate и Spring Boot
Настройки, влияющие на @LazyToOne
hibernate.bytecode.provider (для NO_PROXY):
spring.jpa.properties.hibernate.bytecode.provider=javassist
Без этого NO_PROXY может не работать.
Открытие сессии в View (OpenSessionInView)
``
spring.jpa.open-in-view=true # Держит сессию открытой до конца HTTP-запроса
Позволяет избежать LazyInitializationException в веб-приложениях.
```
Глобальная настройка ленивой загрузки
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true # Не рекомендуется!
(Может привести к N+1 и утечкам памяти.)
4. Примеры использования
Пример 1: Ленивая загрузка с прокси (PROXY)
@Entity
public class Order {
@ManyToOne(fetch = FetchType.LAZY) // Стандартный LAZY
@LazyToOne(LazyToOneOption.PROXY) // Явное указание прокси
private User user;
}
Пример 2: Ленивая загрузка без прокси (NO_PROXY)
@Entity
public class Employee {
@OneToOne(fetch = FetchType.LAZY)
@LazyToOne(LazyToOneOption.NO_PROXY) // Требует инструментирования
private ParkingSpot parkingSpot;
}
#Java #Training #Hard #Spring #Hibernate #LazyToOne
@Loader в Hibernate
Аннотация @Loader позволяет переопределить стандартный SQL-запрос, который Hibernate использует для загрузки сущности из базы данных.
Это полезно для сложных сценариев, когда требуется:
Загружать данные с помощью кастомного SQL (вместо SELECT * FROM table).
Оптимизировать запросы (например, использовать JOIN или подзапросы).
Загружать сущности через хранимые процедуры.
Пакет: org.hibernate.annotations
Применяется к: классу сущности (@Entity).
Параметры и настройки
namedQuery: Именованный запрос (определённый через @NamedQuery).
query: Собственный SQL-запрос (нативный или HQL).
Примеры:
Использование именованного запроса (namedQuery)
Прямое указание SQL (query)
Жизненный цикл и обработка
Когда выполняется кастомный запрос?
При загрузке сущности по ID:
session.get(User.class, 1) → выполняется запрос из @Loader.
При ленивой загрузке прокси:
Если сущность загружается через прокси (например, @ManyToOne с FetchType.LAZY), @Loader не применяется.
Ограничения
Запрос должен возвращать ровно одну сущность.
Параметры:
Для namedQuery — порядковый номер параметра (?1).
Для query — можно использовать ? или именованные параметры (:id).
Механизмы Hibernate и Spring Boot
Интеграция с @NamedQuery:
Если @Loader ссылается на namedQuery, запрос должен быть определён в той же сущности.
Нативные запросы vs HQL:
Если query начинается с SELECT *, Hibernate обрабатывает его как нативный.
Для HQL используйте синтаксис "FROM EntityName WHERE ...".
Влияние кеширования:
@Loader не отключает кеш второго уровня. Если сущность есть в кеше, запрос не выполнится.
Примеры использования
Пример 1: Оптимизированная загрузка с JOIN
Пример 2: Загрузка через хранимую процедуру
Ограничения и лучшие практики
Когда использовать @Loader?
Сложные условия загрузки (например, фильтрация по статусу).
Оптимизация запросов (избегание N+1 через JOIN).
Интеграция с legacy-кодом (хранимые процедуры).
#Java #Training #Hard #Spring #Hibernate #Loader
Аннотация @Loader позволяет переопределить стандартный SQL-запрос, который Hibernate использует для загрузки сущности из базы данных.
Это полезно для сложных сценариев, когда требуется:
Загружать данные с помощью кастомного SQL (вместо SELECT * FROM table).
Оптимизировать запросы (например, использовать JOIN или подзапросы).
Загружать сущности через хранимые процедуры.
Пакет: org.hibernate.annotations
Применяется к: классу сущности (@Entity).
Параметры и настройки
namedQuery: Именованный запрос (определённый через @NamedQuery).
query: Собственный SQL-запрос (нативный или HQL).
Примеры:
Использование именованного запроса (namedQuery)
@Entity
@Loader(namedQuery = "findUserById")
@NamedQuery(name = "findUserById", query = "SELECT u FROM User u WHERE u.id = ?1")
public class User { ... }
Прямое указание SQL (query)
@Entity
@Loader(query = "SELECT * FROM users WHERE user_id = ?1 AND is_active = true")
public class User { ... }
Жизненный цикл и обработка
Когда выполняется кастомный запрос?
При загрузке сущности по ID:
session.get(User.class, 1) → выполняется запрос из @Loader.
При ленивой загрузке прокси:
Если сущность загружается через прокси (например, @ManyToOne с FetchType.LAZY), @Loader не применяется.
Ограничения
Запрос должен возвращать ровно одну сущность.
Параметры:
Для namedQuery — порядковый номер параметра (?1).
Для query — можно использовать ? или именованные параметры (:id).
Механизмы Hibernate и Spring Boot
Интеграция с @NamedQuery:
Если @Loader ссылается на namedQuery, запрос должен быть определён в той же сущности.
Нативные запросы vs HQL:
Если query начинается с SELECT *, Hibernate обрабатывает его как нативный.
Для HQL используйте синтаксис "FROM EntityName WHERE ...".
Влияние кеширования:
@Loader не отключает кеш второго уровня. Если сущность есть в кеше, запрос не выполнится.
Примеры использования
Пример 1: Оптимизированная загрузка с JOIN
@Entity
@Loader(query = """
SELECT u.* FROM users u
LEFT JOIN user_settings s ON u.id = s.user_id
WHERE u.id = ?1
""")
public class User { ... }
Пример 2: Загрузка через хранимую процедуру
@Entity
@Loader(query = "CALL load_user_by_id(:id)")
public class User { ... }
Ограничения и лучшие практики
Когда использовать @Loader?
Сложные условия загрузки (например, фильтрация по статусу).
Оптимизация запросов (избегание N+1 через JOIN).
Интеграция с legacy-кодом (хранимые процедуры).
#Java #Training #Hard #Spring #Hibernate #Loader
@ManyToAny в Hibernate
Аннотация @ManyToAny позволяет реализовать полиморфную ассоциацию, где сущность может ссылаться на разные типы других сущностей через единое поле. Это аналог @ManyToMany, но без жесткой привязки к конкретному классу.
Пакет: org.hibernate.annotations
Применяется к: полям типа List, Set или Map.
Требует:
@AnyMetaDef (определяет маппинг типов).
@JoinTable или @CollectionTable (если ассоциация хранится в отдельной таблице).
Параметры и настройки
Обязательные аннотации
@AnyMetaDef
Определяет, как Hibernate будет различать типы сущностей.
Атрибуты:
name (String): уникальное имя (используется в @ManyToAny).
metaType (String): тип дискриминатора (обычно "string" или "integer").
idType (String): тип ID (например, "long").
metaValues (массив @MetaValue): связывает значения с классами.
@ManyToAny
Атрибуты:
metaDef (String): имя @AnyMetaDef.
fetch (FetchType): LAZY (по умолчанию) или EAGER.
Жизненный цикл и обработка
Как хранится связь?
Для @ManyToAny требуется отдельная таблица (аналог @ManyToMany), которая содержит:
ID владельца (сущности, где объявлено @ManyToAny).
ID целевой сущности.
Дискриминатор (тип сущности из @MetaValue).
Загрузка данных
При обращении к полю Hibernate:
Смотрит item_type и item_id.
Определяет класс через @AnyMetaDef.
Загружает сущность (Book или Movie).
Примеры использования
Пример 1: Полиморфная коллекция
Пример 2: Ссылка на одну сущность
Проблемы
Нет типобезопасности: коллекция содержит Object, требуется проверка instanceof.
Сложные запросы: JPA/HQL не поддерживают полиморфные ассоциации в WHERE.
Нет каскадирования: нельзя использовать CascadeType.ALL.
#Java #Training #Hard #Spring #Hibernate #ManyToAny
Аннотация @ManyToAny позволяет реализовать полиморфную ассоциацию, где сущность может ссылаться на разные типы других сущностей через единое поле. Это аналог @ManyToMany, но без жесткой привязки к конкретному классу.
Пакет: org.hibernate.annotations
Применяется к: полям типа List, Set или Map.
Требует:
@AnyMetaDef (определяет маппинг типов).
@JoinTable или @CollectionTable (если ассоциация хранится в отдельной таблице).
Параметры и настройки
Обязательные аннотации
@AnyMetaDef
Определяет, как Hibernate будет различать типы сущностей.
Атрибуты:
name (String): уникальное имя (используется в @ManyToAny).
metaType (String): тип дискриминатора (обычно "string" или "integer").
idType (String): тип ID (например, "long").
metaValues (массив @MetaValue): связывает значения с классами.
@AnyMetaDef(
name = "myMetaDef",
metaType = "string",
idType = "long",
metaValues = {
@MetaValue(targetEntity = Book.class, value = "BOOK"),
@MetaValue(targetEntity = Movie.class, value = "MOVIE")
}
)
@ManyToAny
Атрибуты:
metaDef (String): имя @AnyMetaDef.
fetch (FetchType): LAZY (по умолчанию) или EAGER.
Жизненный цикл и обработка
Как хранится связь?
Для @ManyToAny требуется отдельная таблица (аналог @ManyToMany), которая содержит:
ID владельца (сущности, где объявлено @ManyToAny).
ID целевой сущности.
Дискриминатор (тип сущности из @MetaValue).
Загрузка данных
При обращении к полю Hibernate:
Смотрит item_type и item_id.
Определяет класс через @AnyMetaDef.
Загружает сущность (Book или Movie).
Примеры использования
Пример 1: Полиморфная коллекция
@Entity
public class User {
@Id
private Long id;
@ManyToAny
@AnyMetaDef(
name = "itemMetaDef",
metaType = "string",
idType = "long",
metaValues = {
@MetaValue(targetEntity = Book.class, value = "BOOK"),
@MetaValue(targetEntity = Movie.class, value = "MOVIE")
}
)
@JoinTable(
name = "user_items",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "item_id")
)
@Column(name = "item_type") // Дискриминатор
private List<Object> items; // Может содержать Book и Movie
}
Пример 2: Ссылка на одну сущность
@Entity
public class Review {
@Id
private Long id;
@Any(metaDef = "contentMetaDef", metaColumn = @Column(name = "content_type"))
@JoinColumn(name = "content_id")
private Object content; // Может быть Book или Movie
}
Проблемы
Нет типобезопасности: коллекция содержит Object, требуется проверка instanceof.
Сложные запросы: JPA/HQL не поддерживают полиморфные ассоциации в WHERE.
Нет каскадирования: нельзя использовать CascadeType.ALL.
#Java #Training #Hard #Spring #Hibernate #ManyToAny
@MetaValue в Hibernate
Аннотация @MetaValue используется в связке с @AnyMetaDef, @ManyToAny или @Any для определения соответствия между дискриминатором (меткой типа) и конкретным классом сущности. Она позволяет Hibernate понять, какую сущность загружать при полиморфных ассоциациях.
Пакет: org.hibernate.annotations
Применяется внутри: @AnyMetaDef.metaValues или @TypeDef.metaValues.
Параметры и настройки
Атрибуты @MetaValue
targetEntity: Класс сущности, на который ссылается метка.
value: Значение дискриминатора в БД (например, "BOOK" или 1).
Пример использования
Жизненный цикл и обработка
Как работает @MetaValue?
При сохранении:
Hibernate смотрит тип объекта в коллекции (например, Book).
Записывает в столбец-дискриминатор (item_type) значение "BOOK".
При загрузке:
Hibernate читает item_type из БД (например, "MOVIE").
Сопоставляет значение с классом Movie через @MetaValue.
Загружает сущность Movie по item_id.
Где хранится дискриминатор?
В отдельном столбце таблицы (например, item_type).
Или в общей таблице, если используется @Any + @Column.
Примеры использования
Пример 1: Полиморфная коллекция (@ManyToAny)
Пример 2: Одиночная полиморфная ссылка (@Any)
Ограничения
Нет типобезопасности:
Коллекция List<Object> требует проверки instanceof.
Решение: использовать Visitor-паттерн или DTO.
Ограниченная поддержка в JPA:
Запросы с WHERE content_type = 'BOOK' не работают в чистом JPA.
Решение: нативные SQL-запросы или Hibernate.initialize().
Нет каскадирования:
CascadeType.ALL не поддерживается.
#Java #Training #Hard #Spring #Hibernate #MetaValue
Аннотация @MetaValue используется в связке с @AnyMetaDef, @ManyToAny или @Any для определения соответствия между дискриминатором (меткой типа) и конкретным классом сущности. Она позволяет Hibernate понять, какую сущность загружать при полиморфных ассоциациях.
Пакет: org.hibernate.annotations
Применяется внутри: @AnyMetaDef.metaValues или @TypeDef.metaValues.
Параметры и настройки
Атрибуты @MetaValue
targetEntity: Класс сущности, на который ссылается метка.
value: Значение дискриминатора в БД (например, "BOOK" или 1).
Пример использования
@AnyMetaDef(
name = "mediaMetaDef",
metaType = "string", // Тип столбца-дискриминатора (varchar)
idType = "long", // Тип ID целевой сущности
metaValues = {
@MetaValue(targetEntity = Book.class, value = "BOOK"),
@MetaValue(targetEntity = Movie.class, value = "MOVIE")
}
)
Жизненный цикл и обработка
Как работает @MetaValue?
При сохранении:
Hibernate смотрит тип объекта в коллекции (например, Book).
Записывает в столбец-дискриминатор (item_type) значение "BOOK".
При загрузке:
Hibernate читает item_type из БД (например, "MOVIE").
Сопоставляет значение с классом Movie через @MetaValue.
Загружает сущность Movie по item_id.
Где хранится дискриминатор?
В отдельном столбце таблицы (например, item_type).
Или в общей таблице, если используется @Any + @Column.
Примеры использования
Пример 1: Полиморфная коллекция (@ManyToAny)
@Entity
public class Library {
@Id
private Long id;
@ManyToAny
@AnyMetaDef(
name = "mediaMetaDef",
metaType = "string",
idType = "long",
metaValues = {
@MetaValue(targetEntity = Book.class, value = "BOOK"),
@MetaValue(targetEntity = Movie.class, value = "MOVIE")
}
)
@JoinTable(
name = "library_items",
joinColumns = @JoinColumn(name = "library_id"),
inverseJoinColumns = @JoinColumn(name = "item_id")
)
@Column(name = "item_type") // Столбец для дискриминатора
private List<Object> items; // Может содержать Book и Movie
}
Пример 2: Одиночная полиморфная ссылка (@Any)
@Entity
public class Review {
@Id
private Long id;
@Any(metaDef = "mediaMetaDef")
@JoinColumn(name = "content_id")
@Column(name = "content_type") // Дискриминатор
private Object content; // Book или Movie
}
Ограничения
Нет типобезопасности:
Коллекция List<Object> требует проверки instanceof.
Решение: использовать Visitor-паттерн или DTO.
Ограниченная поддержка в JPA:
Запросы с WHERE content_type = 'BOOK' не работают в чистом JPA.
Решение: нативные SQL-запросы или Hibernate.initialize().
Нет каскадирования:
CascadeType.ALL не поддерживается.
#Java #Training #Hard #Spring #Hibernate #MetaValue
@NaturalId в Hibernate
Аннотация @NaturalId помечает естественный (бизнес-идентификатор) поля сущности, который не является техническим первичным ключом (@Id), но обладает уникальностью и часто используется для поиска.
Пакет: org.hibernate.annotations
Применяется к: полям или свойствам сущности (String, Integer, UUID и др.).
Особенности:
Ускоряет поиск по "естественному" ключу (Hibernate кеширует NaturalId → ID).
Может быть изменяемым (mutable) или неизменяемым (immutable).
Параметры и настройки
mutable: Если true — NaturalId можно изменить (по умолчанию false).
Примеры
Жизненный цикл и обработка
Как работает @NaturalId?
При сохранении:
Hibernate проверяет уникальность NaturalId (если нет @UniqueConstraint).
Добавляет запись в кеш NaturalId → ID.
При поиске:
session.bySimpleNaturalId(User.class).load("test@mail.com") — использует кеш.
Генерирует SQL: SELECT id FROM users WHERE email = ?.
При изменении (если mutable=true):
Обновляет кеш (старое значение удаляется, новое добавляется).
Кеширование
Уровень L1 (сессия): Кешируется в рамках текущей сессии.
Уровень L2 (глобальный): Требует включенного кеша второго уровня.
Примеры использования
Пример 1: Неизменяемый NaturalId
Пример 2: Поиск по NaturalId
Ограничения
Производительность при mutable=true:
Частые изменения NaturalId требуют обновления кеша.
Отсутствие JPA-стандарта:
@NaturalId — это фича Hibernate (не работает в EclipseLink).
Когда использовать?
Для часто используемых бизнес-идентификаторов (email, ISBN, серийные номера).
Вместо @Id, если первичный ключ — суррогатный (автоинкремент).
#Java #Training #Hard #Spring #Hibernate #NaturalId
Аннотация @NaturalId помечает естественный (бизнес-идентификатор) поля сущности, который не является техническим первичным ключом (@Id), но обладает уникальностью и часто используется для поиска.
Пакет: org.hibernate.annotations
Применяется к: полям или свойствам сущности (String, Integer, UUID и др.).
Особенности:
Ускоряет поиск по "естественному" ключу (Hibernate кеширует NaturalId → ID).
Может быть изменяемым (mutable) или неизменяемым (immutable).
Параметры и настройки
mutable: Если true — NaturalId можно изменить (по умолчанию false).
Примеры
@Entity
public class User {
@Id
private Long id;
@NaturalId(mutable = false) // Неизменяемый (оптимизирует кеширование)
private String email;
@NaturalId(mutable = true) // Изменяемый (редкий случай)
private String passportNumber;
}
Жизненный цикл и обработка
Как работает @NaturalId?
При сохранении:
Hibernate проверяет уникальность NaturalId (если нет @UniqueConstraint).
Добавляет запись в кеш NaturalId → ID.
При поиске:
session.bySimpleNaturalId(User.class).load("test@mail.com") — использует кеш.
Генерирует SQL: SELECT id FROM users WHERE email = ?.
При изменении (если mutable=true):
Обновляет кеш (старое значение удаляется, новое добавляется).
Кеширование
Уровень L1 (сессия): Кешируется в рамках текущей сессии.
Уровень L2 (глобальный): Требует включенного кеша второго уровня.
Примеры использования
Пример 1: Неизменяемый NaturalId
@Entity
@Table(uniqueConstraints = @UniqueConstraint(columnNames = "isbn"))
public class Book {
@Id
private Long id;
@NaturalId // immutable=false по умолчанию
private String isbn;
}
Пример 2: Поиск по NaturalId
Book book = session.bySimpleNaturalId(Book.class)
.load("978-3-16-148410-0");
Ограничения
Производительность при mutable=true:
Частые изменения NaturalId требуют обновления кеша.
Отсутствие JPA-стандарта:
@NaturalId — это фича Hibernate (не работает в EclipseLink).
Когда использовать?
Для часто используемых бизнес-идентификаторов (email, ISBN, серийные номера).
Вместо @Id, если первичный ключ — суррогатный (автоинкремент).
#Java #Training #Hard #Spring #Hibernate #NaturalId
@NotFound в Hibernate
Аннотация @NotFound управляет поведением Hibernate при отсутствии ссылки на связанную сущность в базе данных (например, если @ManyToOne ссылается на несуществующий ID).
Пакет: org.hibernate.annotations
Применяется к: ассоциациям (@OneToOne, @ManyToOne, @OneToMany, @ManyToMany).
Поведение по умолчанию: Hibernate выбрасывает EntityNotFoundException.
Параметры и настройки
action: Действие при отсутствии сущности: EXCEPTION (по умолчанию) или IGNORE.
Примеры
Жизненный цикл и обработка
Как работает @NotFound?
При загрузке сущности:
Если связанная сущность (например, User) не найдена:
action = EXCEPTION → EntityNotFoundException.
action = IGNORE → поле устанавливается в null.
При каскадных операциях:
Не влияет на CascadeType (удаление, обновление).
Сценарии
Ссылка на удаленную сущность:
Ленивая загрузка:
Если прокси не может загрузить сущность, применяется action.
Интеграция с Spring Boot
Валидация при старте:
Чтобы выявить "битые" ссылки заранее:
Логирование:
Примеры использования
Пример 1: Игнорирование отсутствующей сущности
Пример 2: Обработка в сервисе
Проблемы
Неявное поведение:
IGNORE может скрывать ошибки данных.
Нет поддержки в JPA:
Это фича Hibernate (не работает в EclipseLink).
Когда использовать?
Для опциональных ассоциаций, где null — допустимое состояние.
В legacy-системах с "битыми" ссылками.
#Java #Training #Hard #Spring #Hibernate #NotFound
Аннотация @NotFound управляет поведением Hibernate при отсутствии ссылки на связанную сущность в базе данных (например, если @ManyToOne ссылается на несуществующий ID).
Пакет: org.hibernate.annotations
Применяется к: ассоциациям (@OneToOne, @ManyToOne, @OneToMany, @ManyToMany).
Поведение по умолчанию: Hibernate выбрасывает EntityNotFoundException.
Параметры и настройки
action: Действие при отсутствии сущности: EXCEPTION (по умолчанию) или IGNORE.
Примеры
@Entity
public class Order {
@ManyToOne
@NotFound(action = NotFoundAction.IGNORE) // Игнорировать отсутствие пользователя
private User user;
}
Жизненный цикл и обработка
Как работает @NotFound?
При загрузке сущности:
Если связанная сущность (например, User) не найдена:
action = EXCEPTION → EntityNotFoundException.
action = IGNORE → поле устанавливается в null.
При каскадных операциях:
Не влияет на CascadeType (удаление, обновление).
Сценарии
Ссылка на удаленную сущность:
-- order.user_id = 999, но users.id = 999 не существует
@NotFound(action = IGNORE) → order.user = null.
Ленивая загрузка:
Если прокси не может загрузить сущность, применяется action.
Интеграция с Spring Boot
Валидация при старте:
Чтобы выявить "битые" ссылки заранее:
@EventListener(ApplicationReadyEvent.class)
public void checkBrokenReferences() {
// Ручная проверка...
}
Логирование:
logging.level.org.hibernate=DEBUG
Примеры использования
Пример 1: Игнорирование отсутствующей сущности
@Entity
public class Comment {
@ManyToOne
@NotFound(action = NotFoundAction.IGNORE)
private Post post; // Если post удалён, comment.post = null
}
Пример 2: Обработка в сервисе
public CommentDTO getComment(Long id) {
Comment comment = commentRepository.findById(id).orElseThrow();
if (comment.getPost() == null) {
log.warn("Пост для комментария {} не найден", id);
}
return toDto(comment);
}
Проблемы
Неявное поведение:
IGNORE может скрывать ошибки данных.
Нет поддержки в JPA:
Это фича Hibernate (не работает в EclipseLink).
Когда использовать?
Для опциональных ассоциаций, где null — допустимое состояние.
В legacy-системах с "битыми" ссылками.
#Java #Training #Hard #Spring #Hibernate #NotFound
@OnDelete в Hibernate
Аннотация @OnDelete определяет действие при удалении связанной сущности на уровне базы данных (через ON DELETE CASCADE или ON DELETE SET NULL). Это DDL-инструкция, а не логика Hibernate.
Пакет: org.hibernate.annotations
Применяется к: ассоциациям (@OneToOne, @ManyToOne, @OneToMany).
Влияние: Генерирует соответствующий SQL-констрейнт в БД.
Параметры и настройки
action: Варианты: CASCADE (удалить зависимые записи) или SET_NULL (обнулить ссылку).
Примеры
Жизненный цикл и обработка
Как работает @OnDelete?
При генерации DDL:
Hibernate добавляет в SQL-скрипт ON DELETE CASCADE/SET NULL для внешнего ключа.
При удалении через БД:
Если запись удаляется напрямую SQL (минуя Hibernate), констрейнт срабатывает автоматически.
При удалении через Hibernate:
Если action = CASCADE, Hibernate сначала удаляет дочерние сущности (если нет orphanRemoval).
Ограничения
Не влияет на логику EntityManager.remove().
Не работает с @ManyToMany (только через @JoinTable + отдельные констрейнты).
Примеры использования
Пример 1: Каскадное удаление (CASCADE)
Пример 2: Обнуление ссылки (SET_NULL)
Проблемы
Неявное удаление: При CASCADE данные могут исчезнуть без явного вызова delete().
Несовместимость: Некоторые БД (например, MySQL с InnoDB) требуют индексов для ON DELETE CASCADE.
Когда использовать?
Для жестких зависимостей (например, Order → User).
В legacy-системах, где удаление управляется триггерами БД.
#Java #Training #Hard #Spring #Hibernate #OnDelete
Аннотация @OnDelete определяет действие при удалении связанной сущности на уровне базы данных (через ON DELETE CASCADE или ON DELETE SET NULL). Это DDL-инструкция, а не логика Hibernate.
Пакет: org.hibernate.annotations
Применяется к: ассоциациям (@OneToOne, @ManyToOne, @OneToMany).
Влияние: Генерирует соответствующий SQL-констрейнт в БД.
Параметры и настройки
action: Варианты: CASCADE (удалить зависимые записи) или SET_NULL (обнулить ссылку).
Примеры
@Entity
public class Order {
@ManyToOne
@OnDelete(action = OnDeleteAction.CASCADE) // При удалении User удалятся его Order
private User user;
}
Жизненный цикл и обработка
Как работает @OnDelete?
При генерации DDL:
Hibernate добавляет в SQL-скрипт ON DELETE CASCADE/SET NULL для внешнего ключа.
При удалении через БД:
Если запись удаляется напрямую SQL (минуя Hibernate), констрейнт срабатывает автоматически.
При удалении через Hibernate:
Если action = CASCADE, Hibernate сначала удаляет дочерние сущности (если нет orphanRemoval).
Ограничения
Не влияет на логику EntityManager.remove().
Не работает с @ManyToMany (только через @JoinTable + отдельные констрейнты).
Примеры использования
Пример 1: Каскадное удаление (CASCADE)
@Entity
public class Comment {
@ManyToOne
@OnDelete(action = OnDeleteAction.CASCADE) // Удалить комментарии при удалении Post
private Post post;
}
Пример 2: Обнуление ссылки (SET_NULL)
@Entity
public class Employee {
@ManyToOne
@OnDelete(action = OnDeleteAction.SET_NULL) // department_id = NULL при удалении Department
private Department department;
}
Проблемы
Неявное удаление: При CASCADE данные могут исчезнуть без явного вызова delete().
Несовместимость: Некоторые БД (например, MySQL с InnoDB) требуют индексов для ON DELETE CASCADE.
Когда использовать?
Для жестких зависимостей (например, Order → User).
В legacy-системах, где удаление управляется триггерами БД.
#Java #Training #Hard #Spring #Hibernate #OnDelete
@OptimisticLock в Hibernate
Аннотация @OptimisticLock управляет оптимистичной блокировкой сущности в Hibernate, определяя, какие поля должны проверяться на изменение при обновлении.
Пакет: org.hibernate.annotations
Применяется к: классу сущности (@Entity) или отдельным полям.
Стратегии:
OptimisticLockType.VERSION (по умолчанию) — использует @Version.
OptimisticLockType.ALL — проверяет все поля.
OptimisticLockType.DIRTY — проверяет только измененные поля.
OptimisticLockType.NONE — отключает проверку.
Параметры и настройки
Атрибуты
type: Стратегия проверки изменений (VERSION, ALL, DIRTY, NONE).
excluded: Если true, поле исключается из проверки (при type = ALL/DIRTY).
Примеры
Жизненный цикл и обработка
Как работает оптимистичная блокировка?
При чтении сущности:
Hibernate запоминает состояние полей (для ALL/DIRTY).
При обновлении:
Генерируется SQL вида:
Если условие не выполняется (данные изменились), выбрасывается OptimisticLockException.
Интеграция с Spring Boot
Глобальная стратегия:
Обработка исключений:
Примеры использования
Пример 1: Проверка всех полей
Пример 2: Исключение поля
Проблемы
ALL/DIRTY: Требуют хранения исходного состояния (память).
Нет поддержки в JPA: Только Hibernate.
Когда использовать?
VERSION — для большинства случаев.
DIRTY — для больших сущностей с редкими конфликтами.
#Java #Training #Hard #Spring #Hibernate #OptimisticLock
Аннотация @OptimisticLock управляет оптимистичной блокировкой сущности в Hibernate, определяя, какие поля должны проверяться на изменение при обновлении.
Пакет: org.hibernate.annotations
Применяется к: классу сущности (@Entity) или отдельным полям.
Стратегии:
OptimisticLockType.VERSION (по умолчанию) — использует @Version.
OptimisticLockType.ALL — проверяет все поля.
OptimisticLockType.DIRTY — проверяет только измененные поля.
OptimisticLockType.NONE — отключает проверку.
Параметры и настройки
Атрибуты
type: Стратегия проверки изменений (VERSION, ALL, DIRTY, NONE).
excluded: Если true, поле исключается из проверки (при type = ALL/DIRTY).
Примеры
@Entity
@OptimisticLock(type = OptimisticLockType.ALL)
public class Account {
@Id
private Long id;
@OptimisticLock(excluded = true) // Не проверяется при обновлении
private String secretCode;
}
Жизненный цикл и обработка
Как работает оптимистичная блокировка?
При чтении сущности:
Hibernate запоминает состояние полей (для ALL/DIRTY).
При обновлении:
Генерируется SQL вида:
UPDATE account SET ... WHERE id = ? AND (secretCode IS NULL OR secretCode = ?)
Если условие не выполняется (данные изменились), выбрасывается OptimisticLockException.
Интеграция с Spring Boot
Глобальная стратегия:
spring.jpa.properties.hibernate.optimistic_lock.strategy=all
Обработка исключений:
@ExceptionHandler(OptimisticLockException.class)
public void handleConflict() { /* ... */ }
Примеры использования
Пример 1: Проверка всех полей
@Entity
@OptimisticLock(type = OptimisticLockType.ALL)
public class Product {
@Version
private Long version; // Дополнительная проверка
}
Пример 2: Исключение поля
@Entity
@OptimisticLock(type = OptimisticLockType.DIRTY)
public class User {
@OptimisticLock(excluded = true)
private LocalDateTime lastLogin;
}
Проблемы
ALL/DIRTY: Требуют хранения исходного состояния (память).
Нет поддержки в JPA: Только Hibernate.
Когда использовать?
VERSION — для большинства случаев.
DIRTY — для больших сущностей с редкими конфликтами.
#Java #Training #Hard #Spring #Hibernate #OptimisticLock