Статьи

Сравнение JOOQ с JDBC

Эта статья является частью нашего академического курса под названием jOOQ — тип безопасного запроса в БД .

jOOQ — хороший выбор в приложении Java, где важны SQL и конкретная реляционная база данных. Это альтернатива, когда JPA / Hibernate слишком много абстрагирует, JDBC слишком мало. Он показывает, как современный предметно-ориентированный язык может значительно повысить производительность труда разработчиков, внедряя SQL в Java.

В этом курсе мы увидим, как мы можем эффективно запрашивать базы данных, используя jOOQ. Проверьте это здесь !

1. Введение

Примеры, показанные в этом разделе, также доступны из пакета org.jooq.academy.section3 .

Большинство разработчиков Java хорошо понимают, что такое JDBC и как он работает. Если вы еще этого не сделали, ознакомьтесь с официальными учебными пособиями по JDBC от Oracle, чтобы узнать больше о JDBC.

JDBC часто критиковали за многословность. JDBC также подвергается критике за то, что он выбрал неправильные «значения по умолчанию», например, по умолчанию ленивая материализация наборов результатов. Посмотрим, как jOOQ улучшит эту критику:

2. Проверенные исключения

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

Все API jOOQ будут генерировать RuntimeExceptions которые являются производными от org.jooq.exception.DataAccessException в org.jooq.exception.DataAccessException , которые вам не нужно перехватывать в большинстве случаев, позволяя ему провалиться, чтобы прервать выполняемую в данный момент транзакцию. Пример сравнения двух:

2.1. JDBC

01
02
03
04
05
06
07
08
09
10
11
// These two calls can throw a SQLException
try (PreparedStatement stmt = connection.prepareStatement("SELECT FIRST_NAME FROM AUTHOR");
     ResultSet rs = stmt.executeQuery()) {
 
    // This can throw a SQLException
    while (rs.next()) {
 
        // This can throw a SQLException
        System.out.println(rs.getString(1));
    }
}

2.2. jOOQ

1
2
3
4
5
DSL.using(connection)
   .select(AUTHOR.FIRST_NAME)
   .from(AUTHOR)
   .fetch()
   .forEach(record -> System.out.println(record.getValue(AUTHOR.FIRST_NAME)));

3. Наборы результатов

ResultSet JDBC — это объект с полным состоянием, который плохо взаимодействует с API коллекций Java. Например, он не реализует Iterator , потому что он также должен обеспечивать прокрутку назад через курсор базовой базы данных — функция, которая вряд ли кому-то понадобится.

jOOQ значительно лучше интегрирует наборы результатов SQL через тип org.jooq.Result который удовлетворяет 95% всех случаев использования:

  • Result jOOQ реализует java.util.List и, таким образом, наследует все функции List , включая его способность преобразовываться в поток Java 8 .
  • Result jOOQ полностью материализован в память Java, а не ленив по умолчанию. Это позволяет освободить ресурсы на ранней стадии.
  • Result jOOQ знает свой собственный тип Record , который обеспечивает безопасный для типов доступ к атрибутам записи через ссылки на столбцы, а не через индекс столбца.

Обратите внимание, что выше приведены значения по умолчанию. Если у вас есть большие наборы результатов, которые вы не хотите материализовать запись за записью, вы всегда можете использовать функцию отложенного извлечения в jOOQ. Это можно увидеть в следующих примерах:

3.1. Вы можете использовать jOOQ результаты в циклах foreach

1
2
3
4
5
6
for (Record record : DSL.using(connection)
                        .select()
                        .from(AUTHOR)
                        .fetch()) {
    System.out.println(record);
}

3.2. Вы можете использовать результаты jOOQ с потоками Java 8

1
2
3
4
5
6
7
DSL.using(connection)
   .select()
   .from(AUTHOR)
   .fetch()
   .stream()
   .flatMap(record -> Arrays.stream(record.intoArray()))
   .forEach(System.out::println);

4. Подготовленные заявления

Любопытно, что JDBC различает статические типы java.sql.PreparedStatement и java.sql.PreparedStatement . Эта практика избавит вас от выполнения обхода базы данных для подготовки оператора до его выполнения, но 95% всех запросов в любом случае лучше всего выполнять с использованием подготовленных операторов, так зачем беспокоиться?

jOOQ не различает эти два режима выполнения по отдельным типам операторов. Вместо этого вы можете использовать флаг настроек, чтобы указать, что статические операторы должны выполняться, когда это действительно необходимо. Пример:

4.1. JDBC

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
// Static statement
try (Statement stmt = connection.createStatement()) {
 
    // Remember to pass the SQL string here!
    stmt.executeUpdate("ALTER TABLE ...");
}
 
// Prepared statement
try (PreparedStatement stmt = connection.prepareStatement("SELECT * FROM ... ")) {
 
    // Remember not to pass the SQL string here!
    stmt.executeUpdate();
 
    // ... although, from an API perspective, this would be possible too
    stmt.executeUpdate("Some SQL here");
}

4.2. jOOQ

1
2
3
4
5
6
7
// Static statement
DSL.using(connection, new Settings().withStatementType(StatementType.STATIC_STATEMENT))
   .fetch("SELECT * FROM AUTHOR")
 
// Prepared statement
DSL.using(connection)
   .fetch("SELECT * FROM AUTHOR")

5. Заявления с результирующими наборами

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

5.1. 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 (PreparedStatement stmt = connection.prepareStatement("SELECT FIRST_NAME FROM AUTHOR")) {
 
    // Use the little-known execute() method
    boolean moreResults = stmt.execute();
 
    // Use the rarely-used do {} while (...) loop
    do {
 
        // Check first, if there is any ResultSet available
        if (moreResults) {
            try (ResultSet rs = stmt.getResultSet()) {
                while (rs.next()) {
                    System.out.println(rs.getString(1));
                }
            }
        }
        else {
            System.out.println(stmt.getUpdateCount());
        }
    }
 
    // Repeat until there are neither any more result sets or update counts
    while ((moreResults = stmt.getMoreResults()) || stmt.getUpdateCount() != -1);
}

5.2. jOOQ

С jOOQ вы различаете два типа операторов просто по типу :

  • org.jooq.Query это оператор с количеством обновлений и без результатов
  • org.jooq.ResultQuery — это утверждение с результатами

Только ResultQuery имеет различные методы fetch() :

1
2
3
4
5
Query q1 = dsl.query("ALTER TABLE ...");
int rows = q1.execute();
 
ResultQuery<?> q2 = dsl.resultQuery("SELECT * FROM AUTHOR");
Result<?> result = q2.fetch();