Рубрики
Без рубрики

Оптимизация гибернации транзакций только для чтения весной

Автор оригинала: 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 для запроса следующим образом:

List posts = 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 GenericDAOImpl 
        implements 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 List findAllByTitle(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());

    List posts = 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 только для чтения для гибернации заключается в том, что мы можем сэкономить много памяти при загрузке объектов, доступных только для чтения, поскольку загруженное состояние сразу отбрасывается, а не сохраняется в течение всего времени текущего контекста сохранения.