구현하고 있는 API -> https://ilove-ya.tistory.com/144
[table]
delivery (1)
↓
orders (N) (delivery_id FK)
↓
orderedBook (N) (order_id FK)
- delivery INSERT → 생성된 delivery_id 필요
- orders INSERT → 생성된 order_id 필요
- orderedBook INSERT → order_id 필요
주문 API 에서 특이한 점은 이전 INSERT 결과의 PK를 다음 쿼리에서 사용하여야한다는 점입니다.
이를 위해서
직전에 insert한 데이터 PK 가져오는 방법
을 사용하여보겠습니다.
- LAST_INSERT_ID()
: 이전 값을 들고오는 오류가 날 수 있음 - MAX()
: 최신값 -> id값이 가장 큰 값을 고르면 됨
SELECT max(id) FROM Bookshop.orderedBook;
SELECT last_insert_id();
// 주문하기
// 배송 정보 입력
INSERT INTO delivery (address, receiver, contact) VALUES ("용인시 수지구", "김가영", "010-1234-5678")
const delivery_id = SELECT max(id) FROM delivery
// 주문 정보 입력
INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
VALUES ("어린완자들", 3, 60000, 1, delivery_id);
const order_id = SELECT max(id) FROM orders;
// 주문 상세 목록 입력
INSERT INTO orderedBook(order_id, book_id, quantity)
VALUES (order_id,1,1);
INSERT INTO orderedBook(order_id, book_id, quantity)
VALUES (order_id,3,2);
SELECT max(id) FROM Bookshop.orderedBook;
SELECT last_insert_id();
이렇게 하면 가장 최신의 배송정보를 주문정보에 넣어주고,
가장 최신의 주문 정보를 주문상세 목록에 넣어줍니다.
insertId

그런데 현재 코드에서
Node에서 conn.query() 실행했을 때 콜백의 results 값(INSERT 쿼리 실행 결과 메타데이터)를 살펴 보면, "insertId"가 있는 걸 알 수 있습니다.
insertId : 방금 INSERT된 행의 PK 값을 의미
->
INSERT INTO delivery ... 를 실행했고
AUTO_INCREMENT가 3이었다는 뜻입니다.
이를 활용하면 max(id)을 사용하지 않아도 됩니다.
주문을 진행할 때 하나의 주문에 여러 개의 책을 한 번에 구매할 수 있습니다.
이를 위해서는 주문이 진행되는 여러개의 책을 for문을 돌려 insert해줄 수 있지만, 이는 성능저하를 초래할 수 있습니다.
INSERT INTO orderedBook ...
INSERT INTO orderedBook ...
INSERT INTO orderedBook ...
따라서 이를 하나의 쿼리로 처리시켜주는 방법을 사용해 보겠습니다.
벌크 INSERT
INSERT INTO orderedBook(order_id, book_id, quantity)
VALUES
(1, 10, 2),
(1, 15, 1),
(1, 21, 3);
이렇게 INSERT문의 VALUES 뒤에 ( ) 가 여러 개 붙으면 INSERT을 여러번 수행해 줄 수 있다는 겁니다.
VALUES ?
이를 위해 이 방식으로 sql문으로 넣어두고 ?에 이중 배열를 넣어주면,
MySQL Node driver가 자동으로
[
[1, 10, 2],
[1, 15, 1],
[1, 21, 3]
]
이중배열인 이 형태를
(1,10,2),
(1,15,1),
(1,21,3)
이러한 형태로 변형시켜줍니다.
sql = `INSERT INTO orderedBook(order_id, book_id, quantity)
VALUES ?;`;
//items.. 배열 : 요소들을 하나씩 꺼내서 (foreach문 돌려서)
values = [];
items.forEach((items) => {
values.push([order_id, items.book_id, items.quantity]);
})
conn.query(sql, [values],
(err, results) => {
if(err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
})
코드에 적용을 해보자면, forEach을 돌려서 이중배열형태로 책들의 정보를 전체 배열의 하나의 요소의 배열로 push해주었습니다 .
그리하여, query에도 values를 전달을 해줍니다.
이때 물음표 하나의 자리에 values를 각각 넣어주어야하기 때문에 이 자리에 이중배열 전체를 넣어주기 위해 [values]로 감싸야 이중배열이 그대로 물음표 하나에 들어갈 수 있습니다.
⚠️현재 문제(비동기 처리)
현재의 코드의 의도는, delivery를 INSERT -> 그 정보를 가지고 Orders를 INSERT -> 그 정보를 가지고 -> orderedBook을 INSERT 형식입니다.
언뜻봤을 때에는 문제가 없어보이나, 그런데 실행을 했을 때, delivery_id값이 없는 값이라는 오류가 발생합니다.
// 주문 하기
const order = (req, res) => {
const {items, delivery, totalQuantity, totalPrice, userId, firstBookTitle} = req.body;
let delivery_id;
let order_id;
let sql = "INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)";
let values = [delivery.address, delivery.receiver, delivery.contact];
conn.query(sql, values,
(err, results) => {
if(err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
delivery_id = results.insertId;
})
sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
VALUES (?, ?, ?, ?, ?);`;
values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
conn.query(sql, values,
(err, results) => {
if(err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
order_id = results.insertId;
})
sql = `INSERT INTO orderedBook(order_id, book_id, quantity)
VALUES ?;`;
//items.. 배열 : 요소들을 하나씩 꺼내서 (foreach문 돌려서)
values = [];
items.forEach((items) => {
values.push([order_id, items.book_id, items.quantity]);
})
conn.query(sql, [values],
(err, results) => {
if(err) {
console.log(err);
return res.status(StatusCodes.BAD_REQUEST).end();
}
return res.status(StatusCodes.OK).json(results);
})
}
values.push([order_id, items.book_id, items.quantity]); 를 실행하였을때, delivery_id와 order_id가 아직 값이 아니라는 겁니다.
🤔 왜 그런거지?
conn.query(INSERT delivery, callback1)
conn.query(INSERT orders, callback2)
conn.query(INSERT orderedBook, callback3)
이 세 쿼리는 순차 실행이 아닙니다.
Node.js는:
- 첫 번째 query 실행 요청
- 기다리지 않음
- 바로 두 번째 query 실행
- 또 기다리지 않음
- 세 번째 query 실행
즉, delivery INSERT가 끝나기 전에 orders INSERT가 실행된다는 것입니다!
https://ilove-ya.tistory.com/158
[Node.js] Node.js의 비동기 처리 - Promise / Async&await
Node.js는 왜 비동기인가?Node.js는 단일 스레드(Single Thread) 기반 런타임입니다.이는 기본적으로 한 번에 하나의 JS 코드만 실행된다는 말인데요.그런데도 동시에 많은 요청을 처리할 수 있는 이유는
ilove-ya.tistory.com
그리하여 비동기 처리, Promise / Async-await 를 공부하고 왔습니다.
mysql에서는 query를 promise 객체로 감싸줄 수 있는 기능이 있습니다.
공식 사용법 링크 : https://sidorares.github.io/node-mysql2/docs/documentation/promise-wrapper
Promise Wrappers | Quickstart
In addition to errback interface there is thin wrapper to expose Promise-based api
sidorares.github.io
제시된 예시 코드 -> 이걸 활용해 보겠습니다.
async function example1() {
const mysql = require('mysql2/promise');
const conn = await mysql.createConnection({ database: test });
const [rows, fields] = await conn.execute('select ?+? as sum', [2, 2]);
await conn.end();
}
async function example2() {
const mysql = require('mysql2/promise');
const pool = mysql.createPool({ database: test });
// execute in parallel, next console.log in 3 seconds
await Promise.all([
pool.query('select sleep(2)'),
pool.query('select sleep(3)'),
]);
console.log('3 seconds after');
await pool.end();
}
->
주문하기 API
// 주문 하기
const order = async (req, res) => {
const conn = await mariadb.createConnection({
host: '127.0.0.1',
user : 'root',
password : 'root',
database : 'BookShop',
dateStrings : true
});
const {items, delivery, totalQuantity, totalPrice, userId, firstBookTitle} = req.body;
// delivery 테이블 삽입
let sql = "INSERT INTO delivery (address, receiver, contact) VALUES (?, ?, ?)";
let values = [delivery.address, delivery.receiver, delivery.contact];
let [results] = await conn.execute(sql, values);
let delivery_id = results.insertId;
//orders 테이블 삽입
sql = `INSERT INTO orders (book_title, total_quantity, total_price, user_id, delivery_id)
VALUES (?, ?, ?, ?, ?);`;
values = [firstBookTitle, totalQuantity, totalPrice, userId, delivery_id];
[results] = await conn.execute(sql, values);
let order_id = results.insertId;
// items를 가지고, 장바구니에서 book_id, quantity 조회
sql = `SELECT book_id, quantity FROM cartItems WHERE id IN (?);`;
let [orderItems, fields] = await conn.query(sql, [items]);
//orderedBook 테이블 삽입
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]);
let result = await deleteCartItems(conn, items);
return res.status(StatusCodes.OK).json(result);
}
const deleteCartItems = async (conn, items) => {
let sql = `DELETE FROM cartItems WHERE id IN (?); `;
let result = await conn.query(sql, [items]);
return result;
}

주문 전체 목록 조회
const getOrders = async (req, res) => {
const conn = await mariadb.createConnection({
host: '127.0.0.1',
user : 'root',
password : 'root',
database : 'BookShop',
dateStrings : true
});
let sql = `SELECT orders.id, created_at,address, receiver, contact, 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);
}

주문 상세 목록 조회
const getOrderDetail = async (req, res) => {
const {id} = req.params;
const conn = await mariadb.createConnection({
host: '127.0.0.1',
user : 'root',
password : 'root',
database : 'BookShop',
dateStrings : true
});
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);
}

'Devcourse > BookShop 사이드 프로젝트' 카테고리의 다른 글
| [BookShop 프로젝트] 백엔드 마무리하기 (0) | 2026.03.18 |
|---|---|
| [BookShop 프로젝트] jwt 토큰 추가, jwt 예외처리, try...catch, throw (0) | 2026.03.04 |
| [BookShop 프로젝트] 장바구니 API - fk제약조건, in(~, ~) (0) | 2026.02.19 |
| [BookShop] 좋아요 API 구현 - count(), EXISTS (0) | 2026.02.18 |
| [BookShop 프로젝트] Books API 구현 - picsum (0) | 2026.02.14 |