Java for Beginner
672 subscribers
540 photos
155 videos
12 files
827 links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Аннотации @Cache и @Cacheable в Hibernate

Аннотации
@Cache и @Cacheable относятся к кешированию сущностей и коллекций в Hibernate. Они позволяют оптимизировать производительность за счёт хранения часто используемых данных в памяти, уменьшая количество обращений к базе данных.

@Cache (org.hibernate.annotations.Cache) – настраивает стратегию кеширования для сущности или коллекции.
@Cacheable (javax.persistence.Cacheable / jakarta.persistence.Cacheable) – указывает, что сущность может быть кеширована (используется вместе с @Cache).

@Cache (Hibernate)

Параметры:
usage (CacheConcurrencyStrategy) – стратегия кеширования:

READ_ONLY – только для чтения (если сущность не изменяется).
NONSTRICT_READ_WRITE – кеш обновляется асинхронно при изменениях.
READ_WRITE – гарантирует согласованность через мягкие блокировки.
TRANSACTIONAL – полная поддержка транзакций (для JTA).


region (опционально)имя региона кеша (если используется несколько кешей, например, Ehcache, Hazelcast).

include (CacheInclude) указывает, включать ли ленивые поля в кеш (ALL или NON_LAZY).

Пример:
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "employees")
public class Employee { ... }


@Cacheable (JPA)

Указывает, что сущность поддерживает кеширование.
Работает в паре с
@Cache (Hibernate) или настройками JPA-провайдера.
Если
@Cacheable(false), сущность игнорируется кешем, даже если кеширование включено глобально.

Пример:
@Entity
@Cacheable(true) // Включено кеширование (по умолчанию true)
public class Product { ... }


Механизмы кеширования в Hibernate

Уровни кеширования

Кеш первого уровня (Session)
Автоматический, живет в рамках одной сессии (EntityManager).
Не требует
@Cache.

Кеш второго уровня (SessionFactory)
Общий для всех сессий.
Требует явного объявления (
@Cache + @Cacheable).

Кеш запросов (Query Cache)
Кеширует результаты JPQL/HQL-запросов.

Включается отдельно:
spring.jpa.properties.hibernate.cache.use_query_cache=true


Настройка в Spring Boot

Включение кеша второго уровня
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
(Пример для Ehcache; также поддерживаются Infinispan, Hazelcast, Redis.)


Указание кешируемых сущностей

Через @Cache + @Cacheable.

Или глобально в application.properties:
spring.jpa.properties.javax.persistence.sharedCache.mode=ALL
(Варианты: ALL, NONE, ENABLE_SELECTIVE, DISABLE_SELECTIVE.)


Настройка поставщика кеша

Например, для Ehcache:
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>


И файл ehcache.xml в resources/.


Когда использовать?

@Cache + @Cacheable – для часто читаемых, редко изменяемых данных (справочники, настройки).
READ_ONLY – если сущности никогда не обновляются (например, Country, City).
READ_WRITE – если данные изменяются, но требуется высокая скорость чтения.
Избегать для часто изменяемых данных (кеш будет постоянно инвалидироваться).


#Java #Training #Hard #Spring #Hibernate #Cache #Cacheable
Аннотация @Cascade в Hibernate

Аннотация @Cascade (org.hibernate.annotations.Cascade) управляет каскадными операциями (сохранение, обновление, удаление) для ассоциаций между сущностями. Она дополняет или заменяет стандартные каскадные операции JPA (CascadeType).

Отличается от javax.persistence.CascadeType тем, что поддерживает специфичные для Hibernate каскады, например, CascadeType.LOCK или CascadeType.REPLICATE.

Параметры

Принимает одно или несколько значений из перечисления org.hibernate.annotations.CascadeType:
PERSIST – сохраняет связанную сущность при сохранении родителя (аналог CascadeType.PERSIST).
MERGE – обновляет связанную сущность при обновлении родителя (аналог CascadeType.MERGE).
REMOVE – удаляет связанную сущность при удалении родителя (аналог CascadeType.REMOVE).
REFRESH – обновляет связанную сущность при обновлении родителя (аналог CascadeType.REFRESH).
DETACH – отключает связанную сущность от контекста (аналог CascadeType.DETACH).
SAVE_UPDATE – сохраняет или обновляет связанную сущность при сохранении/обновлении родителя (устарело в Hibernate 6+, вместо этого используйте PERSIST + MERGE).
LOCK – блокирует связанную сущность при блокировке родителя (специфично для Hibernate).
REPLICATE – реплицирует связанную сущность при репликации родителя (специфично для Hibernate).
DELETE – удаляет связанную сущность при удалении родителя (аналог REMOVE, но работает в некоторых случаях иначе).
DELETE_ORPHAN – удаляет связанную сущность, если она больше не связана с родителем (работает только для коллекций).


Примеры использования

Каскадное сохранение и удаление
@Entity
public class Parent {
@Id
private Long id;

@OneToMany(mappedBy = "parent")
@Cascade({CascadeType.PERSIST, CascadeType.REMOVE})
private List<Child> children;
}


Что делает:
При сохранении Parent автоматически сохраняются все Child.
При удалении Parent автоматически удаляются все Child.


Каскадное обновление и управление "сиротами"
@Entity
public class Author {
@Id
private Long id;

@OneToMany(mappedBy = "author")
@Cascade({CascadeType.MERGE, CascadeType.DELETE_ORPHAN})
private List<Book> books;
}


Что делает:
При обновлении Author обновляются все связанные Book.
Если книга удаляется из списка books, она автоматически удаляется из БД (orphanRemoval).


Специфичные каскады Hibernate (LOCK, REPLICATE)
@Entity
public class Order {
@Id
private Long id;

@OneToMany(mappedBy = "order")
@Cascade({CascadeType.LOCK, CascadeType.REPLICATE})
private List<Item> items;
}


Что делает:
При блокировке Order блокируются все Item.
При репликации Order реплицируются все Item.


Разница между @Cascade и cascade в JPA

JPA (javax.persistence.CascadeType)
@OneToMany(mappedBy = "parent", cascade = {javax.persistence.CascadeType.PERSIST, javax.persistence.CascadeType.REMOVE})


Стандартный механизм JPA.
Поддерживает только базовые операции (PERSIST, MERGE, REMOVE, REFRESH, DETACH, ALL).


Hibernate (@Cascade)
@OneToMany(mappedBy = "parent")
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN})


Расширенный функционал (например, DELETE_ORPHAN, LOCK, REPLICATE).
Работает только в Hibernate.


Когда использовать @Cascade?

Если нужны специфичные для Hibernate каскады (LOCK, REPLICATE).
Если требуется удаление "сирот" (DELETE_ORPHAN).
В остальных случаях лучше использовать стандартный cascade из JPA.


Оптимизация производительности

Каскадные операции могут приводить к неожиданным DELETE/UPDATE.

Рекомендуется:
Использовать orphanRemoval только там, где это необходимо.
Избегать CascadeType.ALL (может привести к неявным удалениям).


#Java #Training #Hard #Spring #Hibernate #Cascade
@ColumnTransformer в Hibernate / JPA

Аннотация @ColumnTransformer (из пакета org.hibernate.annotations) позволяет кастомизировать SQL-выражения для чтения (read) и записи (write) значений столбца в БД.

Используется, когда необходимо:
Применить функцию БД при загрузке (SELECT).
Преобразовать значение перед вставкой/обновлением (INSERT/UPDATE).
Работать с зашифрованными или вычисляемыми полями.


Аннотация принимает следующие атрибуты:

read (String):
SQL-выражение, применяемое при чтении значения из БД.
Может включать функции БД (например, decrypt, CAST, COALESCE).
Использует плейсхолдер ? для исходного значения столбца.


Пример:
@ColumnTransformer(read = "decrypt(credit_card_num, 'my_secret_key')")
private String creditCardNumber;


write (String):
SQL-выражение, применяемое при записи значения в БД.
Также использует плейсхолдер ? для нового значения.

Пример:
@ColumnTransformer(write = "encrypt(?, 'my_secret_key')")
private String password;


forColumn (String, опционально):
Указывает имя столбца, если оно отличается от имени поля в сущности.

Жизненный цикл и обработка

При загрузке сущности (SELECT)
Hibernate заменяет прямое обращение к столбцу на выражение из read.


Пример SQL:
SELECT decrypt(credit_card_num, 'my_secret_key') FROM users WHERE id = 1;


При сохранении (INSERT/UPDATE)
Значение поля преобразуется выражением из write.

Пример SQL:
INSERT INTO users (password) VALUES (encrypt('qwerty', 'my_secret_key'));


Нативные запросы
Если используется EntityManager.createNativeQuery(), @ColumnTransformer игнорируется (требуется ручное применение функций БД).

Механизмы Hibernate и интеграция

Генерация SQL
Обработка @ColumnTransformer происходит на этапе компиляции HQL в SQL (через ASTQueryTranslatorFactory).

Зависимость от диалекта БД
Выражения в read/write должны быть совместимы с диалектом БД (например, AES_ENCRYPT в MySQL, PGP_SYM_ENCRYPT в PostgreSQL).

Интеграция с JPA
Аннотация является Hibernate-специфичной и не входит в стандарт JPA.

Примеры использования

1. Шифрование данных
@Entity
public class User {
@Id
private Long id;

@ColumnTransformer(
read = "pgp_sym_decrypt(credit_card_num, 'secret_key')",
write = "pgp_sym_encrypt(?, 'secret_key')"
)
@Column(name = "credit_card_num")
private String creditCardNumber;
}


2. Преобразование типов
@ColumnTransformer(
read = "CAST(price AS decimal(10,2))",
write = "CAST(? AS varchar)"
)
private BigDecimal price;


3. Условные значения
@ColumnTransformer(
read = "COALESCE(status, 'DEFAULT')"
)
private String status;


Ограничения и настройки


Производительность

Сложные выражения могут замедлять запросы.

Поддержка в JPQL
В JPQL-запросах @ColumnTransformer не применяется (только в SQL, генерируемом Hibernate).

Альтернативы
Для сложной логики можно использовать:
@Convert (JPA 2.1).
Реализацию UserType в Hibernate.
Настройка через hibernate.xml
Нет глобальной настройки для
@ColumnTransformer — только через аннотации.

#Java #Training #Hard #Spring #Hibernate #ColumnTransformer
@DynamicInsert в Hibernate

Аннотация @DynamicInsert (из пакета org.hibernate.annotations) указывает Hibernate генерировать SQL-запросы INSERT только для ненулевых полей сущности. Это позволяет оптимизировать вставку данных, исключая из запроса столбцы со значениями null.

Применяется на уровне класса сущности:
@Entity
@DynamicInsert
public class User { ... }


У
@DynamicInsert нет настраиваемых атрибутов — это маркерная аннотация (присутствие/отсутствие влияет на поведение).

Как работает

Если @DynamicInsert = true (или аннотация присутствует):
Hibernate анализирует, какие поля сущности не null.
В SQL-запрос INSERT включаются только эти поля.


Если @DynamicInsert = false (по умолчанию):
В INSERT попадают все поля, включая null.

Примеры SQL

Без @DynamicInsert
User user = new User();
user.setId(1L);
user.setName("Alice"); // age = null


Сгенерированный SQL (все поля, даже age=null):
INSERT INTO user (id, name, age) VALUES (1, 'Alice', NULL);


С @DynamicInsert
@DynamicInsert
@Entity
public class User { ... }


Тот же код, но SQL будет короче:
INSERT INTO user (id, name) VALUES (1, 'Alice');  -- поле age пропущено!


Жизненный цикл и обработка

Во время компиляции маппинга
Hibernate определяет, нужно ли учитывать
@DynamicInsert для сущности.
При выполнении
session.save()
Проверяются значения полей.
Формируется SQL с учетом только ненулевых полей.
При генерации прокси-классов (если используется ленивая загрузка)
Не влияет напрямую, но может уменьшить объем данных в кеше.


Настройки и интеграция

В hibernate.cfg.xml или application.properties:
hibernate.dynamic_insert=true  # Применяется ко всем сущностям!


Но лучше использовать аннотацию для точечного контроля.

Совместимость с другими аннотациями

@DynamicUpdate – аналогично оптимизирует UPDATE.
@SelectBeforeUpdate – может конфликтовать (проверяет изменения перед UPDATE).


Когда использовать

Плюсы
Уменьшает размер SQL-запросов.
Ускоряет вставку, если много null-полей.
Полезно для таблиц с большим числом столбцов.


Минусы
Усложняет отладку (меняется SQL).
Незначительный оверхед на проверку null.
Бесполезен, если все поля обычно заполнены.


Оптимальные сценарии

Таблицы с 50+ столбцами, где большинство значений null.
Частые вставки частично заполненных объектов.
Работа с унаследованными системами, где NULL в столбцах нежелателен.


Примеры

Пример 1: Игнорирование null
@DynamicInsert
@Entity
public class Product {
@Id
private Long id;
private String name;
private Integer stock; // Часто null для новых товаров
}


При сохранении Product(id=1, name="Laptop", stock=null):
INSERT INTO product (id, name) VALUES (1, 'Laptop');


Комбинация с @ColumnDefault
@DynamicInsert
@Entity
public class Account {
@Id
private Long id;

@ColumnDefault("0")
private BigDecimal balance; // Если null, БД подставит 0
}


При Account(id=1, balance=null) в БД запишется balance=0.

Ограничения
Не работает с @GeneratedValue (Hibernate требует все поля для идентификации).
Не влияет на JPQL/HQL – только на SQL, генерируемый Hibernate.
Проблемы с кешированием – если кеш второго уровня ожидает полный объект.


#Java #Training #Hard #Spring #Hibernate #DynamicInsert
@DynamicUpdate в Hibernate

Аннотация @DynamicUpdate (из пакета org.hibernate.annotations) указывает Hibernate генерировать SQL-запросы UPDATE только для изменившихся полей сущности, а не для всех полей. Это улучшает производительность, особенно для таблиц с большим количеством столбцов.

Применяется на уровне класса сущности:
@Entity
@DynamicUpdate
public class User { ... }


Как работает


Если @DynamicUpdate = true (или аннотация присутствует):
Hibernate отслеживает изменения в полях сущности.
В SQL-запрос UPDATE включаются только измененные поля.


Если @DynamicUpdate = false (по умолчанию):
В UPDATE попадают все поля, даже если их значения не изменились.

Примеры SQL

Без @DynamicUpdate
User user = session.get(User.class, 1L);
user.setName("Alice"); // Меняем только имя


Сгенерированный SQL (все поля, даже неизмененные):
UPDATE user SET name='Alice', age=30, email='alice@example.com' WHERE id=1;


С @DynamicUpdate
@DynamicUpdate
@Entity
public class User { ... }


Тот же код, но SQL будет короче:
UPDATE user SET name='Alice' WHERE id=1;  -- Только измененное поле!


Жизненный цикл и обработка

При загрузке сущности
Hibernate сохраняет исходные значения полей (снимок состояния).

При изменении полей
Сравниваются текущие значения с исходными.

При выполнении session.update() или transaction.commit()
Формируется SQL только для измененных полей.

Влияние на кеширование
Кеш второго уровня обновляется только фактически измененными данными.

Настройки и интеграция

В hibernate.cfg.xml или application.properties:
hibernate.dynamic_update=true  # Применяется ко всем сущностям


Но рекомендуется использовать аннотацию для точного контроля.

Совместимость с другими аннотациями

@DynamicInsert – аналогичная оптимизация для INSERT.
@SelectBeforeUpdate – если true, Hibernate сначала загружает текущее состояние, что увеличивает точность @DynamicUpdate.

Когда использовать

Плюсы
Уменьшает объем SQL-запросов.
Ускоряет UPDATE для "широких" таблиц.
Снижает нагрузку на БД.


Минусы
Небольшой оверхед на отслеживание изменений.
Может усложнить отладку (меняется SQL).


Оптимальные сценарии

Таблицы с 20+ столбцами, где обновляются 1-2 поля.
Частые обновления части полей (например, счетчики, статусы).
Системы с высокой нагрузкой на БД.


#Java #Training #Hard #Spring #Hibernate #DynamicUpdate
@Filter в Hibernate

Аннотация @Filter позволяет динамически применять условия фильтрации к сущностям или коллекциям на уровне базы данных. Она используется для ограничения выборки данных в зависимости от заданных параметров. Находится в пакете org.hibernate.annotations.

Параметры и настройки

Аннотация @Filter имеет следующие атрибуты:

name (обязательный, String):
Уникальное имя фильтра, которое используется для его активации/деактивации в сессии.
Пример:
@Filter(name = "activeUserFilter").

condition (необязательный, String):
SQL-условие, которое добавляется в запрос при активации фильтра.
Может содержать параметры (например, :param), которые задаются во время выполнения.
Пример:
@Filter(name = "activeUserFilter", condition = "is_active = :activeStatus").

deduceAliasInjectionPoints (необязательный, boolean, default = true):
Определяет, должен ли Hibernate автоматически подставлять алиасы таблиц в условие.
Если false, условие должно содержать явные алиасы.


Жизненный цикл фильтра

Объявление фильтра:
Фильтр определяется на уровне класса сущности (@Entity) или коллекции (@ElementCollection, @OneToMany и др.).

Пример:
@Entity
@FilterDef(name = "activeUserFilter",
condition = "is_active = :activeStatus",
parameters = @ParamDef(name = "activeStatus", type = boolean.class))
@Filter(name = "activeUserFilter")
public class User { ... }


Активация фильтра в сессии:
Фильтр не активен по умолчанию, его нужно включать явно в Session или EntityManager.

Пример:
session.enableFilter("activeUserFilter")
.setParameter("activeStatus", true);


Применение в запросах:
После активации фильтр автоматически добавляет условие ко всем запросам, связанным с сущностью.
Для
@OneToMany и других коллекций фильтр применяется при загрузке ассоциаций.

Деактивация фильтра:
Фильтр можно отключить:
session.disableFilter("activeUserFilter");


Механизмы Hibernate и Spring, связанные с @Filter

1. @FilterDef и @ParamDef
@FilterDef определяет фильтр и его параметры.
@ParamDef задает тип параметра (например, String, boolean).
Должен быть указан на уровне класса (обычно на
@Entity или в package-info.java).

2. Интеграция с Spring Data JPA
Фильтры можно активировать в Spring-репозиториях через EntityManager:
@Repository
public class UserRepository {
@PersistenceContext
private EntityManager em;

public List<User> findActiveUsers() {
em.unwrap(Session.class)
.enableFilter("activeUserFilter")
.setParameter("activeStatus", true);
return em.createQuery("FROM User", User.class).getResultList();
}
}


3. Глобальная настройка фильтров в Spring Boot
Можно автоматически включать фильтры для всех запросов через @PostConstruct:
@Component
public class HibernateFilterConfig {
@PersistenceContext
private EntityManager em;

@PostConstruct
public void init() {
Session session = em.unwrap(Session.class);
session.enableFilter("activeUserFilter")
.setParameter("activeStatus", true);
}
}


Варианты настройки и применения

1. Фильтрация коллекций
@Entity
public class Department {
@OneToMany(mappedBy = "department")
@Filter(name = "activeUserFilter", condition = "is_active = :activeStatus")
private List<User> users;
}


2. Множественные фильтры
@Entity
@FilterDef(name = "activeFilter", condition = "is_active = :active")
@FilterDef(name = "roleFilter", condition = "role = :roleName")
@Filter(name = "activeFilter")
@Filter(name = "roleFilter")
public class User { ... }


3. Динамическое управление фильтрами
// Включение разных параметров в runtime
session.enableFilter("activeUserFilter")
.setParameter("activeStatus", true);
session.enableFilter("roleFilter")
.setParameter("roleName", "ADMIN");


#Java #Training #Hard #Spring #Hibernate #Filter #FilterDef
@Formula в Hibernate

Аннотация @Formula позволяет задавать вычисляемые (производные) поля в сущностях Hibernate, значения которых не хранятся в базе данных, а вычисляются на основе SQL-выражения. Находится в пакете org.hibernate.annotations.

Атрибуты аннотации

value (String, обязательный):
SQL-выражение, которое выполняется при загрузке сущности из БД.
Может включать столбцы таблицы, агрегатные функции (SUM, AVG), подзапросы и даже вызовы SQL-функций.


Пример:
@Formula("(SELECT COUNT(*) FROM orders o WHERE o.customer_id = id)")
private int orderCount;


Жизненный цикл


Загрузка сущности:
При выборке сущности из БД (EntityManager.find(), HQL, Criteria API) Hibernate выполняет SQL из @Formula и подставляет результат в поле.
Вычисление происходит на стороне БД, а не в Java-коде.


Обновление сущности:
Поле, помеченное @Formula, не сохраняется в БД при INSERT/UPDATE.
Если SQL-выражение зависит от других полей, его значение пересчитывается при каждой загрузке.


Кэширование:
Если сущность кэшируется (например, через @Cacheable), значение @Formula также кэшируется.

Обработка @Formula

Во время компиляции Hibernate:
Hibernate парсит SQL-выражение и проверяет его синтаксис (но не валидирует до выполнения запроса).
Если выражение содержит ссылки на другие таблицы, Hibernate не создает JOIN автоматически – нужно явно указывать подзапросы.


В рантайме:
При генерации SQL для загрузки сущности Hibernate подставляет формулу как есть (без параметризации).

Зависимость от диалекта БД

Поскольку
@Formula использует "сырой" SQL, выражение должно быть совместимо с диалектом Hibernate (MySQLDialect, PostgreSQLDialect и т. д.).

Пример для PostgreSQL:
@Formula("CONCAT(first_name, ' ', last_name)")
private String fullName;
Для MySQL CONCAT работает аналогично, но в Oracle нужно использовать ||.


Варианты использования и ограничения

1. Динамические вычисления
Расчет рейтинга на основе связанных данных:
@Formula("(SELECT AVG(r.rating) FROM reviews r WHERE r.product_id = id)")
private Double averageRating;


2. Использование SQL-функций
Форматирование даты:
@Formula("TO_CHAR(created_at, 'YYYY-MM-DD')")
private String creationDate;


3. Ограничения
Нет поддержки JPA-стандарта: @Formula – это аннотация Hibernate, а не JPA.
Нет параметризации: SQL в
@Formula статичен, нельзя передавать параметры.
Производительность: Сложные подзапросы могут замедлять загрузку сущностей.


Альтернативы

@PostLoad + вычисление в Java:
@Transient
private int orderCount;

@PostLoad
private void calculateOrderCount() {
this.orderCount = orderRepository.countByCustomerId(this.id);
}


Плюс: независимость от SQL. Минус: вычисление в Java, а не в БД.
Представления (Database Views) + @Immutable:
Создать VIEW в БД и отобразить его как неизменяемую сущность.


#Java #Training #Hard #Spring #Hibernate #Formula
@Generated в Hibernate

Аннотация @Generated указывает, что значение поля автоматически генерируется базой данных (например, через DEFAULT, TRIGGER, AUTO_INCREMENT или вычисляемый столбец). В отличие от @Formula, поле сохраняется в БД, но Hibernate обновляет его после вставки или обновления. Находится в пакете org.hibernate.annotations.

Атрибуты аннотации

GenerationTime (обязательный):

Определяет, когда значение генерируется:
GenerationTime.INSERT – только при вставке (например, AUTO_INCREMENT).
GenerationTime.ALWAYS – при вставке и обновлении (например, триггеры или DEFAULT-значения).


value (необязательный, String):
SQL-выражение, которое генерирует значение (если БД поддерживает, например, DEFAULT CURRENT_TIMESTAMP).
Используется редко, так как обычно генерация определяется на стороне БД.


Жизненный цикл

При сохранении (INSERT)
Если GenerationTime.INSERT или GenerationTime.ALWAYS:
Hibernate выполняет INSERT, пропуская это поле.
После вставки делает SELECT (или использует RETURNING в PostgreSQL), чтобы получить сгенерированное значение.


Пример для AUTO_INCREMENT:
INSERT INTO users (name) VALUES ('Alice'); -- id не указывается  
SELECT last_insert_id(); -- Hibernate получает сгенерированный ID


При обновлении (UPDATE)
Если GenerationTime.ALWAYS:
Hibernate пропускает поле в UPDATE.
После обновления делает SELECT для получения нового значения.


Пример для триггера, обновляющего last_modified:
UPDATE users SET name = 'Bob' WHERE id = 1; -- last_modified не указывается  
SELECT last_modified FROM users WHERE id = 1; -- Hibernate получает новое значение


Кэширование
Если включен кэш второго уровня (@Cacheable), Hibernate обновляет значение в кэше после SELECT.

Механизмы Hibernate и настройки

1. Как Hibernate обрабатывает @Generated
Генерация SQL:
Поле исключается из INSERT/UPDATE.
Для получения значения выполняется дополнительный SELECT (если БД не поддерживает RETURNING).


Поддержка RETURNING (PostgreSQL, Oracle, SQL Server):
Если диалект поддерживает RETURNING, Hibernate использует его вместо отдельного SELECT:
INSERT INTO users (name) VALUES ('Alice') RETURNING id, created_at;  


2. Зависимость от диалекта БД
Поведение зависит от возможностей БД:

MySQL: AUTO_INCREMENT, DEFAULT CURRENT_TIMESTAMP.
PostgreSQL: SERIAL, GENERATED ALWAYS AS IDENTITY, триггеры.
Oracle: SEQUENCE + триггеры, DEFAULT-значения.



Примеры использования

1. Автоинкрементный ID
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // Аналог для JPA
private Long id;


Эквивалент Hibernate:
@Id
@Generated(GenerationTime.INSERT)
private Long id;


2. Временная метка создания
@Column(insertable = false) // Не указывается в INSERT  
@Generated(GenerationTime.INSERT)
private LocalDateTime createdAt; // DEFAULT CURRENT_TIMESTAMP в БД


3. Триггер для аудита
@Column(updatable = false)  
@Generated(GenerationTime.ALWAYS)
private LocalDateTime lastModified; // Обновляется триггером БД


Ограничения и альтернативы

Ограничения:
Не все БД поддерживают RETURNING:
Для MySQL требуется дополнительный SELECT.


Нет контроля над генерацией:
Логика полностью определяется БД (триггеры, DEFAULT).

Альтернативы:
@PrePersist и @PreUpdate (JPA):
@PrePersist  
private void prePersist() {
this.createdAt = LocalDateTime.now();
}
Плюс: работает везде. Минус: значение генерируется в Java, а не в БД.


#Java #Training #Hard #Spring #Hibernate #Generated
@Immutable в Hibernate

Аннотация @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):
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:

@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):

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)
@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): связывает значения с классами.

@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).

Пример использования
@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).

Примеры
@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.

Примеры
@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 (обнулить ссылку).

Примеры
@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
@TableGenerator в JPA

Аннотация @TableGenerator позволяет настраивать генерацию уникальных идентификаторов для сущностей с использованием высокоуровневого механизма, основанного на выделении диапазонов значений.

Пакет: javax.persistence (JPA)

Применяется к: полю с @Id и
@GeneratedValue(strategy = GenerationType.TABLE).

Параметры и настройки

Основные атрибуты
name: Уникальное имя генератора (обязательно).
table: Специальный объект для хранения значений (по умолчанию hibernate_sequences).
pkColumnName: Столбец, хранящий имена генераторов (по умолчанию sequence_name).
valueColumnName: Столбец, хранящий текущее значение (по умолчанию next_val).
pkColumnValue: Имя текущего генератора в pkColumnName (по умолчанию имя сущности).
initialValue: Начальное значение (по умолчанию 0).
allocationSize: Размер выделяемого блока значений (по умолчанию 50).
catalog: Каталог в БД (опционально).
schema: Схема в БД (опционально).
uniqueConstraints: Ограничения уникальности (опционально).


Жизненный цикл и обработка

Как работает генерация ID?
При старте приложения:
Если специльный объект не существует, Hibernate создает его автоматически (если ddl-auto=create или update).

При сохранении новой сущности:

Hibernate запрашивает новый диапазон значений (размером allocationSize).
Значения выделяются пакетно, что уменьшает количество обращений к БД.


При исчерпании диапазона:
Hibernate автоматически запрашивает следующий блок.

Пример SQL-запроса
SELECT next_val FROM hibernate_sequences WHERE sequence_name = 'employee_seq' FOR UPDATE;
UPDATE hibernate_sequences SET next_val = next_val + 50 WHERE sequence_name = 'employee_seq';


Интеграция с Spring Boot

Автогенерация схемы:
spring.jpa.hibernate.ddl-auto=update  


Кастомизация имени объекта:
@TableGenerator(  
name = "book_seq",
table = "custom_sequences",
pkColumnName = "seq_name",
valueColumnName = "seq_value",
allocationSize = 30
)


Примеры использования

Пример 1: Базовая настройка
@Entity  
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "emp_seq")
@TableGenerator(
name = "emp_seq",
allocationSize = 20
)
private Long id;
}


Пример 2: Кастомизация схемы
@TableGenerator(  
name = "project_seq",
schema = "hr",
table = "project_sequences",
initialValue = 1000
)


Проблемы

Производительность: Частые запросы к БД при малом allocationSize.
Блокировки: Использует SELECT ... FOR UPDATE, что может вызвать конкуренцию.


Когда использовать?

Когда не поддерживаются SEQUENCE (например, MySQL до 8.0).
Для переносимости между разными СУБД.



#Java #Training #Hard #Spring #JPA #TableGenerator
@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).


Примеры
@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