带有Hibernate财务序列生成的Spring JPA [英] Spring JPA with Hibernate fiscal sequence generation

查看:87
本文介绍了带有Hibernate财务序列生成的Spring JPA的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In my App I'm modelling an Invoice. In my country (Italy) every invoice must have a unique sequential number without holes, that every year have to restart from 1.

对于实现它的最佳方法,我进行了漫长而艰辛的思考,但是我还没有找到关于此的很好的指南.现在,我有一个JpaRepository,其中有一个自定义的同步save()方法,在该方法中,我获取了上次使用的ID:

I thought long and hard about the best way to implement it but I have not found a good guide about this. For now I have a JpaRepository where I've my custom synchronized save() method in which I get the last id used:

SELECT MAX(numero) FROM Invoice WHERE YEAR(date) = :year

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

The problem of this approach is that is not very safe because the developer should know that the save should be done only with that particular service.

相反,我想要更多对开发人员隐藏的方法. 我以为在@EntityListeners中使用@Prepersist方法.听起来不错,但在此类中获取实体管理器并不是那么简单.... so可能不是最佳位置...

Instead I'd like more an approach that is hidden to the developer. I thought to use a @Prepersist method in a @EntityListeners. This sounds good but do get entity manager inside this class is not so simple....so maybe is not the optimal place...

最后我想到了休眠拦截器....

Finally I thought about Hibernate Interceptor....

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

Please give me some hints. The problem seems a quite generic problem; so maybe there is yet a good practice to follow.

谢谢

推荐答案

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

This problem can be broken down into the following requirements:

  1. 顺序唯一:从给定值(例如1000001)开始按顺序生成数字,然后始终以固定值(例如1)递增.
  2. 没有空格:数字之间不得有任何空格.因此,如果生成的第一个数字是1000001,增量是1,并且到目前为止已经生成了200个数字,则最新的数字应该是1000201.
  3. 并发性:多个进程必须能够同时生成数字.
  4. 创建时生成:数字必须在创建记录时生成.
  5. 没有排他锁:生成数字不需要排他锁.
  1. Sequentially unique: Generate numbers in a sequence, starting from a given value (say, 1000001) and then always incrementing by a fixed value (say, 1).
  2. No gaps: There must not be any gaps between the numbers. So, if the first number generated is 1000001, the increment is 1 and 200 numbers have been generated so far, the latest number should be 1000201.
  3. Concurrency: Multiple processes must be able to generate the numbers at the same time.
  4. Generation at creation: The numbers must be generated at the time of creation of a record.
  5. No exclusive locks: No exclusive locks should be required for generating the numbers.

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

Any solution can only comply with 4 out of these 5 requirements. For example, if you want to guarantee 1-4, each process will need to take locks so that no other process can generate and use the same number that it has generated. Therefore, imposing 1-4 as requirements will mean that 5 will have to be let gone of. Similarly, if you want to guarantee 1, 2, 4 and 5, you need to make sure that only one process (thread) generates a number at a time because uniqueness cannot be guaranteed in a concurrent environment without locking. Continue this logic and you will see why it is impossible to guarantee all of these requirements at the same time.

现在,解决方案取决于您愿意牺牲1-5个中的哪一个.如果您愿意牺牲#4而不是#5,则可以在空闲时间运行批处理以生成数字.但是,如果您将此列表放在业务用户(或财务人员)面前,他们将要求您遵守1-4,因为#5对他们而言纯粹是技术问题,因此他们不希望成为困扰着它.如果是这样,可能的策略是:

Now, the solution depends on which one out of 1-5 you are willing to sacrifice. If you are willing to sacrifice #4 but not #5, you can run a batch process during idle hours to generate the numbers. However, if you put this list in front of a business user (or a finance guy), they will ask you to comply with 1-4 as #5 is a purely technical issue (to them) and therefore they would not want to be bothered with it. If that is the case, a possible strategy is:

  • 执行所有生成发票所需的可能的计算,并保持发票号生成步骤为最后一步.这样可以确保在生成数字之前可能发生的任何异常,并确保在很短的时间内进行锁定,从而不会对应用程序的并发性或性能造成太大的影响.
  • 保留一个单独的表(例如,DOCUMENT_SEQUENCE)以跟踪上一个生成的数字.
  • 仅在保存发票之前,对序列表进行排他的行级锁定(例如,隔离级别SERIALIZABLE),找到要使用的所需序列值并立即保存发票.这应该不会花费太多时间,因为读取一行,增加其值并保存一条记录应该足够短.如有可能,请将此短事务与主要事务嵌套在一起.
  • 保持足够的数据库超时时间,以使等待SERIALIZABLE锁的并发线程不会太快超时.
  • 将整个操作保留在重试循环中,至少重试10次,然后完全放弃.这将确保如果锁定队列建立得太快,则在完全放弃之前仍会尝试几次操作.许多商业软件包的重试计数高达40、60或100.
  • Perform all possible computation required to generate an invoice upfront, keeping the invoice number generation step as the very last step. This will ensure that any exceptions that can occur, happen before the number is generated and also to make sure that a lock is taken for a very short amount of time, thereby not affecting the concurrency or performance of the application too much.
  • Keep a separate table (for example, DOCUMENT_SEQUENCE) to keep a track of the last generated number.
  • Just before saving an invoice, take an exclusive row-level lock on the sequence table (say, isolation level SERIALIZABLE), find the required sequence value to use and save the invoice immediately. This should not take too much time because reading a row, incrementing its value and saving a record should be a short enough operation. If possible, make this short transaction a nested transaction to the main one.
  • Keep a decent-enough database timeout so that concurrent threads waiting for a SERIALIZABLE lock do not time out too fast.
  • Keep this whole operation in a retry loop, retrying at least 10 times before giving up completely. This will ensure that if the lock queue builds up too fast, the operations are still tried a few times before giving up totally. Many commercial packages have retry count as high as 40, 60 or 100.

此外,在数据库设计准则允许的情况下,如果可能的话,请在发票编号列上设置唯一的约束,以使重复的值不会被花费任何费用.

In addition to this, if possible and allowed by your database design guidelines, put a unique constraint on the invoice number column so that duplicate values are not stored at any cost.

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

Spring gives you all the tools to implement this.

  • Transactions: Through the @Transactional annotation.
  • Serialization: Through the isolation attribute of the @Transactional annotation.
  • Database access: Through Spring JDBC, Spring ORM and Spring Data JPA.
  • Retries: Through Spring Retry.

我有一个示例应用,该示例演示了如何使用所有这些内容在一起.

I have a sample app that demonstrates using all these pieces together.

这篇关于带有Hibernate财务序列生成的Spring JPA的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆