【发布时间】:2020-11-09 11:33:34
【问题描述】:
有 Spring Boot 应用程序。 控制器->服务->存储库
问题:如果用户向服务器发送多个 post 请求,每个请求都会创建自己的线程。
并且 @Service 检查如下 - 不起作用:
@Service
@Transactional
public class MyService {
public List<SingleRecord> save( List<SingleRecord> recs, Integer userId){
List<SingleRecord> recsFromDataBase = singleRecordRepository.findAllByUserId(userId);
if ( recsFromDataBase.size() > 0 ){
throw new MyException("User already has records!");
} else {
return singleRecordRepository.saveAll(recs);
}
}
}
例如:我们有 10 个来自用户的并发请求。 (在连接问题期间,他按了 10 次“发送”按钮)他们都到达singleRecordRepository.saveAll(recs); 并且都坚持到基地。 (因为多线程)
因此服务检查变得毫无用处。
我尝试将 Serializable 用于 @Transactional 隔离级别 - 但在单独的用户使用此服务时会导致大量死锁和错误。
Java 文章作者建议对发布请求使用令牌 - 这看起来不像是优雅的解决方案。
现在我将创建线程安全的 Singleton 来检查用户请求是否重复。这太可怕了……
如何使服务检查正常工作?
https://github.com/PilVadim/FSStackOverflow
与我的应用程序相同的服务示例。 重现错误:
-
启动应用程序。打开swagger UI http://localhost:8085/swagger-ui.html#
-
对于 Chrome - F12->网络->将“在线”更改为“GPRS”;
-
在swagger ui上,打开POST请求,将body中的
id改为null,然后按几次'Execute'; -
将“GPRS”改回“在线”; 几乎所有请求都会成功完成。 这意味着 - 入住服务无法正常工作。
【问题讨论】:
-
您应该使您的服务具有幂等性。这意味着您应该能够处理已创建的记录。
-
@Simon Martinelli,但是怎么做?有什么最佳做法吗?
-
不,这取决于您的要求。什么是单曲记录?它可以通过我的唯一标识符来识别吗?然后你可以使用 Merge 而不是 Insert
-
是的,它有一个唯一标识符。它是代理键,在用户请求中它们都为空。 Spring如何发现ID为空的对象是一个对象?对于合并工作,我们需要由所有字段组成的复合键。
-
听起来像是 XY 问题,为什么不直接在第一次按下按钮时禁用它?
标签: java spring multithreading hibernate duplicates