Перейти к основному содержимому

Контекст сохраняемости JPA/гибернации

· 5 мин. чтения

1. Обзор

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

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

2. Контекст сохранения

Давайте взглянем на официальное определение Persistence Context :

Экземпляр EntityManager связан с контекстом сохраняемости. Контекст персистентности — это набор экземпляров сущности, в котором для любой идентичности персистентной сущности существует уникальный экземпляр сущности. В контексте постоянства осуществляется управление экземплярами сущностей и их жизненным циклом. API EntityManager используется для создания и удаления экземпляров постоянных сущностей, поиска сущностей по их первичному ключу и запроса сущностей.

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

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

EntityManager — это интерфейс, который позволяет нам взаимодействовать с контекстом персистентности. Всякий раз, когда мы используем EntityManager , мы фактически взаимодействуем с контекстом персистентности .

Если каждое изменение, сделанное в сущности, вызывает обращение к постоянному хранилищу, мы можем представить, сколько вызовов будет сделано. Это приведет к снижению производительности, поскольку вызовы постоянного хранилища обходятся дорого.

3. Тип контекста постоянства

Контексты персистентности доступны двух типов:

  • Контекст сохраняемости в области транзакции
  • Контекст сохраняемости с расширенной областью действия

Давайте посмотрим на каждый.

3.1. Контекст сохраняемости в области транзакции

Контекст сохранения транзакции привязан к транзакции. Как только транзакция завершится, сущности, присутствующие в контексте постоянства, будут сброшены в постоянное хранилище.

./96416c2c218f9671d6ef7ce2506fb266.png

Когда мы выполняем любую операцию внутри транзакции, EntityManager проверяет наличие контекста постоянства . Если он существует, то он будет использован. В противном случае он создаст контекст сохранения.

Тип контекста сохраняемости по умолчанию PersistenceContextType.TRANSACTION . Чтобы указать EntityManager использовать контекст сохранения транзакции, мы просто аннотируем его с помощью @PersistenceContext :

@PersistenceContext
private EntityManager entityManager;

3.2. Контекст персистентности с расширенной областью действия

Расширенный контекст сохраняемости может охватывать несколько транзакций. Мы можем сохранить сущность без транзакции, но не можем сбросить ее без транзакции.

./f8539d224e6c0ee976045a28cc518981.png

Чтобы указать EntityManager использовать контекст персистентности с расширенной областью действия, нам нужно применить атрибут type @PersistenceContext :

@PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;

В сеансовом компоненте без сохранения состояния расширенный контекст сохранения в одном компоненте совершенно не знает о каком-либо контексте сохранения состояния другого компонента . Это верно, даже если оба находятся в одной и той же транзакции.

Допустим, мы сохраняем некоторую сущность в методе Компонента А , который выполняется в транзакции. Затем мы вызываем некоторый метод компонента B . В контексте сохранения метода компонента B мы не найдем объект, который мы ранее сохранили в методе компонента A.

4. Пример контекста персистентности

Теперь, когда мы достаточно знаем о контексте персистентности, пришло время погрузиться в пример. Мы рассмотрим разные варианты использования контекста сохраняемости транзакций и расширенного контекста сохраняемости.

Во-первых, давайте создадим наш класс службы TransctionPersistenceContextUserService :

@Component
public class TransctionPersistenceContextUserService {

@PersistenceContext
private EntityManager entityManager;

@Transactional
public User insertWithTransaction(User user) {
entityManager.persist(user);
return user;
}

public User insertWithoutTransaction(User user) {
entityManager.persist(user);
return user;
}

public User find(long id) {
return entityManager.find(User.class, id);
}
}

Следующий класс, ExtendedPersistenceContextUserService , очень похож на предыдущий, за исключением аннотации @PersistenceContext . На этот раз мы передаем PersistenceContextType.EXTENDED в параметр типа его аннотации @PersistenceContext :

@Component
public class ExtendedPersistenceContextUserService {

@PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;

// Remaining code same as above
}

5. Тестовые случаи

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

5.1. Тестирование контекста сохранения транзакции

Давайте сохраним сущность пользователя , используя контекст сохраняемости в области транзакции. Сущность будет сохранена в постоянном хранилище. Затем мы проверяем, выполнив вызов find с помощью EntityManager нашего расширенного контекста персистентности :

User user = new User(121L, "Devender", "admin");
transctionPersistenceContext.insertWithTransaction(user);

User userFromTransctionPersistenceContext = transctionPersistenceContext
.find(user.getId());
assertNotNull(userFromTransctionPersistenceContext);

User userFromExtendedPersistenceContext = extendedPersistenceContext
.find(user.getId());
assertNotNull(userFromExtendedPersistenceContext);

Когда мы попытаемся вставить сущность User без транзакции, будет выброшено TransactionRequiredException :

@Test(expected = TransactionRequiredException.class)
public void testThatUserSaveWithoutTransactionThrowException() {
User user = new User(122L, "Devender", "admin");
transctionPersistenceContext.insertWithoutTransaction(user);
}

5.2. Тестирование расширенного сохраняемого контекста

Далее давайте сохраним пользователя с расширенным контекстом сохранения и без транзакции. Сущность пользователя будет сохранена в контексте постоянства (кэш), но не в постоянном хранилище:

User user = new User(123L, "Devender", "admin");
extendedPersistenceContext.insertWithoutTransaction(user);

User userFromExtendedPersistenceContext = extendedPersistenceContext
.find(user.getId());
assertNotNull(userFromExtendedPersistenceContext);

User userFromTransctionPersistenceContext = transctionPersistenceContext
.find(user.getId());
assertNull(userFromTransctionPersistenceContext);

В контексте персистентности для любого персистентного идентификатора объекта будет уникальный экземпляр объекта. Если мы попытаемся сохранить другую сущность с тем же идентификатором:

@Test(expected = EntityExistsException.class)
public void testThatPersistUserWithSameIdentifierThrowException() {
User user1 = new User(126L, "Devender", "admin");
User user2 = new User(126L, "Devender", "admin");
extendedPersistenceContext.insertWithoutTransaction(user1);
extendedPersistenceContext.insertWithoutTransaction(user2);
}

Мы увидим EntityExistsException :

javax.persistence.EntityExistsException: 
A different object with the same identifier value
was already associated with the session

Расширенный контекст сохраняемости внутри транзакции сохраняет объект в постоянном хранилище в конце транзакции:

User user = new User(127L, "Devender", "admin");
extendedPersistenceContext.insertWithTransaction(user);

User userFromDB = transctionPersistenceContext.find(user.getId());
assertNotNull(userFromDB);

Расширенный контекст сохраняемости сбрасывает кэшированные объекты в постоянное хранилище при использовании в транзакции . Во-первых, мы сохраняем сущность без транзакции. Затем мы сохраняем еще одну сущность в транзакции:

User user1 = new User(124L, "Devender", "admin");
extendedPersistenceContext.insertWithoutTransaction(user1);

User user2 = new User(125L, "Devender", "admin");
extendedPersistenceContext.insertWithTransaction(user2);

User user1FromTransctionPersistenceContext = transctionPersistenceContext
.find(user1.getId());
assertNotNull(user1FromTransctionPersistenceContext);

User user2FromTransctionPersistenceContext = transctionPersistenceContext
.find(user2.getId());
assertNotNull(user2FromTransctionPersistenceContext);

6. Заключение

В этом руководстве мы получили хорошее представление о контексте сохраняемости.

Во-первых, мы рассмотрели контекст сохраняемости транзакций, который существует на протяжении всего жизненного цикла транзакции. Затем мы рассмотрели расширенный контекст сохраняемости, который может охватывать несколько транзакций.

Как всегда, пример кода доступен на GitHub .