Статьи

Асинхронное (неблокирующее) выполнение в JDBC, Hibernate или Spring?

В JDBC нет так называемой поддержки асинхронного выполнения, главным образом потому, что большую часть времени вы хотите дождаться результата вашего DML или DDL, или потому что слишком много сложностей, связанных между внутренней базой данных и внешним драйвером JDBC , 
Некоторые поставщики баз данных предоставляют такую ​​поддержку на своих родных дисках. Например, Oracle поддерживает неблокирующие вызовы в своем родном драйвере OCI. К сожалению, это основано на опросе вместо обратного вызова или прерывания.
Ни Hibernate, ни Spring не поддерживают эту функцию.

Но иногда вам нужна такая функция. Например, некоторая бизнес-логика все еще реализуется с использованием устаревших хранимых процедур Oracle PL / SQL, и они работают довольно долго. Интерфейсный интерфейс не хочет ждать его завершения, и ему просто нужно позже проверить результат выполнения в таблице журналирования базы данных, в которую процедура сохранения запишет статус выполнения.
В других случаях ваше интерфейсное приложение действительно заботится о низкой задержке и не слишком заботится о том, как выполняется отдельный DML. Таким образом, вы просто запускаете DML в базу данных и забываете статус выполнения.

Ничто не может помешать вам выполнять асинхронные вызовы БД с использованием многопоточности в вашем приложении. (На самом деле даже Oracle рекомендует использовать многопоточность вместо опроса OCI для эффективности).
Однако вы должны подумать о том, как обрабатывать транзакции и соединения (или Hibernate Session) в потоках.
Прежде чем продолжить, давайте предположим, что мы обрабатываем только локальную транзакцию вместо JTA.

1. JDBC
Это просто. Вы просто создаете другой поток (далее — поток БД) из вызывающего потока, чтобы выполнить фактический вызов JDBC.
Если такой вызов частый, вы вызываете функцию ThreadPoolExecutor, чтобы уменьшить создание потока и уничтожить накладные расходы.

2. Hibernate
Обычно вы используете «поток» контекстной политики сеанса для Hibernate, чтобы автоматически обрабатывать ваш сеанс и транзакцию.
С помощью этой политики вы получаете один сеанс и транзакцию на поток. Когда вы фиксируете транзакцию, Hibernate автоматически закрывает сеанс.
Опять же, вам нужно создать поток БД для фактического вызова хранимой процедуры.

Некоторому разработчику может быть интересно, наследует ли новый поток БД сеанс и транзакцию своего родительского вызывающего потока.
Это важный вопрос. Прежде всего, вы обычно не хотите делить одну и ту же транзакцию между вызывающим потоком и его порожденным потоком БД, потому что вы хотите немедленно вернуться из вызывающего потока, и если оба потока совместно используют один и тот же сеанс и транзакцию, вызывающий поток может ‘ Не следует совершать транзакцию, а длительную транзакцию следует избегать
Во-вторых, политика потоков Hibernate не поддерживает такое наследование, потому что если вы посмотрите на соответствующий ThreadLocalSessionContext Hibernate ,он использует класс ThreadLocal вместоInheritableThreadLocal .

Вот пример кода в потоке БД:

// Non-managed environment and "thread" policy is in place
// gets a session first
Session sess = factory.getCurrentSession();
Transaction tx = null;
try {
  tx = sess.beginTransaction();

  // call the long running DB stored procedure

  //Hibernate automatically closes the session 
  tx.commit();
}
catch (RuntimeException e) {
  if (tx != null) tx.rollback();
  throw e;
}

3. Декларативная транзакция источника.

Предположим, что вызов вашей хранимой процедуры включен в метод:

  @Transactional(readOnly=false)
  public void callDBStoredProcedure();

Вызывающий поток имеет следующий метод для асинхронного вызова вышеуказанного метода с помощью SpringEx TaskExecutor :

  @Transactional(readOnly=false)
  public void asynchCallDBStoredProcedure() {
        //creates a DB thread pool
        this.taskExecutor.execute(new Runnable() {
            @Override
            public void run() {
                //call callDBStoredProcedure()
            }
        });
  }

Обычно вы настраиваете Spring
HibernateTransactionManager и режим прокси по умолчанию (aspectj — другой режим) для декларативных транзакций. Этот класс связывает транзакцию и сеанс Hibernate с каждым потоком и не наследует так же, как политика «потока» Hibernate.


Где вы положили вышеупомянутый метод
callDBStoredProcedure () имеет огромное значение.

Если вы поместите метод в тот же класс, что и вызывающий поток, объявленная транзакция для
callDBStoredProcedure () не будет выполнена, поскольку в режиме прокси через внешний прокси-сервер AOP поступают только внешние или удаленные вызовы методов (объект, созданный AOP Framework для реализации аспекта транзакции. Этот объект поддерживает класс вашего вызывающего потока путем создания экземпляра класса вашего вызывающего потока) будет перехвачен. Это означает, что «самовывоз», то есть метод внутри целевого объекта (составной экземпляр класса вызывающего потока в прокси-сервере AOP), вызывающий какой-либо другой метод целевого объекта, не приведет к реальной транзакции во время выполнения даже если вызванный метод помечен как
@Transactional !


Поэтому вы должны поместить callDBStoredProcedure () в другой класс в качестве bean-компонента Spring, чтобы поток БД в методе asynchCallDBStoredProcedure () мог загрузить прокси AOP этого бина и вызвать callDBStoredProcedure () через этот прокси.

Об авторе

— технический менеджер SunGard Consulting Services. Он был профессиональным разработчиком программного обеспечения в течение последних 10 лет. Его опыт охватывает Java SE, Java EE, Oracle, настройку приложений и высокопроизводительные вычисления с низкими задержками.