Java 如何防止在两个事务中产生重复值

Java 如何防止在两个事务中产生重复值,java,mysql,spring,hibernate,race-condition,Java,Mysql,Spring,Hibernate,Race Condition,我有一个Spring引导应用程序,它通过RESTAPI获取请求 在收到这样一个请求后,我为我的对象生成一个offerID。这不是数据库的主键,它只是我们与客户交流的一个人类可读的数字,而不是普通的数据库id 对于我们在2016年5月18日收到的第一个请求,此报价id看起来像“16051801” 这将作为字符串存储在数据库中,以选择当前的“最大值”,类似于select count(r)FROM Request as r,其中r.offerId类似于?1% 我只需输入当前日期“160518”,得到存

我有一个Spring引导应用程序,它通过RESTAPI获取请求

在收到这样一个请求后,我为我的对象生成一个offerID。这不是数据库的主键,它只是我们与客户交流的一个人类可读的数字,而不是普通的数据库id

对于我们在2016年5月18日收到的第一个请求,此报价id看起来像“16051801”

这将作为字符串存储在数据库中,以选择当前的“最大值”,类似于
select count(r)FROM Request as r,其中r.offerId类似于?1%
我只需输入当前日期“160518”,得到存储在那里的请求数,然后将计数+1作为下一个值

正如您可能已经猜到的,这会带来一些问题

一方面,这很好,因为我们现在在这个网站上没有那么多的流量

但是,如果我们有两个请求在几乎同一时刻出现,我会得到类似“竞争条件”的东西,我会创建两次相同的offerId,这并不酷

我已经在此列中添加了一个唯一约束,它阻止创建具有相同值的多个条目,但在这种情况下,两个请求中的一个失败

我已经试过了:

try {
     super.create(customerRequest);
} catch (DataIntegrityViolationException ex) {
     if (ex.getCause() instanceof ConstraintViolationException) {
         ConstraintViolationException constraintViolationException = (ConstraintViolationException) ex.getCause();
         if (constraintViolationException.getCause().getMessage().contains("'UC_CUSTOMERREQUESTOFFERNUMBER_COL'")){
             customerRequest.setOfferNumber(generateOfferNumber(customerRequest));
                 super.create(customerRequest);
        }
    }
}
但是在这种情况下,我总是会得到一个类似于
的消息,如果出现异常,不要刷新会话

在这种情况下,我基本上能够处理和解决问题,但Spring/Hibernate阻止我这样做

因此,真正的问题是,以一种既不粗糙(我也尝试了Thread.sleep(),感觉不太好……)又可伸缩(将来也应该适用于应用程序的多个实例)的方式处理此类“问题”的最佳实践是什么


作为一个数据库,我们使用MySQL,据我所知,我们无法将此“ID生成”移动到数据库中。

关于将ID生成逻辑移动到DB的最后想法,是的,您可以这样做。 您可以为非pk列创建序列。您可以在序列中包含逻辑,当发生任何插入时,将使用hibernate自动调用该逻辑

MySQL和Hibernate都可以通过使用顺序的概念来解决您的问题


我们有一个场景,在这个场景中,我们必须生成一个格式为“PK-ABCD-001”的自定义密钥,例如“545455-ABCD-008”。使用sequence我们已经解决了这个问题。

关于将ID生成逻辑移动到DB的最后想法,是的,您可以这样做。 您可以为非pk列创建序列。您可以在序列中包含逻辑,当发生任何插入时,将使用hibernate自动调用该逻辑

MySQL和Hibernate都可以通过使用顺序的概念来解决您的问题


我们有一个场景,在这个场景中,我们必须生成一个格式为“PK-ABCD-001”的自定义密钥,例如“545455-ABCD-008”。使用顺序,我们已经解决了这个问题。

只需抛出几个想法:

  • 在Oracle中,我们有一个“序列”,每次查询时,它都会给您一个新的数字。虽然MySql中没有直接替换,但是应该可以通过使用带有自动增量列的虚拟表来模拟效果

  • 我曾经编写过一个小的存储过程来执行序列号生成(它负责日期前缀和每天自动重置序列等)。自治事务用于避免并发事务相互阻塞。MySQL中的自治事务并没有直接替代(同样),但有一些技巧可以实现类似的结果

  • 编写一个执行“序列生成”的小网络服务,并以您想要的任何方式(SOAP、REST等)公开它。考虑到逻辑的简单性,我认为您不需要它具有那么大的可伸缩性。将当前序列存储在DB中(以便它是可恢复的),并使序列生成逻辑具有自己的短事务。例如,您可以通过“缓存”序列号来提高性能(例如,每次将以DB为单位的数字增加100,并且您可以在不访问DB的情况下返回100个序列号。)


  • 只要抛出几个想法:

  • 在Oracle中,我们有一个“序列”,每次查询时,它都会给您一个新的数字。虽然MySql中没有直接替换,但是应该可以通过使用带有自动增量列的虚拟表来模拟效果

  • 我曾经编写过一个小的存储过程来执行序列号生成(它负责日期前缀和每天自动重置序列等)。自治事务用于避免并发事务相互阻塞。MySQL中的自治事务并没有直接替代(同样),但有一些技巧可以实现类似的结果

  • 编写一个执行“序列生成”的小网络服务,并以您想要的任何方式(SOAP、REST等)公开它。考虑到逻辑的简单性,我认为您不需要它具有那么大的可伸缩性。将当前序列存储在DB中(以便它是可恢复的),并使序列生成逻辑具有自己的短事务。例如,您可以通过“缓存”序列号来提高性能(例如,每次将以DB为单位的数字增加100,并且您可以在不访问DB的情况下返回100个序列号。)


  • MySQL本身没有序列的概念。 最接近的是自动增量

    对于MyISAM表,您需要一种简单的方法。只需将订单的日期保存在表中,根据日期自动递增即可

    这就是MYSQL手册所说的:

    对于MyISAM表,您可以在多列索引中的辅助列上指定自动增量。在这种情况下,将计算自动增量列的生成值
    @Transactional(isolation=Isolation.SERIALIZABLE)