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 조인
'프로젝트 > 도서구매사이트' 카테고리의 다른 글
| 1030 회원 인증 모듈화 적용, 전체 도서 pagination json 추가, 리팩토링 항목 (0) | 2025.10.30 |
|---|---|
| 1029 headers의 Authorization으로 jwt 인증 구현, try~catch 예외처리 (0) | 2025.10.29 |
| 1027 비동기 처리 방식 종류 - promise, async/await (0) | 2025.10.26 |
| 1024 주문 API 구현 - insertId 활용, 다중 인서트 구현 (0) | 2025.10.23 |
| 1023 장바구니 API 구현 - 선택한 장바구니 목록 조회 IN [배열] 적용 (0) | 2025.10.22 |