@ColumnTransformer в Hibernate / JPA
Аннотация @ColumnTransformer (из пакета org.hibernate.annotations) позволяет кастомизировать SQL-выражения для чтения (read) и записи (write) значений столбца в БД.
Используется, когда необходимо:
Применить функцию БД при загрузке (SELECT).
Преобразовать значение перед вставкой/обновлением (INSERT/UPDATE).
Работать с зашифрованными или вычисляемыми полями.
Аннотация принимает следующие атрибуты:
read (String):
SQL-выражение, применяемое при чтении значения из БД.
Может включать функции БД (например, decrypt, CAST, COALESCE).
Использует плейсхолдер ? для исходного значения столбца.
Пример:
write (String):
SQL-выражение, применяемое при записи значения в БД.
Также использует плейсхолдер ? для нового значения.
Пример:
forColumn (String, опционально):
Указывает имя столбца, если оно отличается от имени поля в сущности.
Жизненный цикл и обработка
При загрузке сущности (SELECT)
Hibernate заменяет прямое обращение к столбцу на выражение из read.
Пример SQL:
При сохранении (INSERT/UPDATE)
Значение поля преобразуется выражением из write.
Пример SQL:
Нативные запросы
Если используется EntityManager.createNativeQuery(), @ColumnTransformer игнорируется (требуется ручное применение функций БД).
Механизмы Hibernate и интеграция
Генерация SQL
Обработка @ColumnTransformer происходит на этапе компиляции HQL в SQL (через ASTQueryTranslatorFactory).
Зависимость от диалекта БД
Выражения в read/write должны быть совместимы с диалектом БД (например, AES_ENCRYPT в MySQL, PGP_SYM_ENCRYPT в PostgreSQL).
Интеграция с JPA
Аннотация является Hibernate-специфичной и не входит в стандарт JPA.
Примеры использования
1. Шифрование данных
2. Преобразование типов
3. Условные значения
Ограничения и настройки
Производительность
Сложные выражения могут замедлять запросы.
Поддержка в JPQL
В JPQL-запросах @ColumnTransformer не применяется (только в SQL, генерируемом Hibernate).
Альтернативы
Для сложной логики можно использовать:
@Convert (JPA 2.1).
Реализацию UserType в Hibernate.
Настройка через hibernate.xml
Нет глобальной настройки для @ColumnTransformer — только через аннотации.
#Java #Training #Hard #Spring #Hibernate #ColumnTransformer
Аннотация @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
Что выведет код?
#Tasks
import java.util.function.Supplier;
public class Task080425 {
public static void main(String[] args) {
int x = 5;
Supplier<Integer> lambda = () -> x + 1;
x = 10;
System.out.println(lambda.get());
}
}
#Tasks
Please open Telegram to view this post
VIEW IN TELEGRAM
Вопросы с собеседования 👩💻
Какой метод используется для очистки ArrayList?
Какой метод используется для очистки ArrayList?
Anonymous Quiz
2%
empty()
4%
reset()
60%
clear()
33%
removeAll()
@DynamicInsert в Hibernate
Аннотация @DynamicInsert (из пакета org.hibernate.annotations) указывает Hibernate генерировать SQL-запросы INSERT только для ненулевых полей сущности. Это позволяет оптимизировать вставку данных, исключая из запроса столбцы со значениями null.
Применяется на уровне класса сущности:
У @DynamicInsert нет настраиваемых атрибутов — это маркерная аннотация (присутствие/отсутствие влияет на поведение).
Как работает
Если @DynamicInsert = true (или аннотация присутствует):
Hibernate анализирует, какие поля сущности не null.
В SQL-запрос INSERT включаются только эти поля.
Если @DynamicInsert = false (по умолчанию):
В INSERT попадают все поля, включая null.
Примеры SQL
Без @DynamicInsert
Сгенерированный SQL (все поля, даже age=null):
С @DynamicInsert
Тот же код, но SQL будет короче:
Жизненный цикл и обработка
Во время компиляции маппинга
Hibernate определяет, нужно ли учитывать @DynamicInsert для сущности.
При выполнении session.save()
Проверяются значения полей.
Формируется SQL с учетом только ненулевых полей.
При генерации прокси-классов (если используется ленивая загрузка)
Не влияет напрямую, но может уменьшить объем данных в кеше.
Настройки и интеграция
В hibernate.cfg.xml или application.properties:
Но лучше использовать аннотацию для точечного контроля.
Совместимость с другими аннотациями
@DynamicUpdate – аналогично оптимизирует UPDATE.
@SelectBeforeUpdate – может конфликтовать (проверяет изменения перед UPDATE).
Когда использовать
Плюсы
Уменьшает размер SQL-запросов.
Ускоряет вставку, если много null-полей.
Полезно для таблиц с большим числом столбцов.
Минусы
Усложняет отладку (меняется SQL).
Незначительный оверхед на проверку null.
Бесполезен, если все поля обычно заполнены.
Оптимальные сценарии
Таблицы с 50+ столбцами, где большинство значений null.
Частые вставки частично заполненных объектов.
Работа с унаследованными системами, где NULL в столбцах нежелателен.
Примеры
Пример 1: Игнорирование null
При сохранении Product(id=1, name="Laptop", stock=null):
Комбинация с @ColumnDefault
При Account(id=1, balance=null) в БД запишется balance=0.
Ограничения
Не работает с @GeneratedValue (Hibernate требует все поля для идентификации).
Не влияет на JPQL/HQL – только на SQL, генерируемый Hibernate.
Проблемы с кешированием – если кеш второго уровня ожидает полный объект.
#Java #Training #Hard #Spring #Hibernate #DynamicInsert
Аннотация @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 только для изменившихся полей сущности, а не для всех полей. Это улучшает производительность, особенно для таблиц с большим количеством столбцов.
Применяется на уровне класса сущности:
Как работает
Если @DynamicUpdate = true (или аннотация присутствует):
Hibernate отслеживает изменения в полях сущности.
В SQL-запрос UPDATE включаются только измененные поля.
Если @DynamicUpdate = false (по умолчанию):
В UPDATE попадают все поля, даже если их значения не изменились.
Примеры SQL
Без @DynamicUpdate
Сгенерированный SQL (все поля, даже неизмененные):
С @DynamicUpdate
Тот же код, но SQL будет короче:
Жизненный цикл и обработка
При загрузке сущности
Hibernate сохраняет исходные значения полей (снимок состояния).
При изменении полей
Сравниваются текущие значения с исходными.
При выполнении session.update() или transaction.commit()
Формируется SQL только для измененных полей.
Влияние на кеширование
Кеш второго уровня обновляется только фактически измененными данными.
Настройки и интеграция
В hibernate.cfg.xml или application.properties:
Но рекомендуется использовать аннотацию для точного контроля.
Совместимость с другими аннотациями
@DynamicInsert – аналогичная оптимизация для INSERT.
@SelectBeforeUpdate – если true, Hibernate сначала загружает текущее состояние, что увеличивает точность @DynamicUpdate.
Когда использовать
Плюсы
Уменьшает объем SQL-запросов.
Ускоряет UPDATE для "широких" таблиц.
Снижает нагрузку на БД.
Минусы
Небольшой оверхед на отслеживание изменений.
Может усложнить отладку (меняется SQL).
Оптимальные сценарии
Таблицы с 20+ столбцами, где обновляются 1-2 поля.
Частые обновления части полей (например, счетчики, статусы).
Системы с высокой нагрузкой на БД.
#Java #Training #Hard #Spring #Hibernate #DynamicUpdate
Аннотация @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
Что выведет код?
#Tasks
import java.util.HashSet;
import java.util.Set;
public class Task090425 {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
set.add("A");
set.add(new String("A"));
set.add("B");
set.remove(new String("A"));
System.out.println(set.size());
}
}
#Tasks
Please open Telegram to view this post
VIEW IN TELEGRAM
Вопросы с собеседования 👩💻
Какой метод возвращает квадратный корень числа?
Какой метод возвращает квадратный корень числа?
Anonymous Quiz
8%
Math.power()
14%
Math.square()
74%
Math.sqrt()
4%
Math.root()
@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 и др.).
Пример:
Активация фильтра в сессии:
Фильтр не активен по умолчанию, его нужно включать явно в Session или EntityManager.
Пример:
Применение в запросах:
После активации фильтр автоматически добавляет условие ко всем запросам, связанным с сущностью.
Для @OneToMany и других коллекций фильтр применяется при загрузке ассоциаций.
Деактивация фильтра:
Фильтр можно отключить:
Механизмы Hibernate и Spring, связанные с @Filter
1. @FilterDef и @ParamDef
@FilterDef определяет фильтр и его параметры.
@ParamDef задает тип параметра (например, String, boolean).
Должен быть указан на уровне класса (обычно на @Entity или в package-info.java).
2. Интеграция с Spring Data JPA
Фильтры можно активировать в Spring-репозиториях через EntityManager:
3. Глобальная настройка фильтров в Spring Boot
Можно автоматически включать фильтры для всех запросов через @PostConstruct:
Варианты настройки и применения
1. Фильтрация коллекций
2. Множественные фильтры
3. Динамическое управление фильтрами
#Java #Training #Hard #Spring #Hibernate #Filter #FilterDef
Аннотация @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-функций.
Пример:
Жизненный цикл
Загрузка сущности:
При выборке сущности из БД (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:
Варианты использования и ограничения
1. Динамические вычисления
Расчет рейтинга на основе связанных данных:
2. Использование SQL-функций
Форматирование даты:
3. Ограничения
Нет поддержки JPA-стандарта: @Formula – это аннотация Hibernate, а не JPA.
Нет параметризации: SQL в @Formula статичен, нельзя передавать параметры.
Производительность: Сложные подзапросы могут замедлять загрузку сущностей.
Альтернативы
@PostLoad + вычисление в Java:
Плюс: независимость от SQL. Минус: вычисление в Java, а не в БД.
Представления (Database Views) + @Immutable:
Создать VIEW в БД и отобразить его как неизменяемую сущность.
#Java #Training #Hard #Spring #Hibernate #Formula
Аннотация @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
Что выведет код?
#Tasks
import java.util.HashSet;
import java.util.Set;
class Person1004 {
String name;
Person1004(String name) { this.name = name; }
public boolean equals(Object o) {
return this.name.length() == ((Person1004)o).name.length();
}
}
public class Task100425 {
public static void main(String[] args) {
Set<Person1004> set = new HashSet<>();
set.add(new Person1004("Alice"));
set.add(new Person1004("Bob"));
set.add(new Person1004("Charlie"));
set.add(new Person1004("Dave"));
System.out.println(set.size());
}
}
#Tasks