Статьи

JavaScript идет SQL с Nashorn и JOOQ

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

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

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 следующим образом:

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. Выше генерируется следующий вывод:

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 :

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. Вывод примерно такой же:

+------------------+--------------------+
|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:

// 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. Давайте рассмотрим следующий запрос:

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.');
   });

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

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.
...

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

r.COLUMN_NAME[3]

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