프로젝트/도서구매사이트

1028 conn.query()의 비동기 처리, query 와 execute 차이, 주문 완료한 아이템 장바구니에서 삭제

thinktank911 2025. 10. 28. 17:11

conn.query()의 비동기 처리를 위한 mysql 모듈 셋팅

mysql 모듈을 사용해 query() 함수를 사용하려면 비동기 처리를 해줘야 한다.

비동기 처리 방식으로는 크게 콜백 방식과 Promise 방식 두 가지가 있다.

  • 콜백 방식
    • query() 함수의 두번째 매개변수로 콜백 함수를 셋팅해준다.
    • 쿼리의 결과값은 콜백함수의 results 매개변수로 받아온다.
// Get the client
const mysql = require('mysql2');

// Create the connection to database
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  database: 'test',
});

// A simple SELECT query
connection.query(
  'SELECT * FROM `table` WHERE `name` = "Page" AND `age` > 45',
  function (err, results, fields) {
    console.log(results); // results contains rows returned by server
    console.log(fields); // fields contains extra meta data about results, if available
  }
);

// Using placeholders
connection.query(
  'SELECT * FROM `table` WHERE `name` = ? AND `age` > ?',
  ['Page', 45],
  function (err, results) {
    console.log(results);
  }
);
  • Promise 방식
    • 모듈을 호출할 때 require('mysql2/promise')로 불러온다.
    • async 함수 안에 await conn.query('실행쿼리') 함수를 실행한다.
    • 쿼리 결과값을 받을 때 const [results, fields] 배열로 받는다.
// Get the client
import mysql from 'mysql2/promise';

// Create the connection to database
async (req, res)=>{
const connection = await mysql.createConnection({
  host: 'localhost',
  user: 'root',
  database: 'test',
});

// A simple SELECT query
try {
  const [results, fields] = await connection.query(
    'SELECT * FROM `table` WHERE `name` = "Page" AND `age` > 45'
  );

  console.log(results); // results contains rows returned by server
  console.log(fields); // fields contains extra meta data about results, if available
} catch (err) {
  console.log(err);
}

// Using placeholders
try {
  const [results] = await connection.query(
    'SELECT * FROM `table` WHERE `name` = ? AND `age` > ?',
    ['Page', 45]
  );

  console.log(results);
} catch (err) {
  console.log(err);
}
}

[MySQL 모듈] query 와 execute 차이

query() - 일반 쿼리 실행용

  • 특징
    • SQL 문자열을 그대로 서버에 전달하고, 내부에서 파라미터를 나중에 escape 처리함
    • 즉, 서버가 매번 쿼리를 파싱하고 실행 계획을 새로 세움
    • 동적 쿼리 작성이 가능함 (문자열 붙이기 등 자유도가 높음)
    • INSERT, UPDATE, DELETE, SELECT 모두 가능
  • 단점
    • 매번 쿼리를 파싱하므로 성능이 execute보다 조금 느림
    • 사용자가 문자열을 잘못 넣으면 SQL Injection 위험이 높음

execute() - prepared statement(준비된 문)용

  • 특징
    • 서버 측에서 미리 컴파일된 쿼리(Prepared Statement)를 사용함
    • 즉, SQL 구문은 한 번 파싱해두고, 매개변수만 바꿔 여러 번 실행 가능
    • 내부적으로 ? 자리에 안전하게 값을 바인딩하므로 SQL Injection 방지에 강함
    • mysql2 모듈에서만 지원 (mysql 기본 모듈에는 없음)
  • 장점
    • 보안성 높음
    • 같은 쿼리를 여러 번 실행할 때 성능이 더 좋음
    • 데이터 타입 바인딩이 정확함 (예: 날짜 숫자 등 자동 변환)

MySQL 데이터 삭제하는 방법

DELETE vs DROP vs TRUNCATE 비교

  • DELETE
    DELETE FROM 테이블명 (WHERE 조건);
    : 조건 없으면 모든 행 삭제됨 (테이블 남아있음)
  • DROP
    DROP TABLE 테이블명;
    : 테이블을 통째로 삭제하는 것
  • TRUNCATE
    TRUNCATE 테이블명;
    : 모든 행 삭제됨 (테이블 남아있음)
※ 테이블 delete와 truncate 차이
- truncate는 auto_increment 초기화

 

외래 키 제약조건 끄기 : SET_FOREIGN_KEY_CHECKS = 0;

ERROR 1217 (23000): Cannot delete or update a parent row: a foreign key constraint fails

➡️ 한 테이블에서 다른 테이블을 참조하고 있을 때 나타나는 에러
mysql에 테이블 간 관계가 있는 데이터를 함부로 수정하거나 삭제하지 못하게 막아두었기에 데이터 안정성에 대해 경고하는 문구

 

 해당 에러를 무시하고 계속 수정/삭제를 하고 싶다면 제약 조건을 끄면 된다.

-- FOREIGN_KEY 체크 해제
SET FOREIGN_KEY_CHECKS = 0;

-- DELETE OR UPDATE

-- FOREIGN_KEY 체크 설정
SET FOREIGN_KEY_CHECKS = 1;
  • SET FOREIGN_KEY_CHECKS = 0;
    : FOREIGN_KEY 체크하지 마
  • SET FOREIGN_KEY_CHECKS = 1;
    : FOREIGN_KEY 다시 체크

주문한 책(orderedBook) insert - 다중인서트 위해 conn.query() 사용

    • 다중 인서트 구현 시 인서트문을 반복해서 추가하지 않고, 인서트할 값을 이차원 배열로 받아 values로 넘겨준다.
      ➡️ 최근에 업데이트된 최신 기능으로 conn.query()에만 적용이 가능해 이것만 execute()가 아닌 conn.query()로 구현
      ➡️ values로 배열을 보내줄 땐 query() 함수의 매개변수를 [values]로 감싸줘야 한다.
    • conn.query()와 conn.execute() 반환해주는 형태가 다름
      • conn.query는 배열 안 객체 2개
      • conn.execute는 배열 안 객체 1개

            ➡️ conn.query 로 res.json 뿌려줄 때 results[0] 보내준다.

// 주문한 책
    sql = `INSERT INTO orderedBook (order_id, book_id, quantity)
            VALUES ?`;
           
    // items.. 배열 : 요소들을 하나씩 꺼내서 (foreach문 돌려서)
    values = [];    // 초기화
    items.forEach((item) => {
        values.push([order_id, item.book_id, item.quantity]);
    });
   
    results = await conn.query(sql, [values]);
    return res.status(StatusCodes.CREATED).json(results[0]);  // 201

 

주문 후 장바구니 아이템 삭제

  • 카트(cartItem) 테이블의 cartItemId 가져와서 장바구니 아이템을 삭제해야 한다.
  • request body 수정
    • 상품목록을 담았던 items에 장바구니 도서id 즉, cartItemId의 배열을 담아서 활용할 것.
    • 기존 items에 담았던 도서id와 수량은 cartItem 테이블에서 조회해 사용한다.

    const {items, delivery, totalQuantity, totalPrice, userId, repBookTitle} = req.body;
 
    //======= 

    // items를 가지고, 장바구니에서 book_id, quantity 조회
    sql = `SELECT book_id, quantity FROM cartItems WHERE id IN (?)`;
    let [orderItems, fields] = await conn.query(sql, [items]);

    // 주문한 책
    sql = `INSERT INTO orderedBook (order_id, book_id, quantity)
            VALUES ?`;
           
    // items.. 배열 : 요소들을 하나씩 꺼내서 (foreach문 돌려서)
    values = [];    // 초기화
    orderItems.forEach((item) => {
        values.push([order_id, item.book_id, item.quantity]);
    });
   
    results = await conn.query(sql, [values]);
    return res.status(StatusCodes.CREATED).json(results[0]);  // 201
   
}
  • items에 담긴 장바구니id를 배열 values로 받아 IN [배열] 구문으로 cartItems 테이블의 book_id, quantity를 조회한다.

cartItem 삭제하기

    // 결제한 장바구니 삭제
    let result = await delCartItems(conn, items);
    return res.status(StatusCodes.CREATED).json(results[0]);  // 201
   
}

// 결제한 장바구니 삭제
const delCartItems = async (conn, items) => {
    let sql = `DELETE FROM cartItems WHERE id IN (?)`;

    let result = await conn.query(sql, [items]);
    return result;
}
  • delete할 cartItemId을 IN(배열)로 받을 때
    values를 배열로 감싼다.
  • 최신 기능이므로 현재 conn.query 만 적용가능하다.
  • 장바구니 삭제 함수를 실행할 때 필요한 conn과 items를 매개변수로 받아온다.
  • 주문하기 인서트 시 delCartItem을 await 함수로 호출해 실행한다.

주문내역 조회

// 주문내역 조회
const getOrders = async (req, res)=>{
    // DB connection 생성
    const conn = await mysql.createConnection({
        host: '127.0.0.1',
        port: '3307',
        user: 'root',
        password: 'root',
        database: 'Bookshop',
        dateStrings: true,    // 날짜 형식대로 표기
      });

    // 주문내역 페이지에서 보여줄 컬럼 셋팅 위해 orders와 delivery 조인
    let sql = `SELECT orders.id, created_at, address, receiver, contact,
                    rep_book_title, total_quantity, total_price
                FROM orders LEFT JOIN delivery
                ON orders.delivery_id = delivery.id`;

    let [rows, fields] = await conn.query(sql);
    return res.status(StatusCodes.OK).json(rows);
}
  • DB connection 생성
  • 주문내역 페이지에서 보여줄 컬럼 셋팅 위해 orders와 delivery 조인

주문상세조회

// 주문상세 조회
const getOrderDetail = async (req, res)=>{
    // 상세 조회할 주문id url로 받아오기
    let {id} = req.params;
    id = parseInt(id);

    // DB connection 생성
    const conn = await mysql.createConnection({
        host: '127.0.0.1',
        port: '3307',
        user: 'root',
        password: 'root',
        database: 'Bookshop',
        dateStrings: true,    // 날짜 형식대로 표기
      });

    // orderedBook과 books 조인
    let sql = `SELECT book_id, title, author, price, quantity
                FROM orderedBook LEFT JOIN books
                ON orderedBook.book_id = books.id
                WHERE order_id = ?`;

    let [rows, fields] = await conn.query(sql, id);
    return res.status(StatusCodes.OK).json(rows);
}
  • 상세 조회할 주문id url로 받아오기
  • DB connection 생성
  • orderedBook과 books 조인