Hibernate Sequence卡在值41无法递增问题咨询
问题分析与解决方案
首先,咱们来拆解你遇到的问题根源,再给出针对性的解决办法:
为什么会出现「1→41→一直41」的异常现象?
你的问题本质是JPA序列预取机制和手动查询序列的行为完全不同步,再加上手动查询只读取序列值不更新,导致了这个奇怪的结果:
- 你给
@SequenceGenerator设置了allocationSize=20,JPA(默认是Hibernate实现)会批量预取20个序列值,一次性把序列的next_val推进20,然后在内存中分配这些值给id字段。比如初始序列值是1,第一次保存CustomerOrder时,JPA会把序列值改成21,同时缓存1-20作为可用的id值。 - 你写的原生查询
SELECT next_val FROM CUSTOMER_ORDER_INVOICE_NO_SEQ只是读取当前序列值,不会推进序列。所以:- 第一次调用查询得到1(序列初始值),保存实体后JPA把序列改成21;
- 当JPA缓存的1-20用完后,会再次把序列推进20,变成41,之后只要缓存没耗尽,就不会再更新序列,你每次查询得到的都是41。
- 更关键的是:你用同一个序列同时生成
id和orderInvoiceNo,这会导致两个字段的值重复,本身就是不合理的设计。
推荐解决方案:为orderInvoiceNo单独创建序列
最稳妥的方式是把id和orderInvoiceNo的序列分开,避免冲突和同步问题。
步骤1:创建独立的发票号序列
比如在数据库中创建ORDER_INVOICE_NO_SEQ(和id用的CUSTOMER_ORDER_INVOICE_NO_SEQ区分开)。
步骤2:修改代码实现
方式A:让JPA自动生成发票号(无需手动查询)
直接在orderInvoiceNo字段上配置序列生成器,保存实体时JPA会自动赋值:
@Entity public class CustomerOrder { @Id @SequenceGenerator( name = "CUSTOMER_ORDER_ID_SEQ_NAME", sequenceName = "CUSTOMER_ORDER_ID_SEQ", allocationSize = 20 ) @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "CUSTOMER_ORDER_ID_SEQ_NAME" ) private int id; // 为发票号单独配置序列 @SequenceGenerator( name = "ORDER_INVOICE_NO_SEQ_NAME", sequenceName = "ORDER_INVOICE_NO_SEQ", allocationSize = 1 // 因为需要每次获取唯一值,关闭预取 ) @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "ORDER_INVOICE_NO_SEQ_NAME" ) private long orderInvoiceNo; //.. getters and setters }
对于CustomerPurchase,同样在它的orderInvoiceNo字段上配置这个序列即可。
方式B:手动获取发票号(需保证原子性更新序列)
如果你必须手动获取值并同时设置给两个对象,需要修改仓库方法,确保查询时同时推进序列(不同数据库语法略有差异):
@Repository public interface OrderInvoiceNoRepository extends JpaRepository<Object, Long> { // 针对表模拟的序列(比如H2、MySQL) @Query( value = "UPDATE ORDER_INVOICE_NO_SEQ SET next_val = next_val + 1 RETURNING next_val", nativeQuery = true ) Long getNextOrderInvoiceNoSeq(); // 如果是PostgreSQL原生序列,用这个: // @Query(value = "SELECT nextval('ORDER_INVOICE_NO_SEQ')", nativeQuery = true) // Long getNextOrderInvoiceNoSeq(); // 如果是Oracle,用这个: // @Query(value = "SELECT ORDER_INVOICE_NO_SEQ.nextval FROM DUAL", nativeQuery = true) // Long getNextOrderInvoiceNoSeq(); }
调用这个方法时,会自动更新并返回下一个序列值,不会出现重复或不更新的问题。
临时修复方案(不推荐,仅用于紧急场景)
如果你暂时无法新增序列,只能共用现有序列,可以通过以下方式同步JPA和手动查询的行为:
- 把
allocationSize改成1,关闭JPA的预取机制:
@SequenceGenerator( name = "CUSTOMER_ORDER_INVOICE_NO_SEQ_NAME", sequenceName = "CUSTOMER_ORDER_INVOICE_NO_SEQ", allocationSize = 1 )
- 修改手动查询的SQL,确保每次查询都推进序列(参考上面方式B的SQL)。
但这个方案会降低JPA的性能(每次保存都要访问数据库获取序列值),而且依然存在id和orderInvoiceNo值重复的风险,不建议长期使用。
内容的提问来源于stack exchange,提问作者thedevd
相关产品推荐
相关产品推荐

