Spring + Hibernate + HikariCP: как обрабатывать соединение с БД при длительном вызове REST?

У меня есть проект, работающий на Spring Boot 1.3.8, Hikari CP 2.6.1 и Hibernate (Spring ORM 4.2.8). Код на уровне обслуживания выглядит так:

public void doStuff() {
    A a = dao.findByWhatever();
    if (a.hasProperty()) {
        B b = restService.doRemoteRequestWithRetries(); // May take long time
    }
    a.setProp(b.getSomethig());
    dao.save(b);
}

Конфигурация Hikari имеет это: spring.datasource.leakDetectionThreshold=2000. Проблема в том, что внешняя служба REST работает довольно медленно и часто отвечает за 2+ секунды, в результате мы видим много java.lang.Exception: Apparent connection leak detected, которые являются не чем иным, как ложными отрицательными значениями, хотя проблема хорошо видна: мы удерживаем соединение с БД для время, когда мы выполняем запрос на отдых.

Возникает вопрос: как правильно разделить DB и REST вещи? Или как сказать спящему режиму, чтобы освободить соединение между ними? Чтобы мы вернули соединение с БД в пул, ожидая ответа REST.

Я пробовал установить hibernate.connection.release_mode=AFTER_TRANSACTION, и это вроде как помогает, по крайней мере, у нас нет исключений утечки соединения. Единственная проблема в том, что наши тесты начали показывать следующее:

2018-04-17 15:48:03.438  WARN 94029 --- [           main] o.s.orm.jpa.vendor.HibernateJpaDialect   : JDBC Connection to reset not identical to originally prepared Connection - please make sure to use connection release mode ON_CLOSE (the default) and to run against Hibernate 4.2+ (or switch HibernateJpaDialect's prepareConnection flag to false`

Тесты используют внедренный DAO для вставки записей в БД и последующей их проверки через API приложения. Они не аннотированы @Transactional, и список слушателей выглядит так:

@TestExecutionListeners({
    DependencyInjectionTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
    TransactionDbUnitTestExecutionListener.class
})

Есть идеи, в чем может быть проблема с тестами?

Ваш @Service или @Controller аннотирован @Transactional? Вы упомянули, что тестов нет, но где границы транзакций в производственном коде? Надеюсь, вы не управляете транзакциями в коде Java, например. автопроводным бином txManager.

Karol Dowbecki 17.04.2018 18:35

Нет, я не использую txManager в коде. Ни мои bean-компоненты не аннотированы @Transactional, я заканчиваю аннотированием DAO.

Anton Kushch 19.04.2018 11:49
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
1 431
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В коде

public void doStuff() {
    A a = dao.findByWhatever();
    if (a.hasProperty()) {
        B b = restService.doRemoteRequestWithRetries(); // May take long time
    }
    a.setProp(b.getSomethig());
    dao.save(b);
}

Здесь я вижу три задачи - получение объекта A, подключение к удаленной службе и обновление объекта A. И all these are in same transaction, so the underlying connection will be held till the method is complete.

Таким образом, идея состоит в том, чтобы разделить первую и третью задачи на отдельные транзакции, разрешив разъединение соединения перед вызовом удаленной службы.

По сути, при весенней загрузке вам нужно добавить spring.jpa.open-in-view=false. Это не зарегистрирует OpenEntityManagerInViewInterceptor, и, следовательно, entityManager (в свою очередь соединение) не привязан к текущему потоку / запросу.

Затем разделите три задачи на отдельные методы с помощью @Transactional. Это помогает нам привязать entityManager к области транзакции и освободить соединение в конце метода транзакции.

ПРИМЕЧАНИЕ: И убедитесь, что никакая транзакция не запущена / не выполняется перед вызовом этих методов (например, вызывающий, например, контроллер и т. д.). В противном случае цель будет нарушена, и эти новые методы @Transactional будут работать в той же транзакции, что и раньше.


Таким образом, высокоуровневый подход может выглядеть так:

  • При весенней загрузке application.properties добавляем свойство spring.jpa.open-in-view=false.
  • Затем вам нужно разделить метод doStuff на три метода в новом классе обслуживания. Намерение состоит в том, чтобы убедиться, что они используют разные транзакции.
    • Первый метод с @ Transactionalwill call A a = dao.findByWhatever (); `.
  • Второй метод - удаленный вызов.
  • Третий способ с @ Transactionalwill call rest of the code with JPA merge or hibernate saveOrUpdate on objecta`.
  • Теперь подключите эту новую службу к вашему текущему коду и вызовите 3 метода.

Работает как шарм, престиж @ madhusudana-reddy-sunnapu

Anton Kushch 19.04.2018 11:48

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