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

Лучший способ ленивой загрузки атрибутов сущностей с помощью JPA и гибернации

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

При извлечении сущности также будут загружены все атрибуты. Это связано с тем, что каждый атрибут сущности неявно помечен аннотацией @Basic , политикой выборки по умолчанию которой является FetchType.НЕТЕРПЕЛИВЫЙ .

Однако стратегия выборки атрибутов может быть установлена в FetchType.LAZY , в этом случае атрибут сущности загружается с помощью дополнительного оператора select при первом доступе.

@Basic(fetch = FetchType.LAZY)

Одной этой конфигурации недостаточно, поскольку для Hibernate требуется инструментарий байт-кода для перехвата запроса на доступ к атрибутам и выдачи дополнительной инструкции select по требованию.

При использовании плагина улучшения байт-кода Maven для свойства включить ленивую инициализацию конфигурации должно быть установлено значение true , как показано в следующем примере:


    org.hibernate.orm.tooling
    hibernate-enhance-maven-plugin
    ${hibernate.version}
    
        
            
                true
                true
            
            
                enhance
            
        
    

При наличии этой конфигурации все классы сущностей JPA будут оснащены функцией ленивой выборки атрибутов. Этот процесс происходит во время сборки, сразу после компиляции классов сущностей из связанных с ними исходных файлов.

Механизм ленивой выборки атрибутов очень полезен при работе с типами столбцов, в которых хранятся большие объемы данных (например, BLOB , CLOB , VARBINARY ). Таким образом, сущность может быть извлечена без автоматической загрузки данных из базовых типов больших столбцов, что повышает производительность.

Чтобы продемонстрировать, как работает отложенная выборка атрибутов, в следующем примере будет использоваться Вложение объект, который может хранить любой тип носителя (например, PNG, PDF, MPEG).

@Entity @Table(name = "attachment")
public class Attachment {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Enumerated
    @Column(name = "media_type")
    private MediaType mediaType;

    @Lob
    @Basic(fetch = FetchType.LAZY)
    private byte[] content;

    //Getters and setters omitted for brevity
}

Такие свойства, как идентификатор сущности, имя или тип носителя, должны быть с готовностью извлечены при каждой загрузке сущности. С другой стороны, содержимое медиафайла должно извлекаться лениво, только при доступе к нему с помощью кода приложения.

После инструментирования сущности Attachment байт-код класса изменяется следующим образом:

@Transient
private transient PersistentAttributeInterceptor 
    $$_hibernate_attributeInterceptor;

public byte[] getContent() {
    return $$_hibernate_read_content();
}

public byte[] $$_hibernate_read_content() {
    if ($$_hibernate_attributeInterceptor != null) {
        this.content = ((byte[]) 
            $$_hibernate_attributeInterceptor.readObject(
                this, "content", this.content));
    }
    return this.content;
}

Извлечение атрибута content выполняется перехватчиком постоянных атрибутов ссылки на объект, что позволяет загружать базовый столбец BLOB только при первом вызове средства получения.

При выполнении следующего тестового случая:

Attachment book = entityManager.find(
    Attachment.class, bookId);

LOGGER.debug("Fetched book: {}", book.getName());

assertArrayEquals(
    Files.readAllBytes(bookFilePath), 
    book.getContent()
);

Hibernate генерирует следующие SQL-запросы:

SELECT a.id AS id1_0_0_,
       a.media_type AS media_ty3_0_0_,
       a.name AS name4_0_0_
FROM   attachment a
WHERE  a.id = 1

-- Fetched book: High-Performance Java Persistence

SELECT a.content AS content2_0_
FROM   attachment a
WHERE  a.id = 1

Потому что он помечен типом FetchType.Включено расширение байт-кода с отложенной аннотацией и отложенной выборкой, столбец содержимое не извлекается вместе со всеми другими столбцами, инициализирующими объект Вложение . Только когда уровень доступа к данным пытается получить доступ к свойству content , Hibernate выдает дополнительный выбор для загрузки этого атрибута.

Точно так же, как FetchType.ЛЕНИВЫЕ ассоциации, этот метод подвержен N+1 проблемам с запросами , поэтому рекомендуется соблюдать осторожность. Одним из незначительных недостатков механизма улучшения байт-кода является то, что все свойства сущностей, а не только те, которые отмечены типом FetchType.LAZY аннотации будут преобразованы, как показано ранее.

Другой подход, позволяющий избежать загрузки довольно больших столбцов таблицы, заключается в сопоставлении нескольких вложенных объектов с одной и той же таблицей базы данных.

Как сущность Вложение , так и вложенная сущность Сводка вложений наследуют все общие атрибуты от Базового вложения суперкласса.

@MappedSuperclass
public class BaseAttachment {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Enumerated
    @Column(name = "media_type")
    private MediaType mediaType;

    //Getters and setters omitted for brevity
}

В то время как Краткое описание вложения расширяет Базовое вложение без объявления какого-либо нового атрибута:

@Entity @Table(name = "attachment")
public class AttachmentSummary 
    extends BaseAttachment {}

Сущность Вложение наследует все базовые атрибуты из суперкласса Базовое вложение и также отображает столбец содержимое .

@Entity @Table(name = "attachment")
public class Attachment 
    extends BaseAttachment {

    @Lob
    private byte[] content;

    //Getters and setters omitted for brevity
}

При извлечении Резюме вложения вложенной сущности:

AttachmentSummary bookSummary = entityManager.find(
    AttachmentSummary.class, bookId);

Сгенерированная инструкция SQL не будет извлекать столбец content :

SELECT a.id as id1_0_0_, 
       a.media_type as media_ty2_0_0_, 
       a.name as name3_0_0_ 
FROM attachment a 
WHERE  a.id = 1

Однако при извлечении объекта Вложение :

Attachment book = entityManager.find(
    Attachment.class, bookId);

Hibernate будет извлекать все столбцы из базовой таблицы базы данных:

SELECT a.id as id1_0_0_, 
       a.media_type as media_ty2_0_0_, 
       a.name as name3_0_0_, 
       a.content as content4_0_0_ 
FROM attachment a 
WHERE  a.id = 1

Для ленивой выборки атрибутов сущностей можно использовать либо расширение байт-кода, либо вложенные сущности. Хотя инструментарий байт-кода позволяет использовать только одну сущность в таблице, вложенные элементы более гибки и могут даже повысить производительность, поскольку они не требуют вызова перехватчика при чтении атрибута сущности.

Когда дело доходит до считывания данных, субъективные значения очень похожи на прогнозы DTO . Однако, в отличие от прогнозов DTO, субстанции могут отслеживать изменения состояния и передавать их в базу данных.