Как работает Hibernate.initialize() - программирование
Подтвердить что ты не робот

Как работает Hibernate.initialize()

Я знаю, что для использования лениво загружаемых объектов /colletions вне сеанса мы делаем Hibernate.initialize(Object obj), чтобы объект, который передан как аргумент для метода initialize(), инициализирован и может использоваться вне сферы действия сеанса.

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

Другими словами, я хочу знать разницу между использованием Hibernate.initialize() и eagerly загрузки этого объекта.

Неужели я ошибаюсь или что-то пропустил?

4b9b3361

Ответ 1

Разница в области применения.

Причиной создания ассоциации коллекции является ленивый, чтобы она не загружала коллекцию каждый раз, когда родительский объект загружается, если вам это действительно не нужно.

Если вы обычно выполняете ленивую загрузку коллекции, но для конкретного использования вам необходимо убедиться, что коллекция была загружена до закрытия сеанса, вы можете использовать Hibernate.initialize(Object obj), как вы отметили.

Если вы действительно всегда нуждаетесь в сборке, вы действительно должны загружать ее с нетерпением. Однако в большинстве программ это не так.

Ответ 2

Рассмотрим следующий пример:

У меня есть объект LoanApplication (который в этом случае является очень тяжелым объектом), который имеет в нем различные поля (что также может быть большим). Рассмотрим, например, поле SubLoan в LoanApplication.

@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "application_fk")
@Index(name = "appl_fk_idx_subloansLoanApp")
private Set<SubLoan> subLoans;

В этом примере FetchType LAZY. Теперь, если в каком-либо методе контроллера при выполнении некоторых операций вы сталкиваетесь с LoanApplication, набор subLoans изначально будет null, если вы не хотите его использовать. В этом случае вы используете Hibernate.initialize, как показано ниже:

Hibernate.initialize(loanApplication.getSubLoans());

Это помогает в основном повысить производительность, потому что каждый раз, когда вы извлекаете LoanApplication, большой набор объектов, то есть "subLoan" будет изначально пустым, если вы действительно этого не хотите.

Ответ 3

Рассмотрим ответ @Don Ruby

следующая разница заключается в том, что Hibernate.initialize генерирует и выполняет дополнительный sql для извлечения данных. Таким образом, вы можете использовать его после закрытия сессии. Когда вы используете Eager fetch в сущности, он всегда извлекает эти коллекции во время поиска данных (в сеансе соединения) в базе данных, но не после него.

Ответ 4

На самом деле, если вы используете EAGER, когда у вас большие коллекции, это действительно влияет на вашу производительность. Итак, это хорошая идея в ситуациях, подобных этому, используя Hibernate.initialize.

Взгляните на это: Hibernate Lazy Fetch vs Eager Тип выборки

Ответ 5

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

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

Ответ 6

Как я объяснил в этой статье, Hibernate.initialize(proxy) полезен, только если вы используете кэш второго уровня. В противном случае вы собираетесь выполнить вторичный запрос, который менее эффективен, чем простая инициализация прокси с начальным запросом.

Риск N + 1 вопросов

Итак, при выполнении следующего теста:

LOGGER.info("Очистить кэш второго уровня");

entityManager.getEntityManagerFactory().getCache().evictAll();

LOGGER.info("Loading a PostComment");

PostComment comment = entityManager.find(
    PostComment.class,
    1L
);

assertEquals(
    "A must read!",
    comment.getReview()
);

Post post = comment.getPost();

LOGGER.info("Post entity class: {}", post.getClass().getName());

Hibernate.initialize(post);

assertEquals(
    "High-Performance Java Persistence",
    post.getTitle()
);

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

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

-- Clear the second-level cache

-- Evicting entity cache: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post
-- Evicting entity cache: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment

-- Loading a PostComment

SELECT pc.id AS id1_1_0_,
       pc.post_id AS post_id3_1_0_,
       pc.review AS review2_1_0_
FROM   post_comment pc
WHERE  pc.id=1

-- Post entity class: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post$HibernateProxy$5LVxadxF

SELECT p.id AS id1_0_0_,
       p.title AS title2_0_0_
FROM   post p
WHERE  p.id=1

Мы можем видеть, что кэш второго уровня был правильно удален и что после извлечения сущности PostComment сущность post представлена экземпляром HibernateProxy который содержит только идентификатор сущности Post, извлеченный из столбца post_id строки таблицы базы данных post_comment,

Теперь из-за вызова метода Hibernate.initialize выполняется вторичный SQL-запрос для извлечения сущности Post, что не очень эффективно и может привести к проблемам с N + 1 запросами.

Использование JOIN FETCH с JPQL

В предыдущем случае PostComment следует извлекать вместе с его пост-ассоциацией, используя директиву JOIN FETCH JPQL.

LOGGER.info("Clear the second-level cache");

entityManager.getEntityManagerFactory().getCache().evictAll();

LOGGER.info("Loading a PostComment");

PostComment comment = entityManager.createQuery(
    "select pc " +
    "from PostComment pc " +
    "join fetch pc.post " +
    "where pc.id = :id", PostComment.class)
.setParameter("id", 1L)
.getSingleResult();

assertEquals(
    "A must read!",
    comment.getReview()
);

Post post = comment.getPost();

LOGGER.info("Post entity class: {}", post.getClass().getName());

assertEquals(
    "High-Performance Java Persistence",
    post.getTitle()
);

На этот раз Hibernate выполняет одну инструкцию SQL, и мы больше не рискуем столкнуться с проблемами N + 1 запросов:

-- Clear the second-level cache

-- Evicting entity cache: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post
-- Evicting entity cache: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment

-- Loading a PostComment

SELECT pc.id AS id1_1_0_,
       p.id AS id1_0_1_,
       pc.post_id AS post_id3_1_0_,
       pc.review AS review2_1_0_,
       p.title AS title2_0_1_
FROM   post_comment pc
INNER JOIN post p ON pc.post_id=p.id
WHERE  pc.id=1

-- Post entity class: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post

Использование кеша 2-го уровня с Hibernate.initialize

Итак, чтобы увидеть, когда Hibernate.initialize действительно стоит использовать, вам нужно использовать кэш второго уровня:

LOGGER.info("Loading a PostComment");

PostComment comment = entityManager.find(
    PostComment.class,
    1L
);

assertEquals(
    "A must read!",
    comment.getReview()
);

Post post = comment.getPost();

LOGGER.info("Post entity class: {}", post.getClass().getName());

Hibernate.initialize(post);

assertEquals(
    "High-Performance Java Persistence",
    post.getTitle()
);

На этот раз мы больше не высвобождаем области кэша второго уровня, и, поскольку мы используем стратегию параллельного кэширования READ_WRITE, объекты кэшируются сразу после того, как они сохраняются, поэтому при выполнении теста не требуется выполнение запроса SQL случай выше:

-- Loading a PostComment

-- Cache hit : 
region = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment', 
key = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment#1'

-- Proxy class: com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post$HibernateProxy$rnxGtvMK

-- Cache hit : 
region = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post', 
key = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post#1'

И PostComment и ассоциация post извлекаются из кэша второго уровня, как показано в сообщениях журнала попаданий в кэш.

Таким образом, если вы используете кэш второго уровня, можно использовать Hibernate.initiaize для получения дополнительных ассоциаций, необходимых для выполнения вашего бизнес-сценария. В этом случае, даже если у вас N + 1 кеш-вызовов, каждый вызов должен выполняться очень быстро, поскольку кеш второго уровня настроен правильно и данные возвращаются из памяти.

Hibernate.initialize и коллекция прокси

Hibernate.initialize можно использовать и для коллекций. Теперь, потому что коллекции кэша второго уровня являются читаемыми, это означает, что они сохраняются в кэше при первой загрузке при выполнении следующего контрольного примера:

LOGGER.info("Loading a Post");

Post post = entityManager.find(
    Post.class,
    1L
);

List<PostComment> comments = post.getComments();

LOGGER.info("Collection class: {}", comments.getClass().getName());

Hibernate.initialize(comments);

LOGGER.info("Post comments: {}", comments);

Hibernate выполняет SQL-запрос для загрузки коллекции PostComment:

-- Loading a Post

-- Cache hit : 
region = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post', 
key = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post#1'

-- Collection class: org.hibernate.collection.internal.PersistentBag

- Cache hit, but item is unreadable/invalid : 
region = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post.comments', 
key = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post.comments#1'

SELECT pc.post_id AS post_id3_1_0_,
       pc.id AS id1_1_0_,
       pc.id AS id1_1_1_,
       pc.post_id AS post_id3_1_1_,
       pc.review AS review2_1_1_
FROM   post_comment pc
WHERE  pc.post_id=1

-- Post comments: [
    PostComment{id=1, review='A must read!'}, 
    PostComment{id=2, review='Awesome!'}, 
    PostComment{id=3, review='5 stars'}
]

Однако, если коллекция PostComment уже кэширована:

doInJPA(entityManager -> {
    Post post = entityManager.find(Post.class, 1L);

    assertEquals(3, post.getComments().size());
});

При запуске предыдущего тестового примера Hibernate может извлечь все данные только из кэша:

-- Loading a Post

-- Cache hit : 
region = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post', 
key = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post#1'

-- Collection class: org.hibernate.collection.internal.PersistentBag

-- Cache hit : 
region = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post.comments', 
key = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$Post.comments#1'

-- Cache hit : 
region = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment', 
key = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment#1'

-- Cache hit : 
region = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment', 
key = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment#2'

-- Cache hit : 
region = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment', 
key = 'com.vladmihalcea.book.hpjp.hibernate.fetching.HibernateInitializeTest$PostComment#3'

-- Post comments: [
    PostComment{id=1, review='A must read!'}, 
    PostComment{id=2, review='Awesome!'}, 
    PostComment{id=3, review='5 stars'}
]