Статьи

JDBC — эмуляция последовательности

Вероятно, каждый из нас сталкивался с этой проблемой хотя бы раз в жизни программиста — как эмулировать последовательность базы данных? Ниже вы можете найти мой вариант решения этой проблемы.

Предположим, что у нас есть интерфейс, определяющий желаемый API для возврата последовательности целых чисел:

1
2
3
4
5
public interface Sequences {
 
    int nextValue(String sequenceName) throws SQLException;
 
}

и реализация этого API в следующем виде:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class SequencesService implements Sequences {
 
    private static final String SQL_QUERY =
        "SELECT SEQ_NAME, SEQ_VALUE FROM SEQUENCE WHERE SEQ_NAME = ? FOR UPDATE";
 
    private final DataSource dataSource;
 
    SequencesService(final DataSource dataSource) {
        this.dataSource = dataSource;
    }
 
    @Override
    public int nextValue(final String sequenceName) throws SQLException {
        final long threadId = Thread.currentThread().getId();
 
        try (final Connection connection = dataSource.getConnection()) {
            connection.setAutoCommit(false);
            try (final PreparedStatement statement =
                     connection.prepareStatement(
                         SQL_QUERY, TYPE_SCROLL_SENSITIVE, CONCUR_UPDATABLE)) {
                statement.setString(1, sequenceName);
                try (final ResultSet resultSet = statement.executeQuery()) {
                    System.out.println(
                        String.format("[%d] - select for update", threadId));
                    int nextValue = 1;
                    if (resultSet.next()) {
                        nextValue = 1 + resultSet.getInt(2);
                        resultSet.updateInt(2, nextValue);
                        resultSet.updateRow();
                    } else {
                        resultSet.moveToInsertRow();
                        resultSet.updateString(1, sequenceName);
                        resultSet.updateInt(2, nextValue);
                        resultSet.insertRow();
                    }
                    System.out.println(
                        String.format("[%d] - next val: %d", threadId, nextValue));
                    return nextValue;
                }
            } finally {
                System.out.println(String.format("[%d] - commit", threadId));
                connection.commit();
            }
        }
    }
 
}

Вы должны простить мне две вещи 🙂 — использование println, которое я добавил для генерации некоторой визуальной обратной связи;) и отсутствие подробного объяснения, как работает это решение;) Я просто упомяну, что подсказка — это способ, которым готовится утверждение создание и обработка результирующего набора: updateRow / moveToInsertRow / insertRow Использование;) (подробности см. в ссылках внизу этого поста).

Я написал простой тестовый пример для наблюдения и проверки этого кода, что-то вроде:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Autowired
private Sequences sequences;
 
private Callable<Integer> callable() {
    return () -> {
        System.out.println(String.format("[%d] - starting", Thread.currentThread().getId()));
        return sequences.nextValue("My Sequence");
    };
}
 
@Test
public void test() throws Exception {
    final ExecutorService executor = Executors.newFixedThreadPool(3);
    final CompletionService<Integer> completion = new ExecutorCompletionService<>(executor);
 
    for (int i = 0; i < 3; i++) {
        completion.submit(callable());
    }
     
    for (int completed = 1; completed <= 3; completed++) {
        final Future<Integer> result = completion.take();
        System.out.println(String.format("Result %d - %d", completed, result.get()));
        assertEquals(Integer.valueOf(completed), result.get());
    }
}

При запуске приведенного выше кода результат будет примерно таким (идентификаторы потоков в скобках):

[16] — начало
[18] — начало
[17] — начало
[17] — выберите для обновления
[17] — следующий вал: 1
[17] — совершить
[18] — выберите для обновления
Результат 1 — 1
[18] — следующий вал: 2
[18] — совершить
[16] — выберите для обновления
[16] — следующий вал: 3
[16] — совершить
Результат 2 — 2
Результат 3 — 3

Этот код только для демонстрационных целей 🙂 — если вы хотите сделать что-то подобное в своем проекте, вероятно, вы скорее будете использовать для ex. @Transactional аннотация Spring Framework вместо ручной обработки транзакций или даже JPA, делегирующий эту работу JDBC. Например, в Hibernate вы можете сделать это как-то так:

1
2
3
4
5
import org.hibernate.Session;
...
 
entityManager.unwrap(Session.class)
                      .doReturningWork(connection -> { ... code derived from my example ... });

Несколько ссылок на десерт:

… И я почти забыл;) — репозиторий GitHub, содержащий все мои коды кода для этого поста

Смотрите оригинальную статью здесь: JDBC — эмуляция последовательности

Мнения, высказанные участниками Java Code Geeks, являются их собственными.