Результаты запроса как поток с Hibernate 5.2

С Hibernate 5.2 мы можем использовать метод stream() вместо scroll(), если хотим извлечь большой объем данных.

Однако при использовании scroll() с ScrollableResults мы можем подключиться к поисковому процессу и освободить память путем выселения объекта из постоянного контекста после его обработки и/или очистки всего сеанса каждый раз и затем.

Мои вопросы:

  • Теперь, если мы используем метод stream(), что происходит за кулисами?
  • Можно ли вывести объект из постоянного контекста?
  • Периодически очищается сеанс?
  • Как достигается оптимальное потребление памяти?
  • Можно использовать, например. StatelessSession?
  • Кроме того, если мы установили hibernate.jdbc.fetch_size на некоторое число (например, 1000) по свойствам JPA, то как это хорошо сочетается с прокручиваемыми результатами?

Ответ 1

Для меня работает следующее:

DataSourceConfig.java

@Bean
public LocalSessionFactoryBean sessionFactory() {
    // Link your data source to your session factory
    ...
}

@Bean("hibernateTxManager")
public HibernateTransactionManager hibernateTxManager(@Qualifier("sessionFactory") SessionFactory sessionFactory) {
    // Link your session factory to your transaction manager
    ...
}

MyServiceImpl.java

@Service
@Transactional(propagation = Propagation.REQUIRES_NEW, transactionManager = "hibernateTxManager", readOnly = true)
public class MyServiceImpl implements MyService {

    @Autowired
    private MyRepo myRepo;
    ...
    Stream<MyEntity> stream = myRepo.getStream();
    // Do your streaming and CLOSE the steam afterwards
    ...

MyRepoImpl.java

@Repository
@Transactional(propagation = Propagation.MANDATORY, transactionManager = "hibernateTxManager", readOnly = true)
public class MyRepoImpl implements MyRepo {

    @Autowired
    private SessionFactory sessionFactory;

    @Autowired
    private MyDataSource myDataSource;

    public Stream<MyEntity> getStream() {

        return sessionFactory.openStatelessSession(DataSourceUtils.getConnection(myDataSource))
            .createNativeQuery("my_query", MyEntity.class)
            .setReadOnly(true)
            .setFetchSize(1000)
            .stream();
    }
    ...

Просто помните, что при потоке вы действительно должны быть осторожны с памятью в момент материализации объекта. Это действительно единственная часть операции, восприимчивая к проблемам в памяти. В моем случае я разбиваю поток на 1000 объектов за один раз, сериализую их с помощью gson и немедленно отправляю их в JMS-брокер. Остальное делает сборщик мусора.

Стоит отметить, что Spring осведомленность о границах транзакций закрывает соединение с дБ в конце, без необходимости явно указывать.

Ответ 2

Руководство пользователя Hibernate ORM указывает, что

Внутренне поток() ведет себя как прокрутка запроса #, а основной результат поддерживается прокручиваемыми результатами.

Вы можете проверить исходный код org.hibernate.query.internal.AbstractProducedQuery, чтобы гарантировать, что ваша обязанность периодически очищать сеанс или вытеснять объект из постоянного контекста.

Как я понимаю из комментариев, StatelessSession не вариант для вас. Я думаю, что чистый способ решить ваше дело - реализовать собственный метод stream(). Это может быть очень похоже на оригинальный метод, просто замените ScrollableResultsIterator на свой собственный, который будет делать то, что вам нужно (вывести объект или очистить сеанс) во время итерации.