Статьи

Java 8 пятница: больше функциональных реляционных преобразований

Раньше каждую пятницу мы давали вам новую статью о том, что нового в Java 8. Это была очень захватывающая серия блогов , но мы хотели бы еще раз сосредоточиться на нашем основном контенте, который является Java и SQL , Время от времени мы будем вести блог о Java 8, но уже не каждую пятницу (как некоторые из вас уже заметили).

В этом последнем коротком посте серии Java 8 Friday, мы хотели бы еще раз повторить тот факт, что мы считаем, что будущее принадлежит функциональному преобразованию реляционных данных (в отличие от ORM) . Мы потратили около 20 лет, используя парадигму разработки объектно-ориентированного программного обеспечения . Многие из нас были очень догматичны в этом. Однако за последние 10 лет «новая» парадигма начала набирать обороты в сообществах программистов: функциональное программирование .

Однако функциональное программирование не так уж ново. Лисп был очень ранним функциональным языком программирования. XSLT и SQL также несколько функциональны (и декларативны!). Поскольку мы большие поклонники функциональной (и декларативной!) Природы SQL, мы весьма взволнованы тем фактом, что теперь у нас есть сложные инструменты в Java для преобразования табличных данных, извлеченных из баз данных SQL. Streams!

SQL ResultSets очень похожи на потоки

Как мы уже указывали ранее, JDBC ResultSets и Java 8 Streams очень похожи . Это еще более верно, когда вы используете jOOQ, который заменяет JDBC ResultSet на org.jooq.Result , который расширяет java.util.List и, таким образом, автоматически наследует все функциональные возможности Streams. Рассмотрим следующий запрос, который позволяет получить отношение «один ко многим» между записями BOOK и AUTHOR:

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
Map<Record2<String, String>,
    List<Record2<Integer, String>>> booksByAuthor =
 
// This work is performed in the database
// --------------------------------------
ctx.select(
        BOOK.ID,
        BOOK.TITLE,
        AUTHOR.FIRST_NAME,
        AUTHOR.LAST_NAME
    )
   .from(BOOK)
   .join(AUTHOR)
   .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID))
   .orderBy(BOOK.ID)
   .fetch()
 
// This work is performed in Java memory
// -------------------------------------
   .stream()
 
   // Group BOOKs by AUTHOR
   .collect(groupingBy(
 
        // This is the grouping key     
        r -> r.into(AUTHOR.FIRST_NAME,
                    AUTHOR.LAST_NAME),
 
        // This is the target data structure
        LinkedHashMap::new,
 
        // This is the value to be produced for each
        // group: A list of BOOK
        mapping(
            r -> r.into(BOOK.ID, BOOK.TITLE),
            toList()
        )
    ));

Свободное владение API Java 8 Streams очень идиоматично для тех, кто привык писать SQL с помощью jOOQ. Очевидно, что вы также можете использовать что-то отличное от jOOQ, например Spring JdbcTemplate или Apache Commons DbUtils, или просто обернуть JDBC ResultSet в итератор…

Что очень хорошо в этом подходе, по сравнению с ORM, так это то, что волшебства не происходит вообще. Каждая часть логики сопоставления является явной и, благодаря универсальным Java, полностью безопасна для типов. Тип вывода booksByAuthor является сложным и немного трудным для чтения / записи, в этом примере, но он также полностью описательный и полезный.

То же функциональное преобразование с POJO

Если вы не слишком довольны использованием Record2 кортежей jOOQ Record2 , не проблема. Вы можете указать свои собственные объекты передачи данных следующим образом:

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
class Book {
    public int id;
    public String title;
 
    @Override
    public String toString() { ... }
 
    @Override
    public int hashCode() { ... }
 
    @Override
    public boolean equals(Object obj) { ... }
}
 
static class Author {
    public String firstName;
    public String lastName;
 
    @Override
    public String toString() { ... }
 
    @Override
    public int hashCode() { ... }
 
    @Override
    public boolean equals(Object obj) { ... }
}

С помощью приведенного выше DTO теперь вы можете использовать встроенное отображение POJO в jOOQ для преобразования записей jOOQ в ваши собственные доменные классы:

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
Map<Author, List<Book>> booksByAuthor =
ctx.select(
        BOOK.ID,
        BOOK.TITLE,
        AUTHOR.FIRST_NAME,
        AUTHOR.LAST_NAME
    )
   .from(BOOK)
   .join(AUTHOR)
   .on(BOOK.AUTHOR_ID.eq(AUTHOR.ID))
   .orderBy(BOOK.ID)
   .fetch()
   .stream()
   .collect(groupingBy(
 
        // This is the grouping key     
        r -> r.into(Author.class),
        LinkedHashMap::new,
 
        // This is the grouping value list
        mapping(
            r -> r.into(Book.class),
            toList()
        )
    ));

Явность против неявности

В Data Geekery мы считаем, что для разработчиков Java наступило новое время. Время, когда Annotatiomania ™ (наконец-то!) Заканчивается, и люди перестают воспринимать все это неявное поведение посредством магии аннотаций. ORM зависят от огромного количества спецификаций, объясняющих, как каждая аннотация работает с аннотацией друг друга. Трудно перепроектировать (или отладить!) Этот не очень хорошо понятный язык аннотаций, который JPA принес нам.

С другой стороны, SQL довольно хорошо понят. Таблицы представляют собой простую в обращении структуру данных, и если вам нужно преобразовать эти таблицы в нечто более объектно-ориентированное или более иерархически структурированное, вы можете просто применить функции к этим таблицам и группировать значения самостоятельно! Явно группируя эти значения, вы сохраняете полный контроль над отображением, так же как и с jOOQ, вы полностью контролируете свой SQL.

Вот почему мы считаем, что в ближайшие 5 лет ORM потеряют свою актуальность, и люди снова начнут применять явные , безгосударственные и безмасляные методы преобразования данных, используя потоки Java 8.