Giving the following code which takes a request with a web shop order as JSON in Spring Boot. The order should be handed over to the method processJsonOrder
on which the order will be processed. After handing over, the web service should return OK to the client. Processing in processJsonOrder
should be async without blocking the return to the client.
@RestController
public class WebShopOrderController {
@Autowired
private OrderService orderService;
@PostMapping(value = "/wc-order")
public void getWcOrder(@RequestBody String jsonOrder, @RequestHeader Map<String, String> headers) {
log.info("order received");
// should be done async
processJsonOrder(jsonOrder);
return;
}
}
However, jsonOrder processed in processJsonOrder
should be processed one by one. Maybe by a queue, or there is a nice solution in Spring to do so. The idea is that jsonOrders are waiting when an order is executed within processJsonOrder
. Currently, when two order updated arrive within seconds, order will be inserted into the database by processJsonOrder
twice, as the thread processing the jsonOrder
has not reached the commit point yet. There is some code in the orderService
to check for duplicates on database level, but that doesn't help always.
The OrderService.java
@Service
public class OrderService {
@Async
@Transactional
public void processJsonOrder(WoocommerceOrder wcOrder) {
// performing business logic
}
}
I expect that there is an elegant solution for that, or maybe doing the processJson order Async is the wrong approach. Important is that the calling client gets back an answer immediately, but the order is processed afterwards asynchronously, but one by one.
What you're trying to do with your code is called reach a mutual exclusion to avoid a race condition - two or more processes or threads simultaneously reach a critical segment of your code and try to read and/or update the same piece of data, potentially causing a bug or error. In java , there is a built in feature to avoid this kind of problem: The synchronized
keyword.
First, on your Controller, configure the Thread(s):
@RestController
public class WebShopOrderController {
@Autowired
private OrderService orderService;
@PostMapping(value = "/wc-order")
public void getWcOrder(@RequestBody String jsonOrder, @RequestHeader Map<String, String> headers) {
log.info("order received");
// Will be done async
new Thread(() -> processJsonOrder(jsonOrder)).start();
return;
}
}
Then, just use the synchronized
keyword on your @Service
method:
@Service
public class OrderService {
@Transactional
public synchronized void processJsonOrder(WoocommerceOrder wcOrder) {
// Only one Thread at a time will be allowed to enter this area.
// performing business logic
}
}