Неисправность с использованием ScrollableResults-backed Stream в качестве возвращаемого типа в Spring MVC


Важное примечание: это было принято как Spring вопрос с целевой версией исправления 4.1.2.


Моя цель - достичь сложности O (1) пространства при генерации ответа HTTP из Hibernate ScrollableResults. Я хочу сохранить стандартный механизм, в котором отправляется MessageConverter для обработки объекта, возвращаемого с @Controller. Я установил следующее:

  • MappingJackson2HttpMessageConverter, обогащенный JsonSerializer, который обрабатывает Java 8 Stream;
  • пользовательский ScrollableResultSpliterator, необходимый для переноса ScrollableResults в Stream;
  • OpenSessionInViewInterceptor, необходимый для открытия сеанса Hibernate в MessageConverter;
  • установите hibernate.connection.release_mode в ON_CLOSE;
  • убедитесь, что соединение JDBC имеет необходимую стойкость ResultSet: con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT).

Кроме того, мне нужна база данных, которая поддерживает такую ​​устойчивость. PostgreSQL - такая база данных, и у меня нет проблем с этим.

Конечной точкой преткновения, с которой я столкнулся, является политика, используемая HibernateTransactionManager при совершении транзакции: если базовый сеанс не "управляется с помощью Hibernate", он будет disconnect() он, закрывая курсор вместе со всем остальным. Такая политика полезна в некоторых специальных сценариях, в частности "сеансах с разделением разговоров", которые далеки от моих требований.

Мне удалось обойти это с помощью плохой взлома: мне пришлось переопределить метод оскорбления методом, который фактически является копией пасты оригинала, за исключением удаленного вызова disconnect(), но он должен прибегать к отражению для доступа к частному API.

public class NoDisconnectHibernateTransactionManager extends HibernateTransactionManager
{
  private static final Logger logger = LoggerFactory.getLogger(NoDisconnectHibernateTransactionManager.class);

  public NoDisconnectHibernateTransactionManager(SessionFactory sf) { super(sf); }

  @Override
  protected void doCleanupAfterCompletion(Object transaction) {
    final JdbcTransactionObjectSupport txObject = (JdbcTransactionObjectSupport) transaction;
    final Class<?> c = txObject.getClass();
    try {
      // Remove the session holder from the thread.
      if ((Boolean)jailBreak(c.getMethod("isNewSessionHolder")).invoke(txObject))
        TransactionSynchronizationManager.unbindResource(getSessionFactory());

      // Remove the JDBC connection holder from the thread, if exposed.
      if (getDataSource() != null)
        TransactionSynchronizationManager.unbindResource(getDataSource());

      final SessionHolder sessionHolder = (SessionHolder)jailBreak(c.getMethod("getSessionHolder")).invoke(txObject);
      final Session session = sessionHolder.getSession();
      if ((Boolean)jailBreak(HibernateTransactionManager.class.getDeclaredField("prepareConnection")).get(this)
          && session.isConnected() && isSameConnectionForEntireSession(session))
      {
        // We're running with connection release mode "on_close": We're able to reset
        // the isolation level and/or read-only flag of the JDBC Connection here.
        // Else, we need to rely on the connection pool to perform proper cleanup.
        try {
          final Connection con = ((SessionImplementor) session).connection();
          DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
        }
        catch (HibernateException ex) {
          logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
        }
      }
      if ((Boolean)jailBreak(c.getMethod("isNewSession")).invoke(txObject)) {
        logger.debug("Closing Hibernate Session [{}] after transaction",  session);
        SessionFactoryUtils.closeSession(session);
      }
      else {
        logger.debug("Not closing pre-bound Hibernate Session [{}] after transaction", session);
        if (sessionHolder.getPreviousFlushMode() != null)
          session.setFlushMode(sessionHolder.getPreviousFlushMode());
      }
      sessionHolder.clear();
    }
    catch (ReflectiveOperationException e) { throw new RuntimeException(e); }
  }

  static <T extends AccessibleObject> T jailBreak(T o) { o.setAccessible(true); return o; }
}

Поскольку я рассматриваю мой подход как "правильный путь" для генерации ответов, поддерживаемых ResultSet, и поскольку API Streams делает этот подход очень удобным, я хотел бы решить эту проблему поддерживаемым способом.

Есть ли способ получить такое же поведение без моего взлома? Если нет, было бы хорошо спросить через Spring Jira?

Ответ 1

Очистка. Марко Топольник сказал здесь

Да, я пропустил эту часть, что параметр holdability применяется только при встрече с уже существующим сеансом. Это означает, что моя "идея" о том, как это можно сделать, уже такова, как это делается. Это также означает, что мой комментарий о неудачах не применяется: вы либо хотите удержания, либо пропустить сеансовое отключение - или вам тоже не нужно. Поэтому, если вы не можете получить устойчивость, нет причин не отключать сеанс при фиксации, поэтому нет причин активировать "allowResultSetAccessAfterCompletion" в этом случае.