提问人:NicoESIEA 提问时间:11/10/2023 最后编辑:NicoESIEA 更新时间:11/10/2023 访问量:47
Java Spring 控制器能够同时管理多个 POST 查询,并根据正确的数据库状态进行处理
Java Spring controller able to manage multiple POST queries at the same time and process according to the correct database state
问:
我的例子很简单。
我创建了一个端点来从我的接口(或客户端或其他任何内容)获取 JSON 数据。在此示例中,我将收到一个包含一些颜色的 Car json。如果它是已知颜色,我们必须使用这种颜色,否则必须创建新颜色。
下面可以是 POST 端点控制器:
@RequestMapping(method = RequestMethod.POST, consumes = { "application/json" })
public Car digest(@RequestBody CarFormat body){
return CarsService.digest(body.document, body.car);
}
在 CarsService 摘要方法中,我必须验证每个属性并创建颜色(如果数据库中尚不存在颜色)。
private Car findColorOrCreate(CarFormat car) {
String colorReference = car.color.value;
Assert.notNull(colorReference, String.format("Color reference not found from: %s.", car));
if (colorService.existsByReference(colorReference)) return colorService.findByReference(colorReference);
return colorService.create(extractColor(car, colorReference));
}
正如我们所看到的,服务代码很简单,它检查颜色是否已经存在:
- 如果是,则返回颜色(由 colorService 中的 findByReference 返回)
- 否则,它会根据 extractColor 方法创建新颜色)
但是,我创建了一个单元测试(集成)来验证在当时非常接近发送两个查询时的行为:
@Test
public void should_createOneColorRecord_whenSimultaneousCalls_Endpoint() throws InterruptedException {
String sameColorReference = "red";
String json1 = "...car_id: 1, color:"+sameColorReference+"...";
String json1 = "...car_id: 2, color:"+sameColorReference+"...";
String json1 = "...car_id: 3, color:"+sameColorReference+"...";
String mimeType = "application/json";
CountDownLatch latch = new CountDownLatch(3);
executeAsyncRequest("/api/", mimeType, json1, latch);
executeAsyncRequest("/api/", mimeType, json2, latch);
executeAsyncRequest("/api/", mimeType, json3, latch);
latch.await();
assertThat(entityManager.createQuery("SELECT count(c) FROM Color c WHERE c.reference like '"+sameColorReference+"'").getSingleResult(), equalTo(Long.valueOf(1)));
}
(当然,在我的例子中,引用的是颜色名称,新颜色已经不存在,实际上数据库中根本没有颜色)
这是 executeAsyncRequest 的代码,我使用每个参数调用端点,但使用不同的 Thread 来模拟真实行为:
private void executeAsyncRequest(String endpoint, String mimeType, String json, CountDownLatch latch) {
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post(endpoint)
.contentType(mimeType)
.content(json);
new Thread(() -> {
try {
this.mockMvc.perform(builder)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
latch.countDown();
} catch (Exception e) {
throw new RuntimeException(e);
}
}).start();
}
最后,当我运行此测试时,预期结果必须是创建了 1 种颜色,但创建了 3 种颜色。 我尝试在控制器、服务和接口上添加 Transactional,也是 @Transactional(propagation = Propagation.REQUIRES_NEW)...... 但没有任何效果。
有人可以帮我解决这个问题吗? 非常感谢提前
答:
1赞
Andrew S
11/10/2023
#1
从评论中跟进 - 如果只有一个应用程序实例,那么一个简单的解决方案是使用双重锁定检查。例如:
class ColorService {
private static final Object lock = new Object();
public boolean existsByReference(ColorReference r) {
return ...
}
public Car create(Color color) {
// this code is synchronized across ColorService instances, and only a single Thread can access at a time
synchronized(lock) {
// double lock check - other Thread waiting to also create the new color will return since the new color was already created in another thread
if (existsByReference(color.getColorReference())) {
return;
}
// safe to create the new color
return ... // insert into DB
}
}
}
评论
0赞
NicoESIEA
11/10/2023
这是我的错误,关键字同步必须紧跟在公众之后,而不是正文里面 ^^谢谢
0赞
Gimby
11/10/2023
请注意:应用程序的单个实例并不一定意味着存在服务类的单个实例。锁定静态最终变量将更加万无一失。
0赞
Andrew S
11/10/2023
根据@Gimby的评论进行更新。
上一个:Java:许多线程或进程读取文件
下一个:Java 线程卡住
评论