在我的 java/spring/hibernate 应用程序中,我需要生成格式为 yyxxxxxxxx
的发票号码(yy - 当前年份,xxxxxxxx - 每年从 1 开始的编号序列,总共 10 位数字。)
例如,明年的第一个数字将为 1800000001。
我使用Oracle 11g Express版。该应用程序同时在 2 个 Tomcat 服务器上运行。
我不确定如何处理可能的重复项 - 或者更确切地说,违反唯一约束,因为 InvoiceNumber
列存在唯一约束。 (GenerateInvoiceNumbers
表中有 3 列 - ID
(PK,自动递增)、InvoiceNumber
、CreatedOn
。)
到目前为止,应用程序检索具有 max(ID)
的行,生成新的发票编号并插入新行。由于多个线程/服务器可能选择相同的 max(ID)
并因此生成/插入相同的发票编号,因此应用程序捕获 Spring 抛出的 DataIntegrityViolationException
,增加发票编号并保持尝试插入它直到成功(设置了一些max_attempts_limit
)。它有效,但似乎不是一个干净的解决方案。
我考虑将发票编号生成逻辑放入存储过程中 - 应该可以对其加锁并一次只允许执行一次。但随后我将不得不处理一个不同的异常,这将使我达到现在的水平。
有没有更好的方法来解决这个问题?
编辑:虽然本质上我并不真正关心每个生成的数字。我只需要知道每年的最大发票数量即可生成下一个。我可以制作这样的结构:
ID, Year, LastNumber
如果当前年份行尚不存在,则执行INSERT
,否则使用递增的LastNumberUPDATE
- 这在并发环境中似乎更容易执行。
最佳答案
不要使用 max(id),创建 oracle 序列并从序列中获取下一个值 - select nextval
。这是最可靠且不易出错的方法。即使您有 1 个应用程序,当多个线程尝试从数据库获取 max(id) 时,您也会发生竞争
或者,您可以对 1 个节点使用如下所示的内容:
synchonized (lock) {
long id = selectMaxFromDatabase(id);
id ++;
if (id % 2 != 0) {
id ++;
}
}
对于第二个节点:
synchonized (lock) {
long id = selectMaxFromDatabase(id);
id ++;
if (id % 2 == 0) {
id ++;
}
}
一个应用程序将插入奇数 ID,第二个应用程序将插入偶数 ID。
关于java - 插入新行,并通过简单计算增加最大值(并发问题),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41483993/