Статьи

От JPA до устаревших и расширенных генераторов идентификаторов Hibernate

Генераторы идентификаторов JPA

JPA определяет следующие стратегии идентификаторов:

стратегия Описание
АВТО Поставщик персистентности выбирает наиболее подходящую стратегию идентификатора, поддерживаемую базовой базой данных.
ИДЕНТИЧНОСТЬ Идентификаторы назначаются столбцом IDENTITY базы данных.
ПОСЛЕДОВАТЕЛЬНОСТЬ Поставщик постоянства использует последовательность базы данных для генерации идентификаторов.
СТОЛ Поставщик сохраняемости использует отдельную таблицу базы данных для эмуляции объекта последовательности

В моем предыдущем посте я рассмотрел плюсы и минусы всех этих стратегий суррогатного идентификатора.

Идентификатор оптимизаторов

Хотя оптимизации генератора IDENTITY на стороне приложения не так много (кроме настройки предварительного распределения идентификаторов базы данных), идентификаторы последовательности предлагают гораздо большую гибкость в этом отношении. Одна из наиболее распространенных стратегий оптимизации основана на алгоритме распределения hi / lo .

Для этого Hibernate предлагает:

Генератор Описание
SequenceHiLoGenerator Он использует последовательность базы данных для генерации значения hi, в то время как нижнее значение увеличивается в соответствии с алгоритмом hi / lo

TableHiLoGenerator
Таблица базы данных используется для генерации значений hi. Этот генератор не рекомендуется в пользу MultipleHiLoPerTableGenerator, расширенного TableGenerator или SequenceStyleGenerator.
MultipleHiLo
PerTableGenerator
Это генератор таблиц hi / lo, способный использовать одну таблицу базы данных даже для нескольких последовательностей идентификаторов.
SequenceStyleGenerator Это расширенная версия предыдущего генератора последовательности. Он использует последовательность, если базовая база данных поддерживает их. Если текущая база данных не поддерживает последовательности, она переключается на использование таблицы для генерации значений последовательности. В то время как предыдущие генераторы имели предопределенный алгоритм оптимизации, расширенные генераторы можно настроить с помощью стратегии оптимизатора:

  • none : стратегия оптимизации не применяется, поэтому каждый идентификатор выбирается из базы данных.
  • привет / lo : он использует оригинальный алгоритм привет / lo. Эта стратегия затрудняет для других систем использование одной и той же последовательности идентификаторов, что требует от других систем реализации логики генерации идентичных идентификаторов.
  • pooled : этот оптимизатор использует стратегию оптимизации hi / lo, но вместо сохранения текущего значения hi он сохраняет верхнюю границу текущего диапазона (или нижнюю границу — hibernate.id.optimizer.pooled.prefer_lo ).

Пул является стратегией оптимизатора по умолчанию.

TableGenerator Как и MultipleHiLoPerTableGenerator, он может использовать одну таблицу для нескольких генераторов идентификаторов, предлагая настраиваемые стратегии оптимизатора.

Пул является стратегией оптимизатора по умолчанию.

Отображение идентификатора JPA в Hibernate

Имея такое обширное предложение генераторов, мы не можем не спросить, какой из них используется в качестве генераторов JPA по умолчанию.

Хотя спецификация JPA не предполагает какой-либо конкретной оптимизации, Hibernate предпочтет оптимизированный генератор, а не тот, который всегда попадает в базу данных для каждого нового идентификатора.

JPA SequenceGenerator

Мы определим один объект, настроенный с помощью генератора идентификаторов SEQUENCE JPA. В модульном тесте будет сохраняться пять таких объектов.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Entity(name = "sequenceIdentifier")
public static class SequenceIdentifier {
 
    @Id
    @GeneratedValue(generator = "sequence", strategy=GenerationType.SEQUENCE)
    @SequenceGenerator(name = "sequence", allocationSize = 10)
    private Long id;
}
 
@Test
public void testSequenceIdentifierGenerator() {
    LOGGER.debug("testSequenceIdentifierGenerator");
    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;
        }
    });
}

Запустив этот тест, мы получим следующий вывод

01
02
03
04
05
06
07
08
09
10
11
Query:{[call next value for hibernate_sequence][]}
Generated identifier: 10, using strategy: org.hibernate.id.SequenceHiLoGenerator
Generated identifier: 11, using strategy: org.hibernate.id.SequenceHiLoGenerator
Generated identifier: 12, using strategy: org.hibernate.id.SequenceHiLoGenerator
Generated identifier: 13, using strategy: org.hibernate.id.SequenceHiLoGenerator
Generated identifier: 14, using strategy: org.hibernate.id.SequenceHiLoGenerator
Query:{[insert into sequenceIdentifier (id) values (?)][10]}
Query:{[insert into sequenceIdentifier (id) values (?)][11]}
Query:{[insert into sequenceIdentifier (id) values (?)][12]}
Query:{[insert into sequenceIdentifier (id) values (?)][13]}
Query:{[insert into sequenceIdentifier (id) values (?)][14]}

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

Hibernate предпочитает использовать генератор «seqhilo» по умолчанию, что не является интуитивным предположением, поскольку многие могут ожидать необработанный генератор «sequence» (всегда вызывая последовательность базы данных для каждого нового значения идентификатора).

Чтобы включить расширенные генераторы, нам нужно установить следующее свойство Hibernate:

1
properties.put("hibernate.id.new_generator_mappings", "true");

Дает нам следующий вывод:

01
02
03
04
05
06
07
08
09
10
11
12
Query:{[call next value for hibernate_sequence][]}
Query:{[call next value for hibernate_sequence][]}
Generated identifier: 1, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
Generated identifier: 2, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
Generated identifier: 3, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
Generated identifier: 4, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
Generated identifier: 5, using strategy: org.hibernate.id.enhanced.SequenceStyleGenerator
Query:{[insert into sequenceIdentifier (id) values (?)][1]}
Query:{[insert into sequenceIdentifier (id) values (?)][2]}
Query:{[insert into sequenceIdentifier (id) values (?)][3]}
Query:{[insert into sequenceIdentifier (id) values (?)][4]}
Query:{[insert into sequenceIdentifier (id) values (?)][5]}

Новый SequenceStyleGenerator генерирует другие значения идентификатора, чем унаследованный SequenceHiLoGenerator. Причина, по которой операторы обновления различаются между старым и новым генераторами, заключается в том, что стратегия оптимизатора по умолчанию для новых генераторов «объединена», в то время как старые генераторы могут использовать только стратегию «hi / lo».

JPA TableGenerator

1
2
3
4
5
6
7
8
@Entity(name = "tableIdentifier")
public static class TableSequenceIdentifier {
 
    @Id
    @GeneratedValue(generator = "table", strategy=GenerationType.TABLE)
    @TableGenerator(name = "table", allocationSize = 10)
    private Long id;
}

Выполнение следующего теста:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Test
public void testTableSequenceIdentifierGenerator() {
    LOGGER.debug("testTableSequenceIdentifierGenerator");
    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;
        }
    });
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
Query:{[select sequence_next_hi_value from hibernate_sequences where sequence_name = 'tableIdentifier' for update][]}
Query:{[insert into hibernate_sequences(sequence_name, sequence_next_hi_value) values('tableIdentifier', ?)][0]}
Query:{[update hibernate_sequences set sequence_next_hi_value = ? where sequence_next_hi_value = ? and sequence_name = 'tableIdentifier'][1,0]}
Generated identifier: 1, using strategy: org.hibernate.id.MultipleHiLoPerTableGenerator
Generated identifier: 2, using strategy: org.hibernate.id.MultipleHiLoPerTableGenerator
Generated identifier: 3, using strategy: org.hibernate.id.MultipleHiLoPerTableGenerator
Generated identifier: 4, using strategy: org.hibernate.id.MultipleHiLoPerTableGenerator
Generated identifier: 5, using strategy: org.hibernate.id.MultipleHiLoPerTableGenerator
Query:{[insert into tableIdentifier (id) values (?)][1]}
Query:{[insert into tableIdentifier (id) values (?)][2]}
Query:{[insert into tableIdentifier (id) values (?)][3]}
Query:{[insert into tableIdentifier (id) values (?)][4]}
Query:{[insert into tableIdentifier (id) values (?)][5]}

Как и в предыдущем примере SEQUENCE, Hibernate использует MultipleHiLoPerTableGenerator для обеспечения обратной совместимости.

Переключение на расширенные генераторы идентификаторов:

1
properties.put("hibernate.id.new_generator_mappings", "true");

Дайте нам следующий вывод:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
Query:{[select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update][tableIdentifier]}
Query:{[insert into hibernate_sequences (sequence_name, next_val)  values (?,?)][tableIdentifier,1]}
Query:{[update hibernate_sequences set next_val=?  where next_val=? and sequence_name=?][11,1,tableIdentifier]}
Query:{[select tbl.next_val from hibernate_sequences tbl where tbl.sequence_name=? for update][tableIdentifier]}
Query:{[update hibernate_sequences set next_val=?  where next_val=? and sequence_name=?][21,11,tableIdentifier]}
Generated identifier: 1, using strategy: org.hibernate.id.enhanced.TableGenerator
Generated identifier: 2, using strategy: org.hibernate.id.enhanced.TableGenerator
Generated identifier: 3, using strategy: org.hibernate.id.enhanced.TableGenerator
Generated identifier: 4, using strategy: org.hibernate.id.enhanced.TableGenerator
Generated identifier: 5, using strategy: org.hibernate.id.enhanced.TableGenerator
Query:{[insert into tableIdentifier (id) values (?)][1]}
Query:{[insert into tableIdentifier (id) values (?)][2]}
Query:{[insert into tableIdentifier (id) values (?)][3]}
Query:{[insert into tableIdentifier (id) values (?)][4]}
Query:{[insert into tableIdentifier (id) values (?)][5]}

Вы можете видеть, что на этот раз был использован новый улучшенный TableGenerator .

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

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