Статьи

Асинхронное выполнение SQL с jOOQ и CompletableFuture в Java 8

Реактивное программирование — это новое модное слово, которое по сути означает просто асинхронное программирование или обмен сообщениями.

Факт заключается в том, что функциональный синтаксис очень помогает при структурировании цепочек асинхронного выполнения, и сегодня мы увидим, как мы можем сделать это в Java 8, используя jOOQ и новый API CompletableFuture .

На самом деле все довольно просто:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// Initiate an asynchronous call chain
CompletableFuture
 
    // This lambda will supply an int value
    // indicating the number of inserted rows
    .supplyAsync(() -> DSL
        .using(configuration)
        .insertInto(AUTHOR, AUTHOR.ID, AUTHOR.LAST_NAME)
        .values(3, "Hitchcock")
        .execute()
    )
                           
    // This will supply an AuthorRecord value
    // for the newly inserted author
    .handleAsync((rows, throwable) -> DSL
        .using(configuration)
        .fetchOne(AUTHOR, AUTHOR.ID.eq(3))
    )
 
    // This should supply an int value indicating
    // the number of rows, but in fact it'll throw
    // a constraint violation exception
    .handleAsync((record, throwable) -> {
        record.changed(true);
        return record.insert();
    })
     
    // This will supply an int value indicating
    // the number of deleted rows
    .handleAsync((rows, throwable) -> DSL
        .using(configuration)
        .delete(AUTHOR)
        .where(AUTHOR.ID.eq(3))
        .execute()
    )
 
    // This tells the calling thread to wait for all
    // chained execution units to be executed
    .join();

Что на самом деле здесь произошло? Ничего необычного. Есть 4 исполнительных блока:

  1. Тот, который вставляет новый АВТОР
  2. Тот, который получает тот же самый АВТОР снова
  3. Тот, который повторно вставляет вновь извлеченный AUTHOR (выбрасывая исключение)
  4. Тот, который игнорирует выброшенное исключение и снова удаляет АВТОРА

Наконец, когда цепочка выполнения установлена, вызывающий поток объединит всю цепочку с помощью метода CompletableFuture.join() , который по сути аналогичен Future.get() , за исключением того, что он не Future.get() ни одного проверенного исключения. ,

Сравнивая это с другими API

Другие API, такие как Scala’s Slick , реализовали аналогичные вещи через «стандартный API», такие как вызовы flatMap() . В настоящее время мы не собираемся имитировать такие API-интерфейсы, поскольку считаем, что новые API-интерфейсы Java 8 станут гораздо более идиоматичными для носителей языка Java. В частности, при выполнении SQL очень важно правильно настроить пул соединений и транзакции. Семантика асинхронно связанных цепей выполнения и их связь с транзакциями очень тонки. Если вы хотите, чтобы транзакция охватывала более одного такого блока, вам придется кодировать ее самостоятельно через Configuration jOOQ и содержащийся в ней ConnectionProvider .

Блокировка JDBC

Очевидно, что всегда будет один блокирующий барьер для таких решений, и это сам JDBC, который очень трудно превратить в асинхронный API. Фактически, немногие базы данных действительно поддерживают асинхронное выполнение запросов и курсоры, поскольку чаще всего один сеанс базы данных может использоваться только одним потоком для одного запроса за раз.