(WIP) Row locking when invoicing and job updating orders with skip locked or skip wait

pull/8523/head
Gauthier LO 2025-05-14 11:23:40 +02:00
parent 31d978d66a
commit 001e527bae
9 changed files with 74 additions and 9 deletions

View File

@ -28,6 +28,7 @@ import org.jeecg.modules.quartz.service.IQuartzJobService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.dao.CannotAcquireLockException;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
@ -267,7 +268,7 @@ public class InvoiceController {
} catch (IOException | ParseException e) {
log.error(e.getMessage());
return Result.error("Sorry, server error, please try later");
} catch (MessagingException e) {
} catch (MessagingException | InterruptedException e) {
throw new RuntimeException(e);
}
}
@ -436,7 +437,7 @@ public class InvoiceController {
} catch (IOException | ParseException e) {
log.error(e.getMessage());
return Result.error("Sorry, server error, please try later");
} catch (MessagingException | TemplateException e) {
} catch (MessagingException | TemplateException | InterruptedException e) {
throw new RuntimeException(e);
}
}
@ -1325,4 +1326,20 @@ public class InvoiceController {
throw new RuntimeException(e);
}
}
@PostMapping("/editOrderTest")
public Result<?> editOrderTest(@RequestBody Map<String, String> params) {
System.out.println("Edit Order Test : " + params.get("orderId"));
String orderId = params.get("orderId");
try {
PlatformOrder orderToEdit = platformOrderService.selectForUpdateSkipLock(orderId);
orderToEdit.setCanSend("2");
boolean edited = platformOrderService.updateById(orderToEdit);
return Result.OK(edited);
} catch (Exception e) {
if(e instanceof CannotAcquireLockException) {
return Result.error("Order is already being edited by another user");
}
throw new RuntimeException(e);
}
}
}

View File

@ -123,7 +123,7 @@ public class ShippingInvoiceFactory {
public ShippingInvoice createShippingInvoice(String customerId, List<String> orderIds, String type, String start, String end) throws UserException {
log.info("Creating an invoice with arguments:\n client ID: {}, order IDs: {}]", customerId, orderIds);
// find orders and their contents of the invoice
Map<PlatformOrder, List<PlatformOrderContent>> uninvoicedOrderToContent = platformOrderService.fetchOrderData(orderIds);
Map<PlatformOrder, List<PlatformOrderContent>> uninvoicedOrderToContent = platformOrderService.fetchOrderDataForUpdate(orderIds);
Set<PlatformOrder> platformOrders = uninvoicedOrderToContent.keySet();
List<SavRefundWithDetail> savRefunds = savRefundWithDetailService.findUnprocessedRefundsByClient(customerId);
List<String> shopIds = platformOrders.stream()
@ -166,10 +166,12 @@ public class ShippingInvoiceFactory {
* channel price, this exception will be thrown.
*/
@Transactional
public CompleteInvoice createCompleteShippingInvoice(String username, String customerId, BigDecimal balance, List<String> orderIds, String shippingMethod, String start, String end) throws UserException, MessagingException {
public CompleteInvoice createCompleteShippingInvoice(String username, String customerId, BigDecimal balance, List<String> orderIds, String shippingMethod, String start, String end) throws UserException, MessagingException, InterruptedException {
log.info("Creating a complete invoice for \n client ID: {}, order IDs: {}]", customerId, orderIds);
// find orders and their contents of the invoice
Map<PlatformOrder, List<PlatformOrderContent>> uninvoicedOrderToContent = platformOrderService.fetchOrderData(orderIds);
Map<PlatformOrder, List<PlatformOrderContent>> uninvoicedOrderToContent = platformOrderService.fetchOrderDataForUpdate(orderIds);
System.out.println("Sleeping for 1 minute for testing");
Thread.sleep(60000);
Set<PlatformOrder> platformOrders = uninvoicedOrderToContent.keySet();
List<SavRefundWithDetail> savRefunds = savRefundWithDetailService.findUnprocessedRefundsByClient(customerId);
List<String> shopIds = platformOrders.stream()

View File

@ -46,7 +46,9 @@ public interface PlatformOrderContentMapper extends BaseMapper<PlatformOrderCont
* @param orderIds Order IDs
* @return order contents
*/
// TODO : check all usage and update if need to use FOR UPDATE instead
List<PlatformOrderContent> fetchOrderContent(List<String> orderIds);
List<PlatformOrderContent> fetchOrderContentForUpdate(List<String> orderIds);
List<SkuDetail> searchSkuDetail(List<String> skuIDs);
@ -84,4 +86,5 @@ public interface PlatformOrderContentMapper extends BaseMapper<PlatformOrderCont
List<ShoumanOrderContent> searchShoumanOrderContent();
List<ShoumanOrderContent> searchShoumanOrderContentByPlatformOrderId(@Param("platformOrderId") String platformOrderId);
}

View File

@ -1,6 +1,7 @@
package org.jeecg.modules.business.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.annotations.Param;
import org.jeecg.modules.business.domain.api.mabang.getorderlist.Order;
import org.jeecg.modules.business.domain.api.yd.YDTrackingNumberData;
@ -255,4 +256,8 @@ public interface PlatformOrderMapper extends BaseMapper<PlatformOrder> {
@Param("start") String startDate, @Param("end") String endDate,
@Param("order") String order, @Param("column") String column,
@Param("offset") Integer offset, @Param("size") Integer pageSize);
@MapKey("id")
Map<String, PlatformOrder> selectBatchIdsForUpdate(@Param("ids") List<String> orderIds);
PlatformOrder selectForUpdateSkipLock(@Param("id") String orderId);
}

View File

@ -76,6 +76,14 @@
WHERE erp_status &lt;&gt; 5
AND platform_order_id IN <foreach collection="list" index="i" item="item" open="(" separator="," close=")">#{item}</foreach>
</select>
<select id="fetchOrderContentForUpdate" parameterType="java.lang.String"
resultType="org.jeecg.modules.business.entity.PlatformOrderContent">
SELECT *
FROM platform_order_content
WHERE erp_status &lt;&gt; 5
AND platform_order_id IN <foreach collection="list" index="i" item="item" open="(" separator="," close=")">#{item}</foreach>
FOR UPDATE;
</select>
<select id="searchSkuDetail"
resultType="org.jeecg.modules.business.vo.SkuDetail"

View File

@ -1380,4 +1380,17 @@
END as has_desynced_sku
FROM orders
</select>
<select id="selectBatchIdsForUpdate" resultType="org.jeecg.modules.business.entity.PlatformOrder">
SELECT * FROM platform_order
WHERE id IN
<foreach collection="ids" separator="," open="(" close=")" index="index" item="id">
#{id}
</foreach>
FOR UPDATE;
</select>
<select id="selectForUpdateSkipLock" resultType="org.jeecg.modules.business.entity.PlatformOrder">
SELECT * FROM platform_order
WHERE id = #{id}
FOR UPDATE SKIP LOCKED; -- todo : NO WAIT is recognized but not SKIP LOCKED
</select>
</mapper>

View File

@ -108,6 +108,8 @@ public interface IPlatformOrderService extends IService<PlatformOrder> {
*/
Map<PlatformOrder, List<PlatformOrderContent>> fetchOrderData(List<String> orderIds);
Map<PlatformOrder, List<PlatformOrderContent>> fetchOrderDataForUpdate(List<String> orderIds);
Map<PlatformOrder, List<PlatformOrderContent>> fetchOrderDataByInvoiceCode(String invoiceCode);
@ -293,4 +295,6 @@ public interface IPlatformOrderService extends IService<PlatformOrder> {
List<PlatformOrderFront> listByClientAndShops(String clientId, List<String> shopIds, String startDate, String endDate, String invoicingMethod, Integer pageNo, Integer pageSize, List<String> warehouses, String order, String column);
int countListByClientAndShops(String clientId, List<String> shopIDs, String start, String end, String invoicingMethod, List<String> warehouses);
PlatformOrder selectForUpdateSkipLock(String orderId);
}

View File

@ -265,7 +265,7 @@ public class PlatformOrderShippingInvoiceService {
* @throws IOException exception related to invoice file IO.
*/
@Transactional
public InvoiceMetaData makeCompleteInvoice(ShippingInvoiceOrderParam param) throws UserException, ParseException, IOException, MessagingException {
public InvoiceMetaData makeCompleteInvoice(ShippingInvoiceOrderParam param) throws UserException, ParseException, IOException, MessagingException, InterruptedException {
String username = ((LoginUser) SecurityUtils.getSubject().getPrincipal()).getUsername();
// Creates invoice by factory
CompleteInvoice invoice = factory.createCompleteShippingInvoice(username, param.clientID(), null, param.orderIds(), param.getType(), param.getStart(), param.getEnd());
@ -282,7 +282,7 @@ public class PlatformOrderShippingInvoiceService {
* @throws IOException
*/
@Transactional
public InvoiceMetaData makeCompleteInvoicePostShipping(ShippingInvoiceParam param, String method, String ... user) throws UserException, ParseException, IOException, MessagingException {
public InvoiceMetaData makeCompleteInvoicePostShipping(ShippingInvoiceParam param, String method, String ... user) throws UserException, ParseException, IOException, MessagingException, InterruptedException {
String username = user.length > 0 ? user[0] : ((LoginUser) SecurityUtils.getSubject().getPrincipal()).getUsername();
List<PlatformOrder> platformOrderList;
if(method.equals(POSTSHIPPING.getMethod())) {
@ -711,6 +711,8 @@ public class PlatformOrderShippingInvoiceService {
String internalCode = entry.getKey().getClient().getInternalCode();
invoiceList.add(new InvoiceMetaData("", "error", internalCode, clientId, e.getMessage()));
log.error(e.getMessage());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.gc();
}

View File

@ -37,7 +37,6 @@ import java.util.function.Predicate;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.*;
import static org.jeecg.modules.business.entity.Invoice.InvoicingMethod.*;
/**
* @Description:
@ -276,7 +275,7 @@ public class PlatformOrderServiceImpl extends ServiceImpl<PlatformOrderMapper, P
return platformOrderMap.queryQuantities(client.getId());
}
}
// TODO update to FOR UPDATE
@Override
public Map<PlatformOrder, List<PlatformOrderContent>> findUninvoicedOrders(List<String> shopIds, Date begin, Date end, List<String> warehouses) {
List<PlatformOrder> orderList = platformOrderMap.findUninvoicedOrders(shopIds, begin, end, warehouses);
@ -307,6 +306,7 @@ public class PlatformOrderServiceImpl extends ServiceImpl<PlatformOrderMapper, P
return orderContents.stream().collect(groupingBy(platformOrderContent -> orderMap.get(platformOrderContent.getPlatformOrderId())));
}
// TODO: maybe duplicate this for non invoicing usage
@Override
public Map<PlatformOrder, List<PlatformOrderContent>> fetchOrderData(List<String> orderIds) {
List<PlatformOrder> orderList = platformOrderMap.selectBatchIds(orderIds);
@ -315,6 +315,12 @@ public class PlatformOrderServiceImpl extends ServiceImpl<PlatformOrderMapper, P
return orderContents.stream().collect(groupingBy(platformOrderContent -> orderMap.get(platformOrderContent.getPlatformOrderId())));
}
@Override
public Map<PlatformOrder, List<PlatformOrderContent>> fetchOrderDataForUpdate(List<String> orderIds) {
Map<String, PlatformOrder> ordersMapById = platformOrderMap.selectBatchIdsForUpdate(orderIds);
List<PlatformOrderContent> orderContents = platformOrderContentMap.fetchOrderContentForUpdate(orderIds);
return orderContents.stream().collect(groupingBy(platformOrderContent -> ordersMapById.get(platformOrderContent.getPlatformOrderId())));
}
@Override
public Map<PlatformOrder, List<PlatformOrderContent>> fetchOrderDataByInvoiceCode(String invoiceCode) {
List<PlatformOrder> orderList = platformOrderMap.getPlatformOrdersByInvoiceNumber(invoiceCode);
List<String> orderIds = orderList.stream().map(PlatformOrder::getId).collect(toList());
@ -600,4 +606,9 @@ public class PlatformOrderServiceImpl extends ServiceImpl<PlatformOrderMapper, P
}
return platformOrderMap.countListByClientAndShops(clientId, shopIDs, erpStatuses, warehouses, start, end);
}
@Override
public PlatformOrder selectForUpdateSkipLock(String orderId) {
return platformOrderMap.selectForUpdateSkipLock(orderId);
}
}