변경 파일 목록
총 3개
Util (1)
OrderGoodsLockSortUtil.phpOrder/변경 파일 코드
추가삭제
변경 전OrderNew.php
| use Session; | |
| use Framework\Utility\DateTimeUtils; | |
| use Component\Payment\Tosspay\TosspayConfig; | |
| /** | |
| // 현재 상태 수정 처리 | |
| $arrWhere[] = 'sno = ?'; | |
| $arrWhere[] = 'orderNo = ?'; | |
| foreach ($arrData['sno'] as $key => $val) { | |
| // 주문 로그 저장 | |
| $logCode01 = $this->getOrderStatusAdmin($arrData['orderStatus'][$key]) . '(' . $arrData['orderStatus'][$key] . ')'; | |
| $orderLogData = $this->db->query_fetch($strSQL, $arrBind, false); | |
| if ((int)$orderLogData['cnt'] == 0 || $isClaim === true) { | |
| foreach ($aGoodsSno['sno'] as $v) { | |
| $aValue = array(); | |
| $aOrderGoodsData = $this->getOnlyOrderGoodsData($orderNo, $v); | |
| // 인서트 or 업데이트 | |
| $aMemberOrderGoodsCountData = $this->getMemberOrderGoodsCountData($aOrderData['memNo'], $aOrderGoodsData['goodsNo']); | |
변경 후OrderNew.php
| use Session; | |
| use Framework\Utility\DateTimeUtils; | |
| use Component\Payment\Tosspay\TosspayConfig; | |
| use Util\Order\OrderGoodsLockSortUtil; | |
| /** | |
| // 현재 상태 수정 처리 | |
| $arrWhere[] = 'sno = ?'; | |
| $arrWhere[] = 'orderNo = ?'; | |
| // [데드락 회피] 같은 주문의 여러 es_orderGoods 행을 트랜잭션마다 동일한 순서(sno 오름차순)로 잠그도록 통일 | |
| // 클레임(취소/교환)이 동시 실행될 때 행 잠금 순서가 엇갈려 발생하던 교착(패턴 L)을 예방 | |
| // 키를 유지하는 asort 로 정렬하므로 아래 $arrData['orderStatus'][$key] 쌍 대응은 그대로 보존 | |
| if (is_array($arrData['sno'])) { | |
| asort($arrData['sno'], SORT_NUMERIC); | |
| } | |
| foreach ($arrData['sno'] as $key => $val) { | |
| // 주문 로그 저장 | |
| $logCode01 = $this->getOrderStatusAdmin($arrData['orderStatus'][$key]) . '(' . $arrData['orderStatus'][$key] . ')'; | |
| $orderLogData = $this->db->query_fetch($strSQL, $arrBind, false); | |
| if ((int)$orderLogData['cnt'] == 0 || $isClaim === true) { | |
| // [데드락 회피] es_memberOrderGoodsCount 잠금 순서 통일을 위해 goodsNo 오름차순으로 처리(상세: OrderGoodsLockSortUtil) | |
| $aResolvedGoodsList = OrderGoodsLockSortUtil::resolveOrderGoodsSortedByGoodsNo($this, $orderNo, $aGoodsSno['sno']); | |
| foreach ($aResolvedGoodsList as $aResolved) { | |
| $aValue = array(); | |
| $aOrderGoodsData = $aResolved['data']; | |
| // 인서트 or 업데이트 | |
| $aMemberOrderGoodsCountData = $this->getMemberOrderGoodsCountData($aOrderData['memNo'], $aOrderGoodsData['goodsNo']); | |
변경 전Order.php
| use Framework\Utility\DateTimeUtils; | |
| use Framework\Utility\KafkaUtils; | |
| use Bundle\Component\Payment\Tosspay\TosspayConfig; | |
| /** | |
| * 주문 class | |
| // 현재 상태 수정 처리 | |
| $arrWhere[] = 'sno = ?'; | |
| $arrWhere[] = 'orderNo = ?'; | |
| foreach ($arrData['sno'] as $key => $val) { | |
| // 주문 로그 저장 | |
| $logCode01 = $this->getOrderStatusAdmin($arrData['orderStatus'][$key]) . '(' . $arrData['orderStatus'][$key] . ')'; | |
| $orderLogData = $this->db->query_fetch($strSQL, $arrBind, false); | |
| if ((int)$orderLogData['cnt'] == 0 || $isClaim === true) { | |
| foreach ($aGoodsSno['sno'] as $v) { | |
| $aValue = array(); | |
| $aOrderGoodsData = $this->getOnlyOrderGoodsData($orderNo, $v); | |
| // 인서트 or 업데이트 | |
| $aMemberOrderGoodsCountData = $this->getMemberOrderGoodsCountData($aOrderData['memNo'], $aOrderGoodsData['goodsNo']); | |
변경 후Order.php
| use Framework\Utility\DateTimeUtils; | |
| use Framework\Utility\KafkaUtils; | |
| use Bundle\Component\Payment\Tosspay\TosspayConfig; | |
| use Util\Order\OrderGoodsLockSortUtil; | |
| /** | |
| * 주문 class | |
| // 현재 상태 수정 처리 | |
| $arrWhere[] = 'sno = ?'; | |
| $arrWhere[] = 'orderNo = ?'; | |
| // [데드락 회피] 같은 주문의 여러 es_orderGoods 행을 트랜잭션마다 동일한 순서(sno 오름차순)로 잠그도록 통일 | |
| // 클레임(취소/교환)이 동시 실행될 때 행 잠금 순서가 엇갈려 발생하던 교착(패턴 L)을 예방 | |
| // 키를 유지하는 asort 로 정렬하므로 아래 $arrData['orderStatus'][$key] 쌍 대응은 그대로 보존 | |
| if (is_array($arrData['sno'])) { | |
| asort($arrData['sno'], SORT_NUMERIC); | |
| } | |
| foreach ($arrData['sno'] as $key => $val) { | |
| // 주문 로그 저장 | |
| $logCode01 = $this->getOrderStatusAdmin($arrData['orderStatus'][$key]) . '(' . $arrData['orderStatus'][$key] . ')'; | |
| $orderLogData = $this->db->query_fetch($strSQL, $arrBind, false); | |
| if ((int)$orderLogData['cnt'] == 0 || $isClaim === true) { | |
| // [데드락 회피] es_memberOrderGoodsCount 잠금 순서 통일을 위해 goodsNo 오름차순으로 처리(상세: OrderGoodsLockSortUtil) | |
| $aResolvedGoodsList = OrderGoodsLockSortUtil::resolveOrderGoodsSortedByGoodsNo($this, $orderNo, $aGoodsSno['sno']); | |
| foreach ($aResolvedGoodsList as $aResolved) { | |
| $aValue = array(); | |
| $aOrderGoodsData = $aResolved['data']; | |
| // 인서트 or 업데이트 | |
| $aMemberOrderGoodsCountData = $this->getMemberOrderGoodsCountData($aOrderData['memNo'], $aOrderGoodsData['goodsNo']); | |
변경 전OrderGoodsLockSortUtil.php
변경 후OrderGoodsLockSortUtil.php
| <?php | |
| /* | |
| * Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved | |
| * | |
| * Unauthorized copying or redistribution of this file in source and binary forms via any medium | |
| * is strictly prohibited. | |
| */ | |
| namespace Bundle\Util\Order; | |
| /** | |
| * [데드락 회피] 주문상품 잠금 순서 통일용 정렬 유틸. | |
| * | |
| * es_memberOrderGoodsCount 는 (memNo, goodsNo) 기준으로 잠기므로, 여러 상품을 갱신할 때 | |
| * goodsNo 오름차순으로 처리해 트랜잭션 간 잠금 순서를 통일한다(데드락 패턴 A 예방) | |
| * Order / OrderNew 양쪽에서 동일 로직을 공용으로 사용하기 위해 분리 | |
| */ | |
| class OrderGoodsLockSortUtil | |
| { | |
| /** | |
| * 주문상품 sno 목록을 실제 주문상품 정보로 해석한 뒤 goodsNo 오름차순으로 정렬해 반환 | |
| * | |
| * sno 입력 순서에 의존하지 않으며, 같은 goodsNo 의 상대 순서는 원본 순서를 보존 | |
| * (정렬 안정성 → 중복 goodsNo 누적 처리 동작 불변). goodsNo 를 해석하지 못한 항목은 0 으로 앞에 정렬 | |
| * | |
| * @param object $order getOnlyOrderGoodsData(orderNo, sno) 를 제공하는 주문 객체(Order|OrderNew) | |
| * @param string $orderNo 주문번호 | |
| * @param array $orderGoodsSnoList 주문상품 sno 목록 | |
| * | |
| * @return array 각 항목이 array('seq'=>입력순번, 'goodsNo'=>정렬키, 'data'=>getOnlyOrderGoodsData 결과) | |
| * 이며 goodsNo 오름차순으로 정렬된 목록 | |
| */ | |
| public static function resolveOrderGoodsSortedByGoodsNo(object $order, string $orderNo, array $orderGoodsSnoList): array | |
| { | |
| // 재사용 대비 방어: getOnlyOrderGoodsData 를 제공하지 않는 객체면 빈 목록 반환 | |
| if (!is_object($order) || !method_exists($order, 'getOnlyOrderGoodsData')) { | |
| return []; | |
| } | |
| $resolvedList = []; | |
| foreach ($orderGoodsSnoList as $seq => $orderGoodsSno) { | |
| $orderGoodsData = $order->getOnlyOrderGoodsData($orderNo, $orderGoodsSno); | |
| $resolvedList[] = [ | |
| 'seq' => $seq, | |
| 'goodsNo' => (is_array($orderGoodsData) && isset($orderGoodsData['goodsNo'])) ? (int) $orderGoodsData['goodsNo'] : 0, | |
| 'data' => $orderGoodsData, | |
| ]; | |
| } | |
| usort($resolvedList, function ($leftGoods, $rightGoods) { | |
| if ($leftGoods['goodsNo'] === $rightGoods['goodsNo']) { | |
| return $leftGoods['seq'] - $rightGoods['seq']; // 동률은 원본 순서 유지(정렬 안정성 보장) | |
| } | |
| return $leftGoods['goodsNo'] - $rightGoods['goodsNo']; | |
| }); | |
| return $resolvedList; | |
| } | |
| } | |