Эта статья является частью нашего академического курса под названием jOOQ — тип безопасного запроса в БД .
jOOQ — хороший выбор в приложении Java, где важны SQL и конкретная реляционная база данных. Это альтернатива, когда JPA / Hibernate слишком много абстрагирует, JDBC слишком мало. Он показывает, как современный предметно-ориентированный язык может значительно повысить производительность труда разработчиков, внедряя SQL в Java.
В этом курсе мы увидим, как мы можем эффективно запрашивать базы данных, используя jOOQ. Проверьте это здесь !
1. Введение
Хотя SQL является очень выразительным языком, большая часть вашего SQL, вероятно, CRUD (Create, Read, Update, Delete). Написание таких CRUD скучно и повторяется, поэтому ORM, такие как Hibernate, появились и добились успеха в увеличении производительности разработчиков. Но Hibernate делает много предположений (и ограничений) о вашей архитектуре, когда часто вы действительно просто хотите оперировать отдельными записями из таблиц.
Примеры, показанные в этом разделе, также доступны из пакета org.jooq.academy.section2 .
2. Простые активные записи операций
jOOQ знает «активные записи» или UpdatableRecords
, которые могут быть загружены «специальными» типами SELECT
и которые затем отслеживают грязные флаги внутри. Вот как вы можете обновить DATE_OF_BIRTH
автора без написания слишком большого количества SQL:
1
2
3
|
AuthorRecord author = dsl.selectFrom(AUTHOR).where(AUTHOR.ID.eq( 1 )).fetchOne(); author.setDateOfBirth(Date.valueOf( "2000-01-01" )); author.store(); |
Поскольку приведенный выше пример выбирает только одну таблицу с помощью selectFrom()
, jOOQ знает, что результирующий тип записи будет AuthorRecord
, то есть объектом, сгенерированным генератором кода. AuthorRecord
реализует AuthorRecord
, который имеет множество полезных методов:
- store () чтобы
INSERT
илиUPDATE
запись - вставить (), чтобы вставить запись
- обновить (), чтобы обновить запись
- удалить () чтобы
DELETE
запись - refresh () для обновления записи из базы данных
В следующем разделе примеров приведен полный жизненный цикл создания, чтения, обновления и удаления такой записи:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
AuthorRecord author; // Create a new record and store it to the database. This will perform an INSERT statement author = dsl.newRecord(AUTHOR); author.setId( 3 ); author.setFirstName( "Alfred" ); author.setLastName( "Hitchcock" ); author.store(); // Read the record by refreshing it based on the primary key value author = dsl.newRecord(AUTHOR); author.setId( 3 ); author.refresh(); // Update the record with a new value author.setDateOfBirth(Date.valueOf( "1899-08-13" )); author.store(); // Delete the record again author.delete(); |
UpdatableRecords в jOOQ отслеживает внутреннее «грязное» или «измененное» состояние каждого столбца, которое используется при вызове store()
для того, чтобы вставлять / обновлять только те значения, которые были изменены в UpdatableRecord
.
3. Оптимистичная блокировка
При выполнении CRUD одновременный доступ к данным часто является проблемой, которая может быть решена двумя способами:
- Используя пессимистическую блокировку
- Используя оптимистическую блокировку
Пессимистическая блокировка редко является хорошим выбором, поскольку взаимные блокировки могут легко возникнуть, когда два процесса блокируют несколько строк в таблице в различном порядке, ожидая завершения друг друга. Оптимистичная блокировка — гораздо лучшее решение. Одному процессу может просто посчастливиться завершить транзакцию до того, как другой попытается (и потерпит неудачу). Вот как это работает с jOOQ.
В наших примерах данных таблица BOOK
имеет специальный «системный» столбец с именем REC_TIMESTAMP
. Содержимое этого столбца полностью управляется jOOQ всякий раз, когда вы запускаете операцию CRUD на BookRecord
, вам не нужно обновлять ее. Рассмотрим следующий пример кода:
1
2
3
4
5
6
7
|
// Enable optimistic locking DSLContext dsl = DSL.using(connection, new Settings().withExecuteWithOptimisticLocking( true )); // Perform the CRUD with the above setting BookRecord book1 = dsl.selectFrom(BOOK).where(BOOK.ID.eq( 1 )).fetchOne(); book1.setTitle( "New Title" ); book1.store(); |
jOOQ теперь выполнит UPDATE
которая также обновляет и проверяет значения REC_TIMESTAMP
:
1
2
3
4
|
update "PUBLIC" . "BOOK" set "PUBLIC" . "BOOK" . "TITLE" = 'New Title' , "PUBLIC" . "BOOK" . "REC_TIMESTAMP" = timestamp '2014-09-08 18:40:39.416' where ( "PUBLIC" . "BOOK" . "ID" = 1 and "PUBLIC" . "BOOK" . "REC_TIMESTAMP" is null ) |
Обратите внимание, как REC_TIMESTAMP
установлен на текущее время в предложении SET
, в то время как он также проверяется на NULL
(начальное значение в примере базы данных) в WHERE
.
Если у нас есть два конкурирующих процесса (или секции кода в одном и том же процессе) для этого обновления, например:
1
2
3
4
5
6
7
8
|
BookRecord book1 = dsl.selectFrom(BOOK).where(BOOK.ID.eq( 1 )).fetchOne(); BookRecord book2 = dsl.selectFrom(BOOK).where(BOOK.ID.eq( 1 )).fetchOne(); book1.setTitle( "New Title" ); book1.store(); book2.setTitle( "Another Title" ); book2.store(); |
… Тогда мы увидим DataChangedException
при втором вызове store()
(сокращенная трассировка стека):
1
2
3
4
5
|
org.jooq.exception.DataChangedException: Database record has been changed or doesn't exist any longer at org.jooq.impl.UpdatableRecordImpl.checkIfChanged(UpdatableRecordImpl.java: 420 ) at org.jooq.impl.UpdatableRecordImpl.storeUpdate(UpdatableRecordImpl.java: 193 ) at org.jooq.impl.UpdatableRecordImpl.store(UpdatableRecordImpl.java: 129 ) at org.jooq.impl.UpdatableRecordImpl.store(UpdatableRecordImpl.java: 121 ) |
Оптимистическая блокировка применяется для операций UpdatableRecord
, включая insert()
, update()
и delete()
.
jOOQ поддерживает три режима оптимистичной блокировки:
- Использование выделенного столбца
TIMESTAMP
который отслеживает дату изменения - Использование выделенного столбца
NUMBER
который отслеживает номер версии - Используя сравнение значений. Это значение по умолчанию, если для генератора кода не настроены временные метки или столбцы версий.