Рубрики
Без рубрики

Динамическое отображение с гибернацией

Исследуйте возможности динамического отображения Hibernate с помощью @Formula, @Where, @Filter и @Any аннотаций.

Автор оригинала: baeldung.

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 Set phones = 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);

List fullPhoneList 
  = 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));

Затем с включенным фильтром, как показано выше, только два из них будут видны при запросе:

List employees = 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 .