JPA и кеш первого уровня, какой смысл?
EntityManager поддерживает кэш первого уровня для извлеченных объектов, но если вы хотите иметь поточно-ориентированное приложение, вы создаете и закрываете entityManager для каждой транзакции.
Так какой смысл кеша уровня 1, если эти сущности создаются и закрываются для каждой транзакции? Или кэш entityManager можно использовать, если вы работаете в одном потоке?
2 ответа
Кэш первого уровня служит для других целей. В основном это контекст, в котором JPA размещает объекты, извлеченные из базы данных.
Спектакль
Таким образом, чтобы начать утверждать очевидное, он избегает необходимости извлекать запись, когда она уже извлечена, служа некоторой формой кэша во время обработки транзакции и повышая производительность. Также подумайте о ленивой загрузке. Как вы могли бы реализовать это без кэша для записи объектов, которые уже загружены лениво?
Циклические Отношения
Эта цель кэширования имеет жизненно важное значение для реализации соответствующих структур ORM. В объектно-ориентированных языках распространено, что граф объектов имеет циклические отношения. Например, отдел с объектами Employee и объектами Employee принадлежит отделу.
Без контекста (он же " Единица работы") было бы трудно отследить, какие записи у вас уже есть ORMed, и вы в конечном итоге создадите новые объекты, и в таком случае вы можете даже оказаться в бесконечном цикле.
Отслеживание изменений: фиксация и откат
Кроме того, этот контекст отслеживает изменения, которые вы вносите в объекты, чтобы их можно было сохранить или откатить в более поздний момент, когда транзакция завершится. Без кеша, подобного этому, вы были бы вынуждены немедленно сбросить изменения в базу данных по мере их появления, а затем вы не смогли бы выполнить откат, а также не могли бы оптимизировать наилучший момент для их сброса в хранилище.
Идентичность объекта
Идентичность объекта также имеет жизненно важное значение в рамках ORM. То есть, если вы получите идентификатор сотрудника 123, то, если вам когда-нибудь понадобится этот сотрудник, вы всегда должны получать один и тот же объект, а не какой-то новый объект, содержащий те же данные.
Этот тип кэша не должен совместно использоваться несколькими потоками, в противном случае вы бы снизили производительность и заставили бы всех платить этот штраф, даже если они могут просто подойти с однопоточным решением. Помимо того факта, что вы получите гораздо более сложное решение, которое будет похоже на убийство мухи с помощью базуки.
Вот почему, если вам нужен общий кеш, то вам нужен кеш 2-го уровня, и для этого также есть реализации.
Дело в том, чтобы иметь приложение, которое работает так, как вы ожидаете, и оно не будет медленным. Давайте возьмем пример:
Order order = em.find(Order.class, 3L);
Customer customer = em.find(Customer.class, 5L);
for (Order o : customer.getOrders()) { // line A
if (o.getId().longValue == 3L) {
o.setComment("hello"); // line B
o.setModifier("John");
}
}
System.out.println(order.getComment)); // line C
for (Order o : customer.getOrders()) { // line D
System.out.println(o.getComment()); // line E
}
В строке A JPA выполняет SQL-запрос для загрузки всех заказов клиента.
Что вы ожидаете напечатать в строке C? null
или же "hello"
? Вы ожидаете, что "привет" будет напечатан, потому что порядок, который вы изменили в строке B, имеет тот же идентификатор, что и загруженный в первой строке. Это было бы невозможно без кэша первого уровня.
В строке D вы не ожидаете, что заказы будут снова загружены из базы данных, потому что они уже были загружены в строке A. Это было бы невозможно без кэша первого уровня.
В строке E вы ожидаете, что снова будет напечатано "привет" для заказа 3. Это было бы невозможно без кэша первого уровня.
В строке B вы не ожидаете, что запрос на обновление будет выполнен, потому что может быть много последующих модификаций (как в следующей строке) для той же сущности. Таким образом, вы ожидаете, что эти изменения будут записаны в базу данных как можно позже, все за один раз, в конце транзакции. Это было бы невозможно без кэша первого уровня.