Статьи

Hibernate скрытый драгоценный камень: оптимизатор пула-ло

Вступление

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

Традиционно на выбор было две стратегии идентификатора последовательности.

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

Расширенные идентификаторы

Hibernate предлагает новый класс генераторов идентификаторов , устраняющий многие недостатки оригинальных. Генераторы расширенных идентификаторов не имеют фиксированной стратегии выделения идентификаторов. Стратегия оптимизации настраивается, и мы можем даже предложить нашу собственную реализацию оптимизации. По умолчанию Hibernate поставляется со следующими встроенными оптимизаторами :

  • none : каждый идентификатор выбирается из базы данных, поэтому он эквивалентен исходному генератору последовательности .
  • hi / lo : он использует алгоритм hi / lo и эквивалентен оригинальному генератору seqhilo .
  • pooled : этот оптимизатор использует стратегию оптимизации hi / lo, но верхняя граница текущего идентификатора в памяти извлекается из фактического значения последовательности базы данных.
  • pooled-lo : он аналогичен оптимизатору пула, но в качестве текущей нижней границы в памяти используется значение последовательности базы данных

В официальном объявлении о выпуске объединенные оптимизаторы объявляются как совместимые с другими внешними системами:

Даже если другие приложения также вставляют значения, мы будем в полной безопасности, потому что сама SEQUENCE будет обрабатывать применение этого increment_size.

Это на самом деле то, что мы ищем; Генератор идентификаторов эффективен и не конфликтует, когда другие внешние системы одновременно вставляют строки в те же таблицы базы данных.

Время тестирования

Следующий тест будет проверять, как новые оптимизаторы уживаются с другими вставками таблицы внешних баз данных. В нашем случае внешней системой будут некоторые собственные операторы вставки JDBC в той же таблице / последовательности базы данных.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
doInTransaction(new TransactionCallable<Void>() {
    @Override
    public Void execute(Session session) {
        for (int i = 0; i < 8; i++) {
            session.persist(newEntityInstance());
        }
        session.flush();
        assertEquals(8, ((Number) session.createSQLQuery("SELECT COUNT(*) FROM sequenceIdentifier").uniqueResult()).intValue());
        insertNewRow(session);
        insertNewRow(session);
        insertNewRow(session);
        assertEquals(11, ((Number) session.createSQLQuery("SELECT COUNT(*) FROM sequenceIdentifier").uniqueResult()).intValue());
        List<Number> ids = session.createSQLQuery("SELECT id FROM sequenceIdentifier").list();
        for (Number id : ids) {
            LOGGER.debug("Found id: {}", id);
        }
        for (int i = 0; i < 3; i++) {
            session.persist(newEntityInstance());
        }
        session.flush();
        return null;
    }
});

Объединенный оптимизатор

Сначала мы будем использовать стратегию объединенного оптимизатора:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@Entity(name = "sequenceIdentifier")
public static class PooledSequenceIdentifier {
 
    @Id
    @GenericGenerator(name = "sequenceGenerator", strategy = "enhanced-sequence",
            parameters = {
                    @org.hibernate.annotations.Parameter(name = "optimizer", value = "pooled"),
                    @org.hibernate.annotations.Parameter(name = "initial_value", value = "1"),
                    @org.hibernate.annotations.Parameter(name = "increment_size", value = "5")
            }
    )
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    private Long id;
}

Запуск теста в итоге выдает следующее исключение:

01
02
03
04
05
06
07
08
09
10
DEBUG [main]: n.t.d.l.SLF4JQueryLoggingListener - Name: Time:0 Num:1 Query:{[insert into sequenceIdentifier (id) values (?)][9]}
DEBUG [main]: n.t.d.l.SLF4JQueryLoggingListener - Name: Time:0 Num:1 Query:{[insert into sequenceIdentifier (id) values (?)][10]}
DEBUG [main]: n.t.d.l.SLF4JQueryLoggingListener - Name: Time:0 Num:1 Query:{[insert into sequenceIdentifier (id) values (?)][26]}
WARN  [main]: o.h.e.j.s.SqlExceptionHelper - SQL Error: -104, SQLState: 23505
ERROR [main]: o.h.e.j.s.SqlExceptionHelper - integrity constraint violation: unique constraint or index violation; SYS_PK_10104 table: SEQUENCEIDENTIFIER
ERROR [main]: c.v.h.m.l.i.PooledSequenceIdentifierTest - Pooled optimizer threw
org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:72) ~[hibernate-core-4.3.5.Final.jar:4.3.5.Final]   
Caused by: java.sql.SQLIntegrityConstraintViolationException: integrity constraint violation: unique constraint or index violation; SYS_PK_10104 table: SEQUENCEIDENTIFIER
    at org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source) ~[hsqldb-2.3.2.jar:2.3.2]

Я не уверен, является ли это ошибкой или просто ограничением дизайна, но объединенный оптимизатор не отвечает требованию совместимости.

Чтобы визуализировать, что происходит, я суммировал вызовы последовательности на следующей диаграмме:

pooledoptimizer

Когда объединенный оптимизатор получает текущее значение последовательности, он использует его для вычисления самой низкой границы в памяти. Самое низкое значение — это фактическое предыдущее значение последовательности, и это значение, возможно, уже использовалось каким-либо другим внешним оператором INSERT.

Оптимизатор пула-ло

К счастью, есть еще один оптимизатор (не упомянутый в справочной документации) для тестирования. Оптимизатор pooled-lo использует текущее значение последовательности базы данных в качестве самой низкой границы в памяти, поэтому другие системы могут свободно использовать следующие значения последовательности, не рискуя столкнуться с идентификатором:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@Entity(name = "sequenceIdentifier")
public static class PooledLoSequenceIdentifier {
 
    @Id
    @GenericGenerator(name = "sequenceGenerator", strategy = "enhanced-sequence",
            parameters = {
                    @org.hibernate.annotations.Parameter(name = "optimizer",
                            value = "pooled-lo"
                    ),
                    @org.hibernate.annotations.Parameter(name = "initial_value", value = "1"),
                    @org.hibernate.annotations.Parameter(name = "increment_size", value = "5")
            }
    )
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    private Long id;
}

Чтобы лучше понять внутреннюю работу этого оптимизатора, следующая диаграмма суммирует процесс назначения идентификатора:

pooledlooptimizer

Вывод

Скрытый драгоценный камень — одна из тех замечательных особенностей, о которых большинство даже не подозревают о его существовании. Оптимизатор pooled-lo чрезвычайно полезен, но большинство людей даже не знают о его существовании.

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