Статьи

Выполните CRUD с активными записями

Эта статья является частью нашего академического курса под названием 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 , который имеет множество полезных методов:

В следующем разделе примеров приведен полный жизненный цикл создания, чтения, обновления и удаления такой записи:

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 который отслеживает номер версии
  • Используя сравнение значений. Это значение по умолчанию, если для генератора кода не настроены временные метки или столбцы версий.