介绍
在这篇文章中,我们将揭示一个序列标识符生成器,它结合了标识符分配效率和与其他外部系统的互操作性(同时访问底层数据库系统)。
传统上,有两种序列标识符策略可供选择。
- 序列标识符,对于每个新值分配总是命中数据库。 即使使用数据库序列预分配,我们也要花费大量的数据库往返费用。
- seqhilo标识符,使用hi / lo算法。 该生成器在内存中计算一些标识符值,因此减少了数据库往返调用。 这种优化技术的问题在于当前的数据库序列值不再反映当前的最高内存生成值。 数据库序列用作存储区编号,这使得其他系统很难与所讨论的数据库表进行互操作。 其他应用程序必须了解高/低标识符策略的内部工作方式,才能正确生成非冲突标识符。
增强的标识符
Hibernate提供了一类新的标识符生成器 ,解决了原始标识符生成器的许多缺点。 增强的标识符生成器没有固定的标识符分配策略。 优化策略是可配置的,我们甚至可以提供自己的优化实现。 默认情况下,Hibernate带有以下内置优化器 :
- none :每个标识符都是从数据库中获取的,因此等效于原始序列生成器。
- hi / lo :它使用hi / lo算法,与原始seqhilo生成器等效。
- 合并的 :此优化器使用高/低优化策略,但是当前内存中标识符的最高边界是从实际数据库序列值中提取的。
- pooled-lo :它类似于池优化器,但是数据库序列值用作当前的内存最低边界
在正式发布公告中 , 公告了合并的优化器可与其他外部系统进行互操作:
即使其他应用程序也在插入值,我们也将是绝对安全的,因为SEQUENCE本身将处理应用此增量大小。
这实际上是我们正在寻找的东西; 当其他外部系统在同一数据库表中同时插入行时,标识符生成器既高效又不会冲突。
测试时间
以下测试将检查新的优化器如何与其他外部数据库表插入配合使用。 在我们的情况下,外部系统将是同一数据库表/序列上的一些本机JDBC插入语句。
doInTransaction(new TransactionCallable<Void>() {@Overridepublic 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;}
});
池优化器
我们将首先使用池优化器策略:
@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;
}
运行测试最终会引发以下异常:
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 statementat 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: SEQUENCEIDENTIFIERat org.hsqldb.jdbc.JDBCUtil.sqlException(Unknown Source) ~[hsqldb-2.3.2.jar:2.3.2]
我不确定这是错误还是仅是设计限制,但是合并的优化器不满足互操作性要求。
为了可视化发生的情况,我在下图中总结了序列调用:
当池优化器检索当前序列值时,它将使用它来计算最低的内存边界。 最小值是实际的先前序列值,并且该值可能已被其他一些外部INSERT语句使用。
Pool-lo优化器
幸运的是,还有另外一个要测试的优化器(参考文档中未提及)。 pool-lo优化器使用当前数据库序列值作为最低的内存边界,因此其他系统可以自由使用下一个序列值而不会冒标识符冲突的风险:
@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;
}
为了更好地了解此优化器的内部工作原理,下图总结了标识符分配过程:
结论
一颗隐藏的宝石是大多数人甚至都不知道其存在的巨大特征之一。 pool-lo优化器非常有用,但是大多数人甚至都不知道它的存在。
- 代码可在GitHub上获得 。
翻译自: https://www.javacodegeeks.com/2014/07/hibernate-hidden-gem-the-pooled-lo-optimizer.html