Вступление
В этом посте мы раскроем генератор идентификаторов последовательности, сочетающий в себе эффективность назначения идентификаторов и совместимость с другими внешними системами (одновременный доступ к базовой системе баз данных).
Традиционно на выбор было две стратегии идентификатора последовательности.
- Идентификатор последовательности , всегда попадающий в базу данных для каждого нового присвоения значения. Даже с предварительным выделением последовательности базы данных у нас есть значительные затраты на передачу данных в оба конца.
- Идентификатор 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] |
Я не уверен, является ли это ошибкой или просто ограничением дизайна, но объединенный оптимизатор не отвечает требованию совместимости.
Чтобы визуализировать, что происходит, я суммировал вызовы последовательности на следующей диаграмме:
Когда объединенный оптимизатор получает текущее значение последовательности, он использует его для вычисления самой низкой границы в памяти. Самое низкое значение — это фактическое предыдущее значение последовательности, и это значение, возможно, уже использовалось каким-либо другим внешним оператором 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; } |
Чтобы лучше понять внутреннюю работу этого оптимизатора, следующая диаграмма суммирует процесс назначения идентификатора:
Вывод
Скрытый драгоценный камень — одна из тех замечательных особенностей, о которых большинство даже не подозревают о его существовании. Оптимизатор pooled-lo чрезвычайно полезен, но большинство людей даже не знают о его существовании.
- Код доступен на GitHub .
Ссылка: | Hibernate скрытая жемчужина: оптимизатор пула-ло от нашего партнера JCG Влада Михалча в блоге Влада Михалча в блоге. |