1. Обзор
Поставщики сохраняемости, такие как Hibernate, используют контекст сохранения для управления жизненным циклом сущности в приложении.
В этом уроке мы начнем с введения контекста сохранения, а затем посмотрим, почему это важно. Наконец, мы увидим разницу между контекстом сохранения в области транзакций и контекстом сохранения в расширенной области с примерами.
2. Контекст сохранения
Давайте взглянем на официальное определение Контекста сохранения :
Экземпляр EntityManager связан с контекстом сохранения. Контекст сохранения-это набор экземпляров сущности, в котором для любого постоянного идентификатора сущности существует уникальный экземпляр сущности. В контексте сохранения экземплярами сущностей и их жизненным циклом управляют. API EntityManager используется для создания и удаления постоянных экземпляров сущностей, поиска сущностей по их первичному ключу и выполнения запросов к сущностям.
Приведенное выше утверждение может показаться немного сложным прямо сейчас, но оно будет иметь полный смысл, когда мы продолжим. Контекст сохранения-это кэш первого уровня, в котором все сущности извлекаются из базы данных или сохраняются в базе данных . Он находится между вашим приложением и постоянным хранилищем.
Контекст сохранения отслеживает любые изменения, внесенные в управляемую сущность. Если что-либо меняется во время транзакции, то объект помечается как грязный. Когда транзакция завершается, эти изменения удаляются в постоянное хранилище.
EntityManager – это интерфейс, который позволяет нам взаимодействовать с контекстом сохранения. Всякий раз , когда мы используем EntityManager , мы фактически взаимодействуем с контекстом сохранения .
Если каждое изменение, внесенное в сущность, вызывает постоянное хранилище, мы можем представить, сколько вызовов будет сделано. Это приведет к снижению производительности, поскольку постоянные вызовы хранилища стоят дорого.
3. Тип контекста Сохранения
Контексты сохранения доступны в двух типах:
- Контекст сохранения в области транзакций
- Контекст сохранения с расширенной областью действия
Давайте взглянем на каждого из них.
3.1 Контекст сохранения в области транзакций
Контекст сохранения транзакции привязан к транзакции. Как только транзакция завершится, сущности, присутствующие в контексте сохранения, будут сброшены в постоянное хранилище.
Когда мы выполняем какую-либо операцию внутри транзакции, EntityManager проверяет наличие контекста сохранения . Если он существует, то он будет использоваться. В противном случае это создаст контекст сохранения.
По умолчанию типом контекста сохранения является |/PersistenceContextType.ТРАНЗАКЦИЯ . Чтобы указать EntityManager использовать контекст сохранения транзакций, мы просто аннотируем его с помощью @PersistenceContext :
@PersistenceContext private EntityManager entityManager;
3.2 Контекст сохранения с расширенной областью действия
Расширенный контекст сохранения может охватывать несколько транзакций. Мы можем сохранить сущность без транзакции, но не можем удалить ее без транзакции.
Чтобы указать EntityManager использовать контекст сохранения с расширенной областью действия, нам нужно применить тип атрибут @PersistenceContext :
@PersistenceContext(type = PersistenceContextType.EXTENDED) private EntityManager entityManager;
В компоненте сеанса без сохранения состояния контекст расширенного сохранения в одном компоненте полностью не знает о каком-либо контексте сохранения другого компонента . Это верно, даже если оба находятся в одной и той же транзакции.
Допустим , мы сохраняем некоторую сущность в методе компонента A , который выполняется в транзакции. Затем мы вызываем некоторый метод компонента B . В контексте сохранения метода компонента B мы не найдем сущность, которую мы сохраняли ранее в методе компонента A .
4. Пример Контекста Сохранения
Теперь, когда мы достаточно знаем о контексте сохранения, пришло время погрузиться в пример. Мы рассмотрим различные варианты использования с контекстом сохранения транзакций и расширенным контекстом сохранения.
Во-первых, давайте создадим наш класс обслуживания, Контекстный пользовательский сервис сохранения транзакций :
@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); } }
Следующий класс, Расширенная служба пользователя PersistenceContext , очень похож на описанный выше, за исключением аннотации @PersistenceContext . На этот раз мы передаем PersistenceContextType.РАСШИРЕНО в тип параметр его @PersistenceContext аннотации:
@Component public class ExtendedPersistenceContextUserService { @PersistenceContext(type = PersistenceContextType.EXTENDED) private EntityManager entityManager; // Remaining code same as above }
5. Тестовые примеры
Теперь, когда у нас есть настроенные классы обслуживания, пришло время использовать различные варианты использования с контекстом сохранения транзакций и расширенным контекстом сохранения.
5.1 Тестирование Контекста Сохранения транзакций
Давайте сохраним сущность User , используя контекст сохранения в области транзакций. Объект будет сохранен в постоянном хранилище. Затем мы проверяем, выполнив вызов 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);
Когда мы попытаемся вставить Пользователя сущность без транзакции, будет выдано исключение TransactionRequiredException :
@Test(expected = TransactionRequiredException.class) public void testThatUserSaveWithoutTransactionThrowException() { User user = new User(122L, "Devender", "admin"); transctionPersistenceContext.insertWithoutTransaction(user); }
5.2 Тестирование Расширенного Контекста Сохранения
Далее, давайте сохраним пользователя с расширенным контекстом сохранения и без транзакции. Сущность 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);
В контексте сохранения для любого постоянного идентификатора сущности будет существовать уникальный экземпляр сущности. Если мы попытаемся сохранить другую сущность с тем же идентификатором:
@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 .