Автор оригинала: Vlad Mihalcea.
В этой статье я собираюсь объяснить, как работает оптимизация спящего режима транзакций только для чтения Spring.
Взглянув на то, что делает Spring framework при включении атрибута Только для чтения
в аннотации @Transactional
, я понял, что только режим спящего режима установлен в FlushType.ВРУЧНУЮ
без дальнейшего распространения флага “только для чтения” на сеанс гибернации |.
Итак, в истинном духе разработчика программного обеспечения с открытым исходным кодом, я решил, что пришло время внести изменения.
Сегодня хороший день, чтобы внести свой вклад в #Гибернацию оптимизацию @springframework
Как работает весенняя транзакция только для чтения #Hibernate оптимизация. @vlad_mihalcea https://t.co/ff5qo2Znoo pic.twitter.com/S3Od6JiD7d
При загрузке объекта Hibernate извлекает загруженное состояние из базового набора результатов JDBC /. Этот процесс называется гидратацией по терминологии Hibernate и выполняется Hibernate
EntityPersister следующим образом:
final Object[] values = persister.hydrate( rs, id, object, rootPersister, cols, fetchAllPropertiesRequested, session );
Загруженное состояние или увлажненное состояние необходимо механизму проверки загрязненности для сравнения текущего состояния объекта с моментальным снимком во время загрузки и определения необходимости выполнения инструкции UPDATE
во время сброса. Кроме того, загруженное состояние используется механизмом блокировки менее оптимистичной версии для построения предикатов фильтрации предложений WHERE.
Поэтому при загрузке объекта загруженное состояние сохраняется в режиме гибернации сеанса
, если объект не загружен в режиме только для чтения.
По умолчанию сущности загружаются в режиме чтения-записи, что означает, что загруженное состояние сохраняется текущим контекстом сохранения до тех пор, пока сущность не будет загружена или если JPA EntityManager
или Спящий режим Сеанс
закрыт.
Чтобы загружать объекты в режиме только для чтения, вы можете установить флаг только для чтения по умолчанию
на уровне Сеанса
или задать подсказку запроса org.hibernate.Только для чтения
JPA.
Чтобы установить режим только для чтения для всех объектов, загруженных в режиме гибернации сеанса
либо с помощью запроса, либо с помощью прямой выборки, вам необходимо включить свойство defaultReadOnly
, подобное этому:
Session session = entityManager.unwrap(Session.class); session.setDefaultReadOnly(true);
Или, если у вас есть сеанс записи для чтения
по умолчанию и вы хотите загружать объекты только в режиме только для чтения
для определенного запроса, вы можете использовать подсказку org.hibernate.readOnly
JPA для запроса следующим образом:
Listposts = entityManager .createQuery( "select p from Post p", Post.class) .setHint(QueryHints.HINT_READONLY, true) .getResultList();
Spring, как и Java EE, предлагает поддержку декларативных транзакций. Поэтому вы можете использовать @Транзакционную
аннотацию для обозначения метода уровня обслуживания, который должен быть заключен в транзакционный контекст.
Аннотация @Транзакционная
предлагает атрибут Только для чтения
, который по умолчанию false
. Атрибут Только для чтения
может быть дополнительно использован Spring для оптимизации операций базового уровня доступа к данным.
До версии Spring 5.1 при использовании режима гибернации атрибут Только для чтения
аннотации @Transactional
устанавливал только текущий Сеанс
режим промывки в Тип промывки.ВРУЧНУЮ
, поэтому отключается механизм автоматической проверки на загрязнение.
Однако, поскольку атрибут Только для чтения
не распространился на базовый режим гибернации Сеанс
, я решил создать проблему SPR-16956 и также предоставил Запрос на извлечение , который после Юргенизации был интегрирован и доступен , начиная с Spring Framework 5.1 .
Давайте рассмотрим, что в нашем приложении есть следующие классы уровня обслуживания и доступа к данным:
Класс Post DAO Impl
реализован следующим образом:
@Repository public class PostDAOImpl extends GenericDAOImplimplements PostDAO { protected PostDAOImpl() { super(Post.class); } @Override public List findByTitle(String title) { return getEntityManager() .createQuery( "select p " + "from Post p " + "where p.title = :title", Post.class) .setParameter("title", title) .getResultList(); } }
В то время как ForumServiceImpl
выглядит следующим образом:
@Service public class ForumServiceImpl implements ForumService { @Autowired private PostDAO postDAO; @Autowired private TagDAO tagDAO; @PersistenceContext private EntityManager entityManager; @Override @Transactional public Post newPost(String title, String... tags) { Post post = new Post(); post.setTitle(title); post.getTags().addAll(tagDAO.findByName(tags)); return postDAO.persist(post); } @Override @Transactional(readOnly = true) public ListfindAllByTitle(String title) { List posts = postDAO.findByTitle(title); org.hibernate.engine.spi.PersistenceContext persistenceContext = getHibernatePersistenceContext(); for(Post post : posts) { assertTrue(entityManager.contains(post)); EntityEntry entityEntry = persistenceContext.getEntry(post); assertNull(entityEntry.getLoadedState()); } return posts; } @Override @Transactional public Post findById(Long id) { Post post = postDAO.findById(id); org.hibernate.engine.spi.PersistenceContext persistenceContext = getHibernatePersistenceContext(); EntityEntry entityEntry = persistenceContext.getEntry(post); assertNotNull(entityEntry.getLoadedState()); return post; } private org.hibernate.engine.spi.PersistenceContext getHibernatePersistenceContext() { SharedSessionContractImplementor session = entityManager.unwrap( SharedSessionContractImplementor.class ); return session.getPersistenceContext(); } }
Нас интересуют методы обслуживания findAllByTitle
и findById
.
Обратите внимание, что метод findAllByTitle
снабжен аннотацией @Транзакционный(только для чтения)
. При загрузке Post
сущностей , соответствующих заданному названию
, Hibernate будет извлекать сущности в режиме только для чтения, поэтому отбрасывает загруженное состояние, которое мы можем проверить с помощью Hibernate PersistenceContext
.
С другой стороны, метод findById
использует аннотацию для чтения и записи по умолчанию @Транзакционный
, и мы видим, что Hibernate PersistenceContext
содержит загруженное состояние текущей извлеченной Записи
сущности.
При запуске теста, подтверждающего эту новую оптимизацию спящего режима Spring 5.1, все работает так, как ожидалось:
@Test public void test() { Post newPost = forumService.newPost( "High-Performance Java Persistence", "hibernate", "jpa" ); assertNotNull(newPost.getId()); Listposts = forumService.findAllByTitle( "High-Performance Java Persistence" ); assertEquals(1, posts.size()); Post post = forumService.findById(newPost.getId()); assertEquals( "High-Performance Java Persistence", post.getTitle() ); }
Круто, правда?
Основное преимущество оптимизации Spring 5.1 только для чтения для гибернации заключается в том, что мы можем сэкономить много памяти при загрузке объектов, доступных только для чтения, поскольку загруженное состояние сразу отбрасывается, а не сохраняется в течение всего времени текущего контекста сохранения.