Статьи

Не упустите возможность написания однострочных строк Java 8 SQL с помощью jOOλ или jOOQ

Все больше и больше людей следят за последними обновлениями нашей платформы, внедряя функциональное программирование и для своих предприятий.

В Data Geekery мы используем Java 8 для наших интеграционных тестов jOOQ , поскольку использование нового API-интерфейса Streams с лямбда-выражениями значительно упрощает создание специальных тестовых данных.

Однако мы не чувствуем, что JDK предлагает столько, сколько могло бы , поэтому мы также внедрили и открыли jOOλ , небольшую служебную библиотеку, которая исправляет эти недостатки.

Обратите внимание, что мы не ставим целью заменить более сложные библиотеки, такие как functionsjava . jOOλ действительно исправляет недостатки.

Положить лямбды для работы с jOOλ или jOOQ

Недавно я столкнулся с этим вопросом переполнения стека , в котором запрашивалась потоковая передача набора результатов со всеми столбцами в один список. Например:

вход

1
2
3
4
5
6
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">+----+------------+------------+</span> + ---- + ------------ + ------------ +</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">|</span> |</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">ID |</span> ID |</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">FIRST_NAME |</span> FIRST_NAME |</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">LAST_NAME |</span> LAST_NAME |</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">+----+------------+------------+</span> + ---- + ------------ + ------------ +</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">|</span> |</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">1 |</span> 1 |</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Joslyn |</span> Джослин |</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Vanderford |</span> Вандерфорд |</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">|</span> |</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">2 |</span> 2 |</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Rudolf |</span> Рудольф |</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Hux |</span> Хакс |</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">+----+------------+------------+</span> + ---- + ------------ + ------------ +</span>

Выход

1
2
3
4
5
6
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">1</span> 1</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Joslyn</span> Джослин</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Vanderford</span> Vanderford</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">2</span> 2</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Rudolf</span> Рудольф</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Hux</span> Hux</span>

Это типичный пример учебника для использования функционального программирования, а не итеративного решения:

Итеративное решение

01
02
03
04
05
06
07
08
09
10
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">ResultSet rs = ...;</span> ResultSet rs = ...;</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">ResultSetMetaData meta = rs.getMetaData();</span> ResultSetMetaData meta = rs.getMetaData ();</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">List<Object> list = new ArrayList<>();</span> List <Object> list = new ArrayList <> ();</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">while (rs.next()) {</span> while (rs.next ()) {</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">for (int i = 0; i < meta.getColumnCount(); i++) {</span> for (int i = 0; i <meta.getColumnCount (); i ++) {</span>
        <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">list.add(rs.getObject(i + 1));</span> list.add (rs.getObject (i + 1));</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>

На самом деле, итеративное решение не так уж и плохо, но давайте узнаем, как это можно сделать с помощью функционального программирования.

Использование jOOλ

jool-логотип-черный

Мы используем jOOλ для этого примера по нескольким причинам:

  • JDBC действительно не принял новые функции. Нет простого преобразования ResultSet в Stream , даже если оно должно быть.
  • К сожалению, новые функциональные интерфейсы не позволяют создавать проверенные исключения. try .. catch блоки внутри лямбды не совсем красиво
  • Интересно, что невозможно создать конечный поток, не реализовав Iterator или Spliterator

Итак, вот простой код:

01
02
03
04
05
06
07
08
09
10
11
12
13
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">ResultSet rs = ...;</span> ResultSet rs = ...;</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">ResultSetMetaData meta = rs.getMetaData();</span> ResultSetMetaData meta = rs.getMetaData ();</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">List<Object> list =</span> Список <Объект> список =</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Seq.generate()</span> Seq.generate ()</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.limitWhile(Unchecked.predicate(v -> rs.next()))</span> .limitWhile (Unchecked.predicate (v -> rs.next ()))</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.flatMap(Unchecked.function(v -> IntStream</span> .flatMap (Unchecked.function (v -> IntStream)</span>
       <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.range(0, meta.getColumnCount())</span> .range (0, meta.getColumnCount ())</span>
       <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.mapToObj(Unchecked.intFunction(i -></span> .mapToObj (Unchecked.intFunction (i -></span>
           <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">rs.getObject(i + 1)</span> rs.getObject (i + 1)</span>
       <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">))</span> ))</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">))</span> ))</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.toList()</span> .к списку()</span>

Пока что это выглядит как многословно (или немного больше), чем итеративное решение. Как видите, здесь потребовалось несколько расширений jOOλ:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">// This generate is a shortcut to generate an</span> // Это генерирует ярлык для генерации</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">// infinite stream with unspecified content</span> // бесконечный поток с неопределенным содержимым</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Seq.generate()</span> Seq.generate ()</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">// This predicate-based stream termination</span> // Это основанное на предикате завершение потока</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">// unfortunately doesn't exist in the JDK</span> // к сожалению, не существует в JDK</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">// Besides, the checked exception is wrapped in a</span> // Кроме того, проверенное исключение помещается в</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">// RuntimeException by calling Unchecked.wrapper(...)</span> // RuntimeException путем вызова Unchecked.wrapper (...)</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.limitWhile(Unchecked.predicate(v -> rs.next()))</span> .limitWhile (Unchecked.predicate (v -> rs.next ()))</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">// Standard JDK flatmapping, producing a "nested"</span> // Стандартное отображение JDK, производящее "вложенный"</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">// stream of column values for the "outer" stream</span> // поток значений столбца для "внешнего" потока</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">// of database rows</span> // строк базы данных</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.flatMap(Unchecked.function(v -> IntStream</span> .flatMap (Unchecked.function (v -> IntStream)</span>
       <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.range(0, meta.getColumnCount())</span> .range (0, meta.getColumnCount ())</span>
       <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.mapToObj(Unchecked.intFunction(i -></span> .mapToObj (Unchecked.intFunction (i -></span>
           <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">rs.getObject(i + 1)</span> rs.getObject (i + 1)</span>
       <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">))</span> ))</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">))</span> ))</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">// This is another convenience method that is more</span> // Это еще один удобный метод, который более</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">// verbose to write with standard JDK code</span> // подробно писать со стандартным кодом JDK</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.toList()</span> .к списку()</span>

Используя jOOQ

У jOOQ есть еще более удобный API для работы с записями результатов вашего оператора SQL. Рассмотрим следующую часть логики:

1
2
3
4
5
6
7
8
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">ResultSet rs = ...;</span> ResultSet rs = ...;</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">List<Object> list =</span> Список <Объект> список =</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">DSL.using(connection)</span> DSL.using (соединение)</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.fetch(rs)</span> .fetch (RS)</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.stream()</span> .ручей()</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.flatMap(r -> Arrays.stream(r.intoArray()))</span> .flatMap (r -> Arrays.stream (r.intoArray ()))</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.collect(Collectors.toList());</span> .collect (Collectors.toList ());</span>

Обратите внимание, что в приведенном выше примере используется стандартный API JDK, не прибегая к jOOλ для удобства. Если вы хотите использовать jOOλ с jOOQ, вы можете написать:

1
2
3
4
5
6
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">ResultSet rs = ...;</span> ResultSet rs = ...;</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">List<Object> list =</span> Список <Объект> список =</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Seq.seq(DSL.using(connection).fetch(rs))</span> Seq.seq (DSL.using (подключение) .fetch (RS))</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.flatMap(r -> Arrays.stream(r.intoArray()))</span> .flatMap (r -> Arrays.stream (r.intoArray ()))</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.toList();</span> .к списку();</span>

Легко? Я бы так сказал! Давайте вспомним, что этот пример:

  • Извлекает ResultSet JDBC в коллекцию Java
  • Преобразует каждую запись в наборе результатов в массив значений столбцов
  • Преобразует каждый массив в поток
  • Сглаживает этот поток в поток потоков
  • Собирает все значения в один список

Уф!

Вывод

Мы приближаемся к захватывающим временам! Это займет некоторое время, пока все идиомы Java 8 и функциональное мышление не станут «естественными» для разработчиков Java, в том числе и на предприятии.

Идея иметь своего рода источник данных, который может быть сконфигурирован с конвейерными преобразованиями данных, выраженными в виде лямбда-выражений, которые лениво оцениваются, очень убедительна. jOOQ — это API, который инкапсулирует источники данных SQL очень бегло и интуитивно, но на этом он не останавливается. jOOQ создает регулярные коллекции записей JDK, которые можно трансформировать «из коробки» с помощью нового API потоков.

Мы верим, что это кардинально изменит то, как экосистема Java будет думать о преобразовании данных . Следите за новыми примерами в этом блоге !