1. введение
В этой статье мы рассмотрим некоторые возможности динамического отображения Hibernate с помощью @Formula , @Where , @Filter и @Any аннотаций.
Обратите внимание, что, хотя Hibernate реализует спецификацию JPA, аннотации, описанные здесь, доступны только в Hibernate и не переносятся непосредственно в другие реализации JPA.
2. Настройка проекта
Чтобы продемонстрировать эти функции, нам понадобится только библиотека hibernate-core и резервная база данных H2:
org.hibernate hibernate-core 5.4.12.Final com.h2database h2 1.4.194
Для текущей версии библиотеки hibernate-core перейдите в Maven Central .
3. Вычисляемые Столбцы С Формулой @
Предположим, мы хотим вычислить значение поля сущности на основе некоторых других свойств. Один из способов сделать это-определить вычисляемое поле только для чтения в нашей сущности Java:
@Entity public class Employee implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private long grossIncome; private int taxInPercents; public long getTaxJavaWay() { return grossIncome * taxInPercents / 100; } }
Очевидным недостатком является то, что нам придется делать пересчет каждый раз, когда мы обращаемся к этому виртуальному полю с помощью геттера .
Было бы гораздо проще получить уже вычисленное значение из базы данных. Это можно сделать с помощью аннотации @Formula :
@Entity public class Employee implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private long grossIncome; private int taxInPercents; @Formula("grossIncome * taxInPercents / 100") private long tax; }
С @Формула , мы можем использовать подзапросы, вызывать собственные функции базы данных и хранимые процедуры и в основном делать все, что не нарушает синтаксис предложения SQL select для этого поля.
Hibernate достаточно умен, чтобы проанализировать предоставленный нами SQL и вставить правильные псевдонимы таблиц и полей. Следует иметь в виду, что, поскольку значение аннотации является необработанным SQL, это может сделать нашу картографическую базу данных зависимой.
Кроме того, имейте в виду, что значение вычисляется при извлечении сущности из базы данных . Следовательно, когда мы сохраняем или обновляем сущность, значение не будет пересчитано до тех пор, пока сущность не будет удалена из контекста и загружена снова:
Employee employee = new Employee(10_000L, 25); session.save(employee); session.flush(); session.clear(); employee = session.get(Employee.class, employee.getId()); assertThat(employee.getTax()).isEqualTo(2_500L);
4. Фильтрация Сущностей С Помощью @Where
Предположим, мы хотим предоставить дополнительное условие для запроса всякий раз, когда мы запрашиваем какую-либо сущность.
Например, нам нужно реализовать “мягкое удаление”. Это означает, что сущность никогда не удаляется из базы данных, а только помечается как удаленная с помощью поля boolean .
Мы должны были бы проявлять большую осторожность со всеми существующими и будущими запросами в приложении. Мы должны были бы предоставить это дополнительное условие для каждого запроса. К счастью, Hibernate предоставляет способ сделать это в одном месте:
@Entity @Where(clause = "deleted = false") public class Employee implements Serializable { // ... }
Аннотация @Where метода содержит предложение SQL, которое будет добавлено к любому запросу или подзапросу к этой сущности:
employee.setDeleted(true); session.flush(); session.clear(); employee = session.find(Employee.class, employee.getId()); assertThat(employee).isNull();
Как и в случае с @Formula аннотацией, поскольку мы имеем дело с необработанным SQL, условие @Where не будет переоценено до тех пор, пока мы не очистим сущность в базе данных и не исключим ее из контекста .
До этого времени сущность будет оставаться в контексте и будет доступна с запросами и поисками по id .
Аннотацию @Where также можно использовать для поля коллекции. Предположим, у нас есть список удаляемых телефонов:
@Entity public class Phone implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private boolean deleted; private String number; }
Затем, со стороны Сотрудника , мы могли бы сопоставить коллекцию удаляемых телефонов следующим образом:
public class Employee implements Serializable { // ... @OneToMany @JoinColumn(name = "employee_id") @Where(clause = "deleted = false") private Setphones = new HashSet<>(0); }
Разница в том, что Сотрудник.телефоны коллекция всегда будет отфильтрована, но мы все равно можем получить все телефоны, включая удаленные, с помощью прямого запроса:
employee.getPhones().iterator().next().setDeleted(true); session.flush(); session.clear(); employee = session.find(Employee.class, employee.getId()); assertThat(employee.getPhones()).hasSize(1); ListfullPhoneList = session.createQuery("from Phone").getResultList(); assertThat(fullPhoneList).hasSize(2);
5. Параметризованная Фильтрация С Помощью @Filter
Проблема с аннотацией @Where заключается в том, что она позволяет нам указывать только статический запрос без параметров, и он не может быть отключен или включен по требованию.
То @Фильтр аннотация работает так же, как и @Где , но он также может быть включен или отключен на уровне сеанса, а также параметризован.
5.1. Определение фильтра @
Чтобы продемонстрировать, как работает @Filter , давайте сначала добавим следующее определение фильтра к сущности Employee :
@FilterDef( name = "incomeLevelFilter", parameters = @ParamDef(name = "incomeLimit", type = "int") ) @Filter( name = "incomeLevelFilter", condition = "grossIncome > :incomeLimit" ) public class Employee implements Serializable {
Аннотация @FilterDef определяет имя фильтра и набор его параметров, которые будут участвовать в запросе. Тип параметра-это имя одного из типов Hibernate ( Type , User Type или CompositeUserType ), в нашем случае int .
Аннотация @FilterDef может быть размещена как на уровне типа, так и на уровне пакета. Обратите внимание, что он не указывает само условие фильтра (хотя мы могли бы указать Условие по умолчанию параметр).
Это означает, что мы можем определить фильтр (его имя и набор параметров) в одном месте, а затем по-разному определить условия для фильтра в нескольких других местах.
Это можно сделать с помощью аннотации @Filter . В нашем случае мы поместили его в тот же класс для простоты. Синтаксис условия-это необработанный SQL с именами параметров, которым предшествуют двоеточия.
5.2. Доступ к Отфильтрованным Объектам
Еще одно отличие @Filter от @Where заключается в том, что @Filter по умолчанию не включен. Мы должны включить его на уровне сеанса вручную и предоставить значения параметров для него:
session.enableFilter("incomeLevelFilter") .setParameter("incomeLimit", 11_000);
Теперь предположим, что у нас есть следующие три сотрудника в базе данных:
session.save(new Employee(10_000, 25)); session.save(new Employee(12_000, 25)); session.save(new Employee(15_000, 25));
Затем с включенным фильтром, как показано выше, только два из них будут видны при запросе:
Listemployees = session.createQuery("from Employee") .getResultList(); assertThat(employees).hasSize(2);
Обратите внимание, что как включенный фильтр, так и значения его параметров применяются только в текущем сеансе. В новом сеансе без включенного фильтра мы увидим всех трех сотрудников:
session = HibernateUtil.getSessionFactory().openSession(); employees = session.createQuery("from Employee").getResultList(); assertThat(employees).hasSize(3);
Кроме того, при прямой выборке объекта по идентификатору фильтр не применяется:
Employee employee = session.get(Employee.class, 1); assertThat(employee.getGrossIncome()).isEqualTo(10_000);
5.3. @Фильтр и Кэширование второго уровня
Если у нас есть приложение с высокой нагрузкой, то мы определенно хотим включить кэш второго уровня Hibernate, что может быть огромным преимуществом в производительности. Мы должны иметь в виду, что аннотация @Filter не очень хорошо работает с кэшированием.
Кэш второго уровня хранит только полные нефильтрованные коллекции . Если бы это было не так, то мы могли бы прочитать коллекцию в одном сеансе с включенным фильтром, а затем получить ту же кэшированную отфильтрованную коллекцию в другом сеансе даже с отключенным фильтром.
Вот почему @Фильтр аннотация в основном отключает кэширование для объекта.
6. Сопоставление Любой Ссылки На Сущность С @Any
Иногда мы хотим сопоставить ссылку на любой из нескольких типов сущностей, даже если они не основаны на одном @MappedSuperclass . Они даже могут быть сопоставлены с различными несвязанными таблицами. Мы можем добиться этого с помощью аннотации @Any .
В нашем примере нам нужно будет прикрепить некоторое описание к каждому объекту в нашем подразделении персистентности , а именно Сотрудник и Телефон . Было бы неразумно наследовать все сущности из одного абстрактного суперкласса только для этого.
6.1. Сопоставление Отношений С @Any
Вот как мы можем определить ссылку на любую сущность, которая реализует Сериализуемый (т. Е. На любую сущность вообще):
@Entity public class EntityDescription implements Serializable { private String description; @Any( metaDef = "EntityDescriptionMetaDef", metaColumn = @Column(name = "entity_type")) @JoinColumn(name = "entity_id") private Serializable entity; }
Свойство metaDef – это имя определения, а metaColumn – это имя столбца, который будет использоваться для различения типа сущности (в отличие от столбца-дискриминатора в сопоставлении иерархии одной таблицы).
Мы также указываем столбец, который будет ссылаться на id сущности. Стоит отметить, что этот столбец не будет внешним ключом , потому что он может ссылаться на любую таблицу, которую мы хотим.
Столбец entity_id также обычно не может быть уникальным, поскольку разные таблицы могут иметь повторяющиеся идентификаторы.
То entity_type / entity_id пара, однако, должна быть уникальной, поскольку она однозначно описывает сущность, на которую мы ссылаемся.
6.2. Определение сопоставления @Any С @AnyMetaDef
Прямо сейчас Hibernate не знает, как различать различные типы сущностей, потому что мы не указали, что может содержать столбец entity_type .
Чтобы сделать эту работу, нам нужно добавить мета-определение отображения с аннотацией @AnyMetaDef . Лучшим местом для его размещения был бы уровень пакета, чтобы мы могли повторно использовать его в других сопоставлениях.
Вот как package-info.java файл с аннотацией @AnyMetaDef будет выглядеть следующим образом:
@AnyMetaDef( name = "EntityDescriptionMetaDef", metaType = "string", idType = "int", metaValues = { @MetaValue(value = "Employee", targetEntity = Employee.class), @MetaValue(value = "Phone", targetEntity = Phone.class) } ) package com.baeldung.hibernate.pojo;
Здесь мы указали тип столбца entity_type ( string ), тип столбца entity_id ( int ), допустимые значения в столбце entity_type ( “Сотрудник” и “Телефон” ) и соответствующие типы сущностей.
Теперь предположим, что у нас есть сотрудник с двумя телефонами, описанными следующим образом:
Employee employee = new Employee(); Phone phone1 = new Phone("555-45-67"); Phone phone2 = new Phone("555-89-01"); employee.getPhones().add(phone1); employee.getPhones().add(phone2);
Теперь мы можем добавить описательные метаданные ко всем трем сущностям, даже если они имеют разные несвязанные типы:
EntityDescription employeeDescription = new EntityDescription( "Send to conference next year", employee); EntityDescription phone1Description = new EntityDescription( "Home phone (do not call after 10PM)", phone1); EntityDescription phone2Description = new EntityDescription( "Work phone", phone1);
7. Заключение
В этой статье мы рассмотрели некоторые аннотации Hibernate, которые позволяют точно настраивать отображение сущностей с помощью необработанного SQL.
Исходный код статьи доступен на GitHub .