1. Обзор
Поставщики сохраняемости, такие как Hibernate, используют контекст сохраняемости для управления жизненным циклом объекта в приложении.
В этом руководстве мы начнем с введения в контекст сохраняемости, а затем увидим, почему это важно. Наконец, мы увидим разницу между контекстом сохраняемости в области транзакции и контекстом сохраняемости в расширенной области на примерах.
2. Контекст сохранения
Давайте взглянем на официальное определение Persistence Context :
Экземпляр EntityManager связан с контекстом сохраняемости. Контекст персистентности — это набор экземпляров сущности, в котором для любой идентичности персистентной сущности существует уникальный экземпляр сущности. В контексте постоянства осуществляется управление экземплярами сущностей и их жизненным циклом. API EntityManager используется для создания и удаления экземпляров постоянных сущностей, поиска сущностей по их первичному ключу и запроса сущностей.
Вышеупомянутое утверждение может показаться немного сложным прямо сейчас, но оно обретет смысл, когда мы продолжим. Контекст персистентности — это кеш первого уровня, в котором все сущности извлекаются из базы данных или сохраняются в ней . Он находится между нашим приложением и постоянным хранилищем.
Контекст сохраняемости отслеживает любые изменения, внесенные в управляемый объект. Если во время транзакции что-то меняется, сущность помечается как грязная. Когда транзакция завершается, эти изменения сбрасываются в постоянное хранилище.
EntityManager
— это интерфейс, который позволяет нам взаимодействовать с контекстом персистентности. Всякий раз, когда мы используем EntityManager
, мы фактически взаимодействуем с контекстом персистентности .
Если каждое изменение, сделанное в сущности, вызывает обращение к постоянному хранилищу, мы можем представить, сколько вызовов будет сделано. Это приведет к снижению производительности, поскольку вызовы постоянного хранилища обходятся дорого.
3. Тип контекста постоянства
Контексты персистентности доступны двух типов:
- Контекст сохраняемости в области транзакции
- Контекст сохраняемости с расширенной областью действия
Давайте посмотрим на каждый.
3.1. Контекст сохраняемости в области транзакции
Контекст сохранения транзакции привязан к транзакции. Как только транзакция завершится, сущности, присутствующие в контексте постоянства, будут сброшены в постоянное хранилище.
Когда мы выполняем любую операцию внутри транзакции, EntityManager
проверяет наличие контекста постоянства .
Если он существует, то он будет использован. В противном случае он создаст контекст сохранения.
Тип контекста сохраняемости по умолчанию —
PersistenceContextType.TRANSACTION
. Чтобы указать EntityManager
использовать контекст сохранения транзакции, мы просто аннотируем его с помощью @PersistenceContext
:
@PersistenceContext
private EntityManager entityManager;
3.2. Контекст персистентности с расширенной областью действия
Расширенный контекст сохраняемости может охватывать несколько транзакций. Мы можем сохранить сущность без транзакции, но не можем сбросить ее без транзакции.
Чтобы указать 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 .