mariaDB/3. SQL 사용법

도서구매사이트 - 주문 API 작성 시 코드 리팩토링

bbomkim 2025. 5. 21. 21:38

내가 작성한 코드

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 접근만 최소화
  • 전체 쿼리 횟수 줄어듦 (성능, 가독성 개선)

결론

  • 기존 코드도 기능상 문제가 없음
  • 다만, 반복적인 쿼리를 묶고, 커넥션 관리 위치를 정리하여 더 안정적이고 효율적인 코드가 됨