在我的java / spring / hibernate应用程序中,我需要生成yyxxxxxxxx
格式的发票号码(yy - 当前年份,xxxxxxxx - 每年从1开始的数字序列,总共10位数。)
例如,明年的第一个数字将是1800000001。
我使用的是Oracle 11g快递版。该应用程序在2台Tomcat服务器上同时运行。
我不确定如何处理可能的重复项 - 或者更确切地说是唯一的约束违规,因为InvoiceNumber
列存在唯一约束。 (GeneratedInvoiceNumbers
表中有3列 - ID
(PK,自动递增),InvoiceNumber
,CreatedOn
。)
到目前为止,该应用程序检索max(ID)
行,生成新的发票编号并插入新行。由于多个线程/服务器可能选择相同的max(ID)
并因此生成/插入相同的发票号,因此应用程序捕获Spring DataIntegrityViolationException
抛出,增加发票号并继续尝试插入它直到成功(与一些max_attempts_limit
集)。它有效,但似乎不是一个干净的解决方案。
我想过将发票号生成逻辑放入存储过程中 - 应该可以锁定它并且当时只允许执行一次。但是接下来我将不得不处理一个不同的例外情况,这会让我现在处于原状。
有没有更好的方法来解决这个问题?
编辑:虽然基本上我并不真正关心每个生成的数字。我只需要知道每年的最大发票号码以生成下一个。我可以像这样制作结构:
ID, Year, LastNumber
并且如果当前年份行尚不存在则执行INSERT
,否则UPDATE
会增加LastNumber - 这在并发环境中似乎更容易。
答案 0 :(得分:1)
不要使用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,第二个应用程序甚至是ids。
答案 1 :(得分:0)
如果我理解正确的问题,应该是。
这是一个并发问题,其中两个应用程序可能会尝试同时在您的表上写入。因此,您可以在应用程序上开始编写范围0-5.0000.0000
,另一个来自10.0000.0000-5.000.0000
。此外,您不需要关心他们编写与00.0000.0001
相同的编号,因为他们是从不同的地方编写的,然后由于并发问题,您不会最终覆盖/丢失主键。
答案 2 :(得分:0)
正确的答案是使用序列,让数据库处理它。易于实施,不易出错(如Anton所述)