내가 작성한 코드
async function postOrder(req, res) {
try {
console.log("주문 API 실행됨")
const {user_id, cart_id_arr, adress, receiver} = req.body;
let totalPrice = 0; // 계산해야함
const conn = await pool.getConnection();
await conn.beginTransaction();
// orders 주문 테이블에 메타데이터 저장 (이걸 먼저해야 order_id를 orderItems테이블에 넣을 수 있음)
const [result_postOrder] = await conn.query(
`INSERT INTO orders (user_id, adress, receiver, totalPrice)
VALUES (?, ?, ?, ?)`,
[user_id, adress, receiver, totalPrice]
);
const order_id = result_postOrder.insertId; // order_id 추출 (INSERT 쿼리를 실행하면 삽입된 행의 ID (Auto Increment)를 바로 insertId로 얻을 수 있습니다.)
// 프론트에서 받은 cart_id_arr로 => [ {book_id, count}, ...] 뽑아내기
let order_items = [];
for(const cart_id of cart_id_arr) {
const [result] = await conn.query(`SELECT * FROM cart WHERE id = ?`, cart_id);
order_items.push(result[0]);
}
console.log("order_items : ",order_items);
// orderItems 주문 상세 테이블에 반복 저장
for(const item of order_items) {
await conn.query(
`INSERT INTO orderItems (book_id, count, order_id) VALUES (?, ?, ?)`,
[item.book_id, item.count, order_id]
);
}
// totalPrice 계산 => orderItems테이블에 books테이블의 price를 JOIN에서 조회
const [result_orderItems] = await conn.query(
`SELECT orderItems.book_id, orderItems.count, books.price
FROM orderItems
JOIN books ON orderItems.book_id = books.id
WHERE order_id = ?`,
order_id
);
console.log("result_orderItems : ",result_orderItems);
totalPrice = result_orderItems.reduce((prev, curr) => {
return prev + ( parseInt(curr.price) * parseInt(curr.count) )
}, 0);
console.log("totalPrice : ",totalPrice);
// totalPrice => orders 테이블에 넣기
await conn.query(`UPDATE orders SET totalPrice = ? WHERE id = ?`,
[totalPrice, order_id]
);
// cart_id => cart테이블에서 삭제
const cart_id_placeholder = cart_id_arr.map(value => "?").join(); // "?,?,?" 이런 형태태
await conn.query(
`DELETE FROM cart WHERE id IN (${cart_id_placeholder})`, cart_id_arr
)
await conn.commit(); // ✅ 커밋 꼭 필요
conn.release(); // ✅ 커넥션 반환
return res.status(StatusCodes.OK).end();
}
catch(err) {
console.error("에러 : ", err);
return res.status(StatusCodes.BAD_REQUEST).json({message: "주문하기 실패"});
}
}
리팩토링 코드
async function postOrder(req, res) {
const { user_id, cart_id_arr, adress, receiver } = req.body;
const conn = await pool.getConnection();
let totalPrice = 0;
try {
await conn.beginTransaction();
// cart에서 주문 항목 한번에 조회
const [order_items] = await conn.query(
`SELECT cart.book_id, cart.count, books.price
FROM cart
JOIN books ON cart.book_id = books.id
WHERE cart.id IN (?)`,
[cart_id_arr]
);
// 주문 테이블 저장
const [orderRes] = await conn.query(
`INSERT INTO orders (user_id, adress, receiver, totalPrice)
VALUES (?, ?, ?, ?)`,
[user_id, adress, receiver, 0]
);
const order_id = orderRes.insertId;
// 주문 상세 저장 + 가격 누적 계산
for (const item of order_items) {
await conn.query(
`INSERT INTO orderItems (book_id, count, order_id)
VALUES (?, ?, ?)`,
[item.book_id, item.count, order_id]
);
totalPrice += item.price * item.count;
}
// totalPrice 반영
await conn.query(
`UPDATE orders SET totalPrice = ? WHERE id = ?`,
[totalPrice, order_id]
);
// cart에서 삭제
await conn.query(
`DELETE FROM cart WHERE id IN (?)`,
[cart_id_arr]
);
await conn.commit();
res.status(StatusCodes.OK).end();
} catch (err) {
await conn.rollback();
console.error("에러 : ", err);
res.status(StatusCodes.BAD_REQUEST).json({ message: "주문하기 실패" });
} finally {
conn.release(); // 에러가 나든 말든 항상 커넥션 반환
}
}
개선 포인트 요약
| 항목 |
개선 전 |
개선 후 |
설명 |
| 1. conn.release() 위치 |
try 내부에만 있음 |
finally 블록에 위치 |
에러 발생 시 release 누락 방지 |
| 2. cart에서 book_id, count 조회 |
여러 번 SELECT |
IN으로 한 번에 조회 |
N개의 쿼리를 1개로 줄임 |
| 3. totalPrice 계산 |
orderItems → books 다시 JOIN |
order_items 만들 때 가격 포함 |
쿼리 횟수 줄이고 간결하게 처리 |
| 4. 쿼리 안전성 |
placeholder .join() 방식 |
IN (?) + 배열로 처리 |
SQL Injection 및 가독성 개선 |
효과
- cart_id_arr로 cart + books를 한 번에 JOIN 조회
- order_items을 미리 구성하면서 totalPrice도 같이 계산
- for문 안에서는 DB 접근만 최소화
- 전체 쿼리 횟수 줄어듦 (성능, 가독성 개선)
결론
- 기존 코드도 기능상 문제가 없음
- 다만, 반복적인 쿼리를 묶고, 커넥션 관리 위치를 정리하여 더 안정적이고 효율적인 코드가 됨