Статьи

Java 8 пятница: JavaScript идет SQL с Nashorn и jOOQ

В Data Geekery мы любим Java. И так как мы действительно входим в свободный API jOOQ и запросы DSL , мы абсолютно взволнованы тем, что Java 8 принесет в нашу экосистему.

Ява 8 Пятница

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

JavaScript идет SQL с Nashorn и JOOQ

На этой неделе мы рассмотрим некоторые замечательные серверные сценарии SQL с Nashorn и Java 8. В Интернете можно найти лишь несколько вещей, касающихся использования JDBC в Nashorn. Но зачем использовать JDBC и заботиться о болезненном управлении ресурсами и составлении строк SQL, когда вы можете использовать jOOQ ? Все работает из коробки!

Давайте настроим небольшой пример файла JavaScript так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var someDatabaseFun = function() {
    var Properties = Java.type("java.util.Properties");
    var Driver = Java.type("org.h2.Driver");
 
    var driver = new Driver();
    var properties = new Properties();
 
    properties.setProperty("user", "sa");
    properties.setProperty("password", "");
 
    try {
        var conn = driver.connect(
            "jdbc:h2:~/test", properties);
 
        // Database code here
    }
    finally {
        try {
            if (conn) conn.close();
        } catch (e) {}
    }
}
 
someDatabaseFun();

Это почти все, что вам нужно для взаимодействия с JDBC и базой данных H2. Таким образом, мы могли бы выполнять операторы SQL с JDBC следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
try {
    var stmt = conn.prepareStatement(
        "select table_schema, table_name " +
        "from information_schema.tables");
    var rs = stmt.executeQuery();
 
    while (rs.next()) {
        print(rs.getString("TABLE_SCHEMA") + "."
            + rs.getString("TABLE_NAME"))
    }
}
finally {
    if (rs)
        try {
            rs.close();
        }
        catch(e) {}
 
    if (stmt)
        try {
            stmt.close();
        }
        catch(e) {}
}

Большая часть этой проблемы связана с обработкой ресурсов JDBC, поскольку, к сожалению, у нас нет оператора try-with-resources в JavaScript. Выше генерируется следующий вывод:

01
02
03
04
05
06
07
08
09
10
11
12
13
INFORMATION_SCHEMA.FUNCTION_COLUMNS
INFORMATION_SCHEMA.CONSTANTS
INFORMATION_SCHEMA.SEQUENCES
INFORMATION_SCHEMA.RIGHTS
INFORMATION_SCHEMA.TRIGGERS
INFORMATION_SCHEMA.CATALOGS
INFORMATION_SCHEMA.CROSS_REFERENCES
INFORMATION_SCHEMA.SETTINGS
INFORMATION_SCHEMA.FUNCTION_ALIASES
INFORMATION_SCHEMA.VIEWS
INFORMATION_SCHEMA.TYPE_INFO
INFORMATION_SCHEMA.CONSTRAINTS
...

Давайте посмотрим, сможем ли мы выполнить тот же запрос, используя jOOQ :

1
2
3
4
5
6
7
var DSL = Java.type("org.jooq.impl.DSL");
 
print(
    DSL.using(conn)
       .fetch("select table_schema, table_name " +
              "from information_schema.tables")
);

Вот как вы можете выполнять простые операторы SQL в jOOQ, с гораздо меньшим раздуванием, чем в JDBC. Вывод примерно такой же:

01
02
03
04
05
06
07
08
09
10
11
12
13
+------------------+--------------------+
|TABLE_SCHEMA      |TABLE_NAME          |
+------------------+--------------------+
|INFORMATION_SCHEMA|FUNCTION_COLUMNS    |
|INFORMATION_SCHEMA|CONSTANTS           |
|INFORMATION_SCHEMA|SEQUENCES           |
|INFORMATION_SCHEMA|RIGHTS              |
|INFORMATION_SCHEMA|TRIGGERS            |
|INFORMATION_SCHEMA|CATALOGS            |
|INFORMATION_SCHEMA|CROSS_REFERENCES    |
|INFORMATION_SCHEMA|SETTINGS            |
|INFORMATION_SCHEMA|FUNCTION_ALIASES    |
 ...

Но сила jOOQ заключается не в его простых возможностях SQL, а в API DSL, который абстрагирует все тонкости SQL, специфичные для поставщика, и позволяет быстро составлять запросы (а также DML). Рассмотрим следующий оператор SQL:

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
// Let's assume these objects were generated
// by the jOOQ source code generator
var Tables = Java.type(
    "org.jooq.db.h2.information_schema.Tables");
var t = Tables.TABLES;
var c = Tables.COLUMNS;
 
// This is the equivalent of Java's static imports
var count = DSL.count;
var row = DSL.row;
 
// We can now execute the following query:
print(
    DSL.using(conn)
       .select(
           t.TABLE_SCHEMA,
           t.TABLE_NAME,
           c.COLUMN_NAME)
       .from(t)
       .join(c)
       .on(row(t.TABLE_SCHEMA, t.TABLE_NAME)
           .eq(c.TABLE_SCHEMA, c.TABLE_NAME))
       .orderBy(
           t.TABLE_SCHEMA.asc(),
           t.TABLE_NAME.asc(),
           c.ORDINAL_POSITION.asc())
       .fetch()
);

Обратите внимание, что в приведенном выше запросе очевидно нет безопасности типов, так как это JavaScript. Но я бы предположил, что создатели IntelliJ, Eclipse или NetBeans в конечном итоге обнаружат зависимости Nashorn от Java-программ и обеспечат автоматическое завершение и выделение синтаксиса, поскольку некоторые вещи можно анализировать статически.

Все становится еще лучше, если вы используете Java 8 Streams API от Nashorn. Давайте рассмотрим следующий запрос:

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
DSL.using(conn)
   .select(
       t.TABLE_SCHEMA,
       t.TABLE_NAME,
       count().as("CNT"))
   .from(t)
   .join(c)
   .on(row(t.TABLE_SCHEMA, t.TABLE_NAME)
       .eq(c.TABLE_SCHEMA, c.TABLE_NAME))
   .groupBy(t.TABLE_SCHEMA, t.TABLE_NAME)
   .orderBy(
       t.TABLE_SCHEMA.asc(),
       t.TABLE_NAME.asc())
 
// This fetches a List<Map<String, Object>> as
// your ResultSet representation
   .fetchMaps()
 
// This is Java 8's standard Collection.stream()
   .stream()
 
// And now, r is like any other JavaScript object
// or record!
   .forEach(function (r) {
       print(r.TABLE_SCHEMA + '.'
           + r.TABLE_NAME + ' has '
           + r.CNT + ' columns.');
   });

Выше генерирует этот вывод:

1
2
3
4
5
6
7
8
9
INFORMATION_SCHEMA.CATALOGS has 1 columns.
INFORMATION_SCHEMA.COLLATIONS has 2 columns.
INFORMATION_SCHEMA.COLUMNS has 23 columns.
INFORMATION_SCHEMA.COLUMN_PRIVILEGES has 8 columns.
INFORMATION_SCHEMA.CONSTANTS has 7 columns.
INFORMATION_SCHEMA.CONSTRAINTS has 13 columns.
INFORMATION_SCHEMA.CROSS_REFERENCES has 14 columns.
INFORMATION_SCHEMA.DOMAINS has 14 columns.
...

Если ваша база данных поддерживает массивы, вы даже можете получить доступ к таким столбцам массива по индексу, например,

1
r.COLUMN_NAME[3]

Так что, если вы являетесь поклонником JavaScript на стороне сервера, скачайте jOOQ сегодня и начните писать потрясающий SQL на JavaScript прямо сейчас! Чтобы узнать больше о Nashorn, прочитайте эту статью здесь .