Статьи

Более функциональная реляционная трансформация

Раньше каждую пятницу мы давали вам новую статью о том, что нового в 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 на a  org.jooq.Result, который расширяется java.util.Listи, таким образом, автоматически наследует все функциональные возможности потоков. Рассмотрим следующий запрос, который позволяет получить отношение «один ко многим» между записями BOOK и AUTHOR:

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  , не проблема. Вы можете указать свои собственные объекты передачи данных следующим образом:

class Book {
    public int id;
    public String title;

    @Override
    public String toString() { ... }
    @Override
    public int hashCode() { ... }
    @Override
    public boolean equals(Object obj) { ... }
}
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 в ваши собственные доменные классы:

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.