Проблема с использованием потока, поддерживаемого ScrollableResults, в качестве возвращаемого типа в Spring MVC


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


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

  1. MappingJackson2HttpMessageConverter обогащен JsonSerializer который обрабатывает Java 8 Stream;
  2. обычай ScrollableResultSpliterator нужно обернуть ScrollableResults в Stream;
  3. OpenSessionInViewInterceptor необходимо держать сеанс Hibernate открытым в MessageConverter;
  4. задавать hibernate.connection.release_mode в ON_CLOSE;
  5. убедитесь, что соединение 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, и поскольку Streams API делает этот подход очень удобным, я хотел бы решить эту проблему поддерживаемым способом.

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

1 ответ

Убираться. Как сказал здесь Марко Топольник

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

Другие вопросы по тегам