«1. Обзор

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

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

2. Контекст персистентности

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

An EntityManager instance is associated with a persistence context. A persistence context is a set of entity instances in which for any persistent entity identity there is a unique entity instance. Within the persistence context, the entity instances and their lifecycle are managed. The EntityManager API is used to create and remove persistent entity instances, to find entities by their primary key, and to query over entities.

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

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

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

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

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

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

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

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

3.1 Контекст персистентности в области транзакции

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

transaction persistence context diagram

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

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

@PersistenceContext
private EntityManager entityManager;

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

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

extended persistence context diagram

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

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

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

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

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

Сначала создадим класс службы TransctionPersistenceContextUserService:

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

@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);
    }
}

5. Тестовые примеры

@Component
public class ExtendedPersistenceContextUserService {

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

    // Remaining code same as above
}

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

5.1 Тестирование контекста персистентности транзакции

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

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

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);

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

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

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

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

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);

Мы увидим EntityExistsException:

@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);
}

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

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);

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

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);

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

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

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

«