带有Hibernate财务序列生成的Spring JPA

dre*_*nda 4 java spring hibernate jpa

在我的应用程序中,我正在为发票建模.在我的国家(意大利),每张发票都必须有一个没有漏洞的唯一序号,每年必须从1开始重新开始.

我认为实施它的最佳方法很长很难,但我还没有找到一个很好的指导.现在我有一个JpaRepository我自定义同步save()方法的地方,我在其中使用了最后一个ID:

SELECT MAX(numero) FROM Invoice WHERE YEAR(date) = :year
Run Code Online (Sandbox Code Playgroud)

这种方法的问题是不安全,因为开发人员应该知道只应该使用该特定服务进行保存.

相反,我想要更多的开发人员隐藏的方法.我想在一个@Prepersist方法中使用一种方法@EntityListeners.这听起来不错但是在这个课程中获得实体经理并不是那么简单....所以也许不是最佳的地方......

最后我想到了Hibernate Interceptor ....

请给我一些提示.问题似乎是一个非常普遍的问题; 所以也许还有一个很好的做法可以效仿.

谢谢

man*_*ish 7

此问题可以分解为以下要求:

  1. 顺序唯一:从序列中生成数字,从给定值开始(比如说1000001),然后总是按固定值递增(比方说1).
  2. 无差距:数字之间不得有任何差距.因此,如果生成的第一个数字是1000001,增量是1,到目前为止已经生成了200个数字,最新的数字应该是1000201.
  3. 并发:多个进程必须能够同时生成数字.
  4. 创建时生成:必须在创建记录时生成数字.
  5. 没有排他锁:生成数字不需要专用锁.

任何解决方案只能满足这5个要求中的4个.例如,如果要保证1-4,则每个进程都需要锁定,以便其他进程不能生成和使用它生成的相同数字.因此,强制1-4作为要求将意味着必须放弃5.同样,如果要保证1,2,4和5,则需要确保一次只有一个进程(线程)生成一个数字,因为在没有锁定的情况下并发环境中无法保证唯一性.继续这个逻辑,你会明白为什么不可能同时保证所有这些要求.

现在,解决方案取决于您愿意牺牲的1-5中的哪一个.如果您愿意牺牲#4而不是#5,您可以在空闲时间运行批处理以生成数字.但是,如果你把一个企业用户(或融资人)面前这个名单,他们会问你遵守1-4为#5是一个纯粹的技术问题(给他们),因此他们不希望对它感到困扰.如果是这种情况,可能的策略是:

  • 执行预先生成发票所需的所有可能计算,将发票编号生成步骤保留为最后一步.这将确保可能发生的任何异常,发生生成号码前,也确保锁被采取的时间很短的量,从而也不会影响应用程序的并发性或性能太多.
  • 保留一个单独的表(例如,DOCUMENT_SEQUENCE)以跟踪上次生成的数字.
  • 在保存发票之前,在序列表上进行独占的行级锁定(例如,隔离级别SERIALIZABLE),找到要使用的所需序列值并立即保存发票.这不应该花费太多时间,因为读取一行,增加其值并保存记录应该是足够短的操作.如果可能,将此短事务作为主要事务的嵌套事务.
  • 保持足够的数据库超时,以便等待SERIALIZABLE锁定的并发线程不会超时太快.
  • 将整个操作保持在重试循环中,在完全放弃之前重试至少10次.这将确保如果锁定队列建立得太快,则在完全放弃之前仍然会尝试几次操作.许多商业包装的重试次数高达40,60或100.

除此之外,如果可能并且您的数据库设计准则允许,请在发票编号列上添加一个唯一约束,以便不会以任何代价存储重复值.

Spring为您提供了实现此功能的所有工具.

我有一个示例应用程序,演示了如何使用所有这些部分.