Эта статья является частью нашего академического курса под названием 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(); |