通过简单计算(并发问题)增加最大值的新行

时间:2017-01-05 11:32:37

标签: java spring oracle hibernate

在我的java / spring / hibernate应用程序中,我需要生成yyxxxxxxxx格式的发票号码(yy - 当前年份,xxxxxxxx - 每年从1开始的数字序列,总共10位数。)

例如,明年的第一个数字将是1800000001。

我使用的是Oracle 11g快递版。该应用程序在2台Tomcat服务器上同时运行。

我不确定如何处理可能的重复项 - 或者更确切地说是唯一的约束违规,因为InvoiceNumber列存在唯一约束。 (GeneratedInvoiceNumbers表中有3列 - ID(PK,自动递增),InvoiceNumberCreatedOn。)

到目前为止,该应用程序检索max(ID)行,生成新的发票编号并插入新行。由于多个线程/服务器可能选择相同的max(ID)并因此生成/插入相同的发票号,因此应用程序捕获Spring DataIntegrityViolationException抛出,增加发票号并继续尝试插入它直到成功(与一些max_attempts_limit集)。它有效,但似乎不是一个干净的解决方案。

我想过将发票号生成逻辑放入存储过程中 - 应该可以锁定它并且当时只允许执行一次。但是接下来我将不得不处理一个不同的例外情况,这会让我现在处于原状。

有没有更好的方法来解决这个问题?

编辑:虽然基本上我并不真正关心每个生成的数字。我只需要知道每年的最大发票号码以生成下一个。我可以像这样制作结构:

ID, Year, LastNumber

并且如果当前年份行尚不存在则执行INSERT,否则UPDATE会增加LastNumber - 这在并发环境中似乎更容易。

3 个答案:

答案 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所述)