Статьи

Больше нет необходимости в ОРМ

 Последние десять лет ведутся дебаты о полезности  ORM (объектно-реляционного отображения) . В то время как многие согласны с тем, что Hibernate и JPA очень хорошо решают многие проблемы (главным образом, постоянство сложных графов объектов), другие могут утверждать, что  сложность отображения в основном избыточна для приложений, ориентированных на данные .

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

Давайте начнем с простого примера, где мы используем H2, INFORMATION_SCHEMA чтобы собрать все таблицы и их столбцы. Мы хотим создать специальную структуру данных такого типа, которая  Map<String, List<String>> будет содержать эту информацию. Для простоты взаимодействия с SQL мы будем использовать  jOOQ  (как всегда, шокер в этом блоге). Вот как мы готовим это:

public static void main(String[] args)
throws Exception {
    Class.forName("org.h2.Driver");
    try (Connection c = getConnection(
        "jdbc:h2:~/sql-goodies-with-mapping",
        "sa", "")) {

        // This SQL statement produces all table
        // names and column names in the H2 schema
        String sql =
            "select table_name, column_name " +
            "from information_schema.columns " +
            "order by " +
                "table_catalog, " +
                "table_schema, " +
                "table_name, " +
                "ordinal_position";

        // This is jOOQ's way of executing the above
        // statement. Result implements List, which
        // makes subsequent steps much easier
        Result<Record> result =
        DSL.using(c)
           .fetch(sql)
    }
}

Теперь, когда мы создали этот запрос, давайте посмотрим , как мы можем производить Map<String, List<String>> из jOOQ  Результат :

DSL.using(c)
   .fetch(sql)
   .stream()
   .collect(groupingBy(
       r -> r.getValue("TABLE_NAME"),
       mapping(
           r -> r.getValue("COLUMN_NAME"),
           toList()
       )
   ))
   .forEach(
       (table, columns) ->
           System.out.println(table + ": " + columns)
   );

Приведенный выше пример производит следующий вывод:

FUNCTION_COLUMNS: 
 
  
CONSTANTS: [CONSTANT_CATALOG, CONSTANT_SCHEMA, ...]
SEQUENCES: [SEQUENCE_CATALOG, SEQUENCE_SCHEMA, ...]

 

Как это работает? Давайте пройдемся по шагам

DSL.using(c)
   .fetch(sql)

   // Here, we transform a List into a Stream
   .stream()

   // We're collecting Stream elements into a new
   // collection type
   .collect(

       // The Collector is a grouping operation, producing
       // a Map
       groupingBy(

           // The grouping operation's group key is defined by
           // the jOOQ Record's TABLE_NAME value
           r -> r.getValue("TABLE_NAME"),

           // The grouping operation's group value is generated
           // by this mapping expression...
           mapping(

               // ... which is essentially mapping each grouped
               // jOOQ Record to the Record's COLUMN_NAME value
               r -> r.getValue("COLUMN_NAME"),

               // ... and then collecting all those values into a
               // java.util.List. Whew
               toList()
           )
   ))

   // Once we have this List<String, List<String>> we
   // can simply consume it with the following Consumer
   // lambda expression
   .forEach(
       (table, columns) ->
           System.out.println(table + ": " + columns)
   );

Понял? Эти вещи, конечно, немного сложнее, когда играешь с ним в первый раз. Сочетание новых типов, обширных обобщений, лямбда-выражений поначалу может быть немного запутанным. Лучше всего просто практиковаться с этими вещами, пока вы не освоитесь с этим. В конце концов, весь API Streams действительно революция по сравнению с предыдущими API коллекций Java.

Хорошей новостью является то, что этот API является окончательным и здесь, чтобы остаться. Каждая минута, которую вы тратите на практику, — это инвестиции в ваше собственное будущее

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

import static java.util.stream.Collectors.*;

Также обратите внимание, что вывод больше не был упорядочен как в базе данных. Это потому, что  groupingBy коллектор возвращает  java.util.HashMap. В нашем случае мы могли бы предпочесть собирать вещи в [a href = «http://docs.oracle.com/javase/8/docs/api/java/util/LinkedHashMap.html» style = «font-size: 14px ; цвет фона: прозрачный; цвет: rgb (0, 0, 0); оформление текста: нет; «] java.util.LinkedHashMap, который сохраняет порядок вставки / сбора:

DSL.using(c)
   .fetch(sql)
   .stream()
   .collect(groupingBy(
       r -> r.getValue("TABLE_NAME"),

       // Add this Supplier to the groupingBy
       // method call
       LinkedHashMap::new,
       mapping(
           r -> r.getValue("COLUMN_NAME"),
           toList()
       )
   ))
   .forEach(...);

Мы могли бы продолжить с другими средствами преобразования результатов. Давайте представим, что мы хотели бы создать упрощенный DDL из приведенной выше схемы. Это очень просто. Сначала нам нужно выбрать тип данных столбца. Мы просто добавим его в наш SQL-запрос:

String sql =
    "select " +
        "table_name, " +
        "column_name, " +
        "type_name " + // Add the column type
    "from information_schema.columns " +
    "order by " +
        "table_catalog, " +
        "table_schema, " +
        "table_name, " +
        "ordinal_position";

Я также ввел новый локальный класс для примера, чтобы обернуть атрибуты name и type:

class Column {

    final String name;
    final String type;

    Column(String name, String type) {
        this.name = name;
        this.type = type;
    }
}

Теперь давайте посмотрим, как мы изменим наши вызовы метода Streams API:

result
   .stream()
   .collect(groupingBy(
       r -> r.getValue("TABLE_NAME"),
       LinkedHashMap::new,
       mapping(

           // We now collect this new wrapper type
           // instead of just the COLUMN_NAME
           r -> new Column(
               r.getValue("COLUMN_NAME", String.class),
               r.getValue("TYPE_NAME", String.class)
           ),
           toList()
       )
   ))
   .forEach(
       (table, columns) -> {

           // Just emit a CREATE TABLE statement
           System.out.println("CREATE TABLE " + table + " (");

           // Map each "Column" type into a String
           // containing the column specification,
           // and join them using comma and
           // newline. Done!
           System.out.println(
                columns.stream()
                       .map(col -> "  " + col.name +
                                    " " + col.type)
                       .collect(Collectors.joining(",\n"))
           );

           System.out.println(");");
       }
    );

Вывод не может быть более потрясающим!

CREATE TABLE CATALOGS(
  CATALOG_NAME VARCHAR
);
CREATE TABLE COLLATIONS(
  NAME VARCHAR,
  KEY VARCHAR
);
CREATE TABLE COLUMNS(
  TABLE_CATALOG VARCHAR,
  TABLE_SCHEMA VARCHAR,
  TABLE_NAME VARCHAR,
  COLUMN_NAME VARCHAR,
  ORDINAL_POSITION INTEGER,
  COLUMN_DEFAULT VARCHAR,
  IS_NULLABLE VARCHAR,
  DATA_TYPE INTEGER,
  CHARACTER_MAXIMUM_LENGTH INTEGER,
  CHARACTER_OCTET_LENGTH INTEGER,
  NUMERIC_PRECISION INTEGER,
  NUMERIC_PRECISION_RADIX INTEGER,
  NUMERIC_SCALE INTEGER,
  CHARACTER_SET_NAME VARCHAR,
  COLLATION_NAME VARCHAR,
  TYPE_NAME VARCHAR,
  NULLABLE INTEGER,
  IS_COMPUTED BOOLEAN,
  SELECTIVITY INTEGER,
  CHECK_CONSTRAINT VARCHAR,
  SEQUENCE_NAME VARCHAR,
  REMARKS VARCHAR,
  SOURCE_DATA_TYPE SMALLINT
);

В восторге? Эра ORM, возможно, закончилась только сейчас

Это сильное утверждение. Эра ORM, возможно, закончилась. Зачем? Потому что использование функциональных выражений для преобразования наборов данных является одним из самых мощных понятий в разработке программного обеспечения. Функциональное программирование очень выразительно и очень универсально. Он лежит в основе обработки данных и потоков данных. Мы, разработчики Java, уже знаем существующие функциональные языки. Например, каждый раньше использовал SQL. Думаю об этом. С помощью SQL вы объявляете источники таблиц, проецируете / трансформируете их в новые потоки кортежей и передаете их либо как производные таблицы в другие высокоуровневые операторы SQL, либо в свою программу Java.

Если вы используете XML, вы можете объявить преобразование XML с помощью XSLT и передать результаты другим объектам обработки XML, например другой таблице стилей XSL, используя  конвейерную обработку XProc .

Потоки Java 8 больше ничего. Использование SQL и Streams API — одна из самых мощных концепций обработки данных. Если вы добавите  jOOQ  в стек, вы сможете получить безопасный доступ к записям базы данных и API запросов. Представьте себе, что вы пишете предыдущий оператор, используя свободный API jOOQ, вместо использования строк SQL.

jooq-The-лучший способ к записи-SQL-в-Java

Вся цепочка методов может быть одной единой цепочкой преобразования данных, как таковой:

DSL.using(c)
   .select(
       COLUMNS.TABLE_NAME,
       COLUMNS.COLUMN_NAME,
       COLUMNS.TYPE_NAME
   )
   .from(COLUMNS)
   .orderBy(
       COLUMNS.TABLE_CATALOG,
       COLUMNS.TABLE_SCHEMA,
       COLUMNS.TABLE_NAME,
       COLUMNS.ORDINAL_POSITION
   )
   .fetch()  // jOOQ ends here
   .stream() // Streams start here
   .collect(groupingBy(
       r -> r.getValue(COLUMNS.TABLE_NAME),
       LinkedHashMap::new,
       mapping(
           r -> new Column(
               r.getValue(COLUMNS.COLUMN_NAME),
               r.getValue(COLUMNS.TYPE_NAME)
           ),
           toList()
       )
   ))
   .forEach(
       (table, columns) -> {

           // Just emit a CREATE TABLE statement
           System.out.println("CREATE TABLE " + table + " (");

           // Map each "Column" type into a String
           // containing the column specification,
           // and join them using comma and
           // newline. Done!
           System.out.println(
               columns.stream()
                      .map(col -> "  " + col.name +
                                   " " + col.type)
                      .collect(Collectors.joining(",\n"))
           );

           System.out.println(");");
       }
   );

Java 8 is the future, and with jOOQ, Java 8, and the Streams API, you can write powerful data transformation APIs. I hope we got you as excited as we are! Stay tuned for more awesome Java 8 content on this blog.