Рубрики
Без рубрики

Руководство для начинающих по гибернации расширенных генераторов идентификаторов

Автор оригинала: Vlad Mihalcea.

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

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

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

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

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

Последовательностьилогенератор Он использует последовательность базы данных для генерации значения hi, в то время как низкое значение увеличивается в соответствии с алгоритмом hi/lo
Настольный хилогенератор Для генерации значений hi используется таблица базы данных. Этот генератор устарел в пользу MultipleHiLoPerTableGenerator, расширенного табличного генератора или генератора последовательностей.
Генератор Нескольких ХиЛо На Таблицу Это генератор таблиц hi/lo, способный использовать одну таблицу базы данных даже для нескольких последовательностей идентификаторов.
Генератор последовательностей Это улучшенная версия предыдущего генератора последовательностей. Он использует последовательность, если базовая база данных поддерживает их. Если текущая база данных не поддерживает последовательности, она переключается на использование таблицы для генерации значений последовательностей. В то время как предыдущие генераторы имели заранее определенный алгоритм оптимизации, улучшенные генераторы можно настроить с помощью стратегии оптимизатора: нет: стратегия оптимизации не применяется, поэтому каждый идентификатор извлекается из базы данных hi/lo: он использует оригинальный алгоритм hi/lo. Эта стратегия затрудняет для других систем совместное использование одной и той же последовательности идентификаторов, требуя, чтобы другие системы реализовывали одну и ту же логику генерации идентификаторов. объединенный: Этот оптимизатор использует стратегию оптимизации hi/lo, но вместо сохранения текущего значения hi он сохраняет верхнюю границу текущего диапазона (или нижнюю границу – hibernate.id.optimizer.объединенный.prefer_lo). Объединение-это стратегия оптимизатора по умолчанию.
Генератор таблиц Как и MultipleHiLoPerTableGenerator, он может использовать одну таблицу для нескольких генераторов идентификаторов, предлагая при этом настраиваемые стратегии оптимизатора. Объединение-это стратегия оптимизатора по умолчанию.

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

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

Генератор последовательности JPA

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

@Entity(name = "sequenceIdentifier")
public 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() {
        @Override
        public Void execute(Session session) {
            for (int i = 0; i < 5; i++) {
                session.persist(new SequenceIdentifier());
            }
            session.flush();
            return null;
        }
    });
}

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

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

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

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

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

Это дает нам следующий результат:

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]} 

Новый Генератор последовательностей генерирует значения идентификаторов, отличные от устаревшего генератора последовательностей. Причина, по которой операторы обновления отличаются между старыми и новыми генераторами, заключается в том, что стратегия оптимизатора новых генераторов по умолчанию “объединена”, в то время как старые генераторы могут использовать только стратегию “привет/привет”.

Генератор таблиц JPA

@Entity(name = "tableIdentifier")
public class TableSequenceIdentifier {

    @Id
    @GeneratedValue(generator = "table", strategy=GenerationType.TABLE)
    @TableGenerator(name = "table", allocationSize = 10)
    private Long id;
}

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

@Test
public void testTableSequenceIdentifierGenerator() {
    LOGGER.debug("testTableSequenceIdentifierGenerator");
    doInTransaction(new TransactionCallable() {
        @Override
        public Void execute(Session session) {
            for (int i = 0; i < 5; i++) {
                session.persist(new TableSequenceIdentifier());
            }
            session.flush();
            return null;
        }
    });
}

Генерирует следующий вывод инструкции SQL:

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]}

Как и в предыдущем примере ПОСЛЕДОВАТЕЛЬНОСТИ, Hibernate использует MultipleHiLoPerTableGenerator для поддержания обратной совместимости.

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

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

Дайте нам следующий результат:

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 .