Статьи

Генератор Hibernate Identity, Sequence и Table (Sequence)

Вступление

В моем предыдущем посте я говорил о различных стратегиях определения базы данных. Этот пост будет сравнивать наиболее распространенные стратегии суррогатного первичного ключа:

  • ИДЕНТИЧНОСТЬ
  • ПОСЛЕДОВАТЕЛЬНОСТЬ
  • СТОЛ (ПОСЛЕДОВАТЕЛЬНОСТЬ)

ИДЕНТИЧНОСТЬ

Тип IDENTITY (включенный в стандарт SQL: 2003 ) поддерживается:

Генератор IDENTITY позволяет автоматически увеличивать столбец integer / bigint по требованию. Процесс приращения происходит вне текущей выполняемой транзакции, поэтому откат может в конечном итоге отбросить уже присвоенные значения (могут возникнуть пропуски значений).

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

Единственным недостатком является то, что мы не можем знать вновь назначенное значение до выполнения инструкции INSERT. Это ограничение препятствует реализации стратегии очистки транзакций с обратной записью, принятой в Hibernate. По этой причине Hibernates отключает пакетную поддержку JDBC для объектов, использующих генератор IDENTITY.

В следующих примерах мы включим пакетную обработку JDBC Session Factory:

1
2
3
properties.put("hibernate.order_inserts", "true");
properties.put("hibernate.order_updates", "true");
properties.put("hibernate.jdbc.batch_size", "2");

Давайте определим сущность, используя стратегию генерации IDENTITY:

1
2
3
4
5
6
7
@Entity(name = "identityIdentifier")
public static class IdentityIdentifier {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
}

Сохраняются 5 сущностей:

01
02
03
04
05
06
07
08
09
10
doInTransaction(new TransactionCallable<Void>() {
    @Override
    public Void execute(Session session) {
        for (int i = 0; i < 5; i++) {
            session.persist(new IdentityIdentifier());
        }
        session.flush();
        return null;
    }
});

Выполняет один запрос за другим (пакетирование JDBC не задействовано):

1
2
3
4
5
Query:{[insert into identityIdentifier (id) values (default)][]}
Query:{[insert into identityIdentifier (id) values (default)][]}
Query:{[insert into identityIdentifier (id) values (default)][]}
Query:{[insert into identityIdentifier (id) values (default)][]}
Query:{[insert into identityIdentifier (id) values (default)][]}

Помимо отключения пакетной обработки JDBC, стратегия генератора IDENTITY не работает с моделью наследования « Таблица для каждого конкретного класса» , поскольку может быть несколько объектов подкласса, имеющих один и тот же идентификатор, и запрос базового класса приведет к получению объектов с одним и тем же идентификатором (даже если принадлежат к разным типам).

ПОСЛЕДОВАТЕЛЬНОСТЬ

Генератор SEQUENCE (определенный в стандарте SQL: 2003 ) поддерживается:

SEQUENCE — это объект базы данных, который генерирует инкрементные целые числа при каждом последующем запросе. SEQUENCES намного более гибки, чем столбцы IDENTIFIER, потому что:

  • SEQUENCE не содержит таблиц, и одну и ту же последовательность можно назначить нескольким столбцам или таблицам
  • ПОСЛЕДОВАТЕЛЬНОСТЬ может предварительно распределять значения для улучшения производительности
  • ПОСЛЕДОВАТЕЛЬНОСТЬ может определять пошаговый шаг, что позволяет нам воспользоваться «объединенным» алгоритмом Хило
  • ПОСЛЕДОВАТЕЛЬНОСТЬ не ограничивает пакетирование JDBC в Hibernate
  • ПОСЛЕДОВАТЕЛЬНОСТЬ не ограничивает модели наследования Hibernate

Давайте определим сущность, используя стратегию генерации SEQUENCE:

01
02
03
04
05
06
07
08
09
10
@Entity(name = "sequenceIdentifier")
public static class SequenceIdentifier {
    @Id
    @GenericGenerator(name = "sequence", strategy = "sequence", parameters = {
            @org.hibernate.annotations.Parameter(name = "sequenceName", value = "sequence"),
            @org.hibernate.annotations.Parameter(name = "allocationSize", value = "1"),
    })
    @GeneratedValue(generator = "sequence", strategy=GenerationType.SEQUENCE)
    private Long id;
}

Я использовал генератор «sequence», потому что не хотел, чтобы Hibernate выбрал SequenceHiLoGenerator или SequenceStyleGenerator от нашего имени.

Добавление 5 сущностей:

01
02
03
04
05
06
07
08
09
10
doInTransaction(new TransactionCallable<Void>() {
    @Override
    public Void execute(Session session) {
        for (int i = 0; i < 5; i++) {
            session.persist(new SequenceIdentifier());
        }
        session.flush();
        return null;
    }
});

Создайте следующие запросы:

1
2
3
4
5
6
7
8
Query:{[call next value for hibernate_sequence][]}
Query:{[call next value for hibernate_sequence][]}
Query:{[call next value for hibernate_sequence][]}
Query:{[call next value for hibernate_sequence][]}
Query:{[call next value for hibernate_sequence][]}
Query:{[insert into sequenceIdentifier (id) values (?)][1]} {[insert into sequenceIdentifier (id) values (?)][2]}
Query:{[insert into sequenceIdentifier (id) values (?)][3]} {[insert into sequenceIdentifier (id) values (?)][4]}
Query:{[insert into sequenceIdentifier (id) values (?)][5]}

В этой таблице вставки группируются, но мы знаем, что перед вставкой сущностей есть 5 последовательных вызовов. Это можно оптимизировать с помощью алгоритма HILO .

СТОЛ (ПОСЛЕДОВАТЕЛЬНОСТЬ)

Существует еще одна независимая от базы данных альтернатива генерации последовательностей. Одна или несколько таблиц могут использоваться для хранения счетчика последовательности идентификаторов. Но это означает производительность записи и торговли для переносимости базы данных.

Хотя IDENTITY и SEQUENCES не требуют транзакций, используются ACID мандата таблицы базы данных для синхронизации нескольких одновременных запросов на генерацию идентификатора

Это стало возможным благодаря использованию блокировки на уровне строк, которая обходится дороже, чем генераторы IDENTITY или SEQUENCE.

Последовательность должна быть рассчитана в отдельной транзакции базы данных, и для этого требуется механизм IsolationDelegate , который поддерживает как локальные (JDBC), так и глобальные (JTA) транзакции.

  • Для локальных транзакций он должен открывать новое соединение JDBC, поэтому оказывает большее давление на текущий механизм объединения соединений.
  • Для глобальных транзакций требуется приостановка текущей запущенной транзакции. После того, как значение последовательности сгенерировано, фактическая транзакция должна быть возобновлена . Этот процесс имеет свою стоимость, поэтому это может повлиять на общую производительность приложения.

Давайте определим сущность, используя стратегию генерации TABLE:

01
02
03
04
05
06
07
08
09
10
@Entity(name = "tableIdentifier")
public static class TableSequenceIdentifier {
 
    @Id
    @GenericGenerator(name = "table", strategy = "enhanced-table", parameters = {
            @org.hibernate.annotations.Parameter(name = "table_name", value = "sequence_table")
    })
    @GeneratedValue(generator = "table", strategy=GenerationType.TABLE)
    private Long id;
}

Я использовал более новый генератор «улучшенных таблиц», потому что устаревший генератор «таблиц» устарел.

Добавление 5 сущностей:

01
02
03
04
05
06
07
08
09
10
doInTransaction(new TransactionCallable<Void>() {
    @Override
    public Void execute(Session session) {
        for (int i = 0; i < 5; i++) {
            session.persist(new TableSequenceIdentifier());
        }
        session.flush();
        return null;
    }
});

Создайте следующие запросы:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
Query:{[select tbl.next_val from sequence_table tbl where tbl.sequence_name=? for update][default]}
Query:{[insert into sequence_table (sequence_name, next_val)  values (?,?)][default,1]}
Query:{[update sequence_table set next_val=?  where next_val=? and sequence_name=?][2,1,default]}
Query:{[select tbl.next_val from sequence_table tbl where tbl.sequence_name=? for update][default]}
Query:{[update sequence_table set next_val=?  where next_val=? and sequence_name=?][3,2,default]}
Query:{[select tbl.next_val from sequence_table tbl where tbl.sequence_name=? for update][default]}
Query:{[update sequence_table set next_val=?  where next_val=? and sequence_name=?][4,3,default]}
Query:{[select tbl.next_val from sequence_table tbl where tbl.sequence_name=? for update][default]}
Query:{[update sequence_table set next_val=?  where next_val=? and sequence_name=?][5,4,default]}
Query:{[select tbl.next_val from sequence_table tbl where tbl.sequence_name=? for update][default]}
Query:{[update sequence_table set next_val=?  where next_val=? and sequence_name=?][6,5,default]}
Query:{[insert into tableIdentifier (id) values (?)][1]} {[insert into tableIdentifier (id) values (?)][2]}
Query:{[insert into tableIdentifier (id) values (?)][3]} {[insert into tableIdentifier (id) values (?)][4]}
Query:{[insert into tableIdentifier (id) values (?)][5]}

Генератор таблиц позволяет выполнять пакетную обработку JDBC, но использует запросы SELECT FOR UPDATE . Блокировка на уровне строк определенно менее эффективна, чем использование собственной IDENTITY или SEQUENCE.

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

  • Код доступен на GitHub .