데브코스 9주차 Day 2,3,4 - [쇼핑몰]주문 controller, 비동기, promise
구현한 부분
- 주문하기 (flow : 배송정보 삽입 [delivery테이블] => 주문 정보 삽입 [order테이블] => 주문 상세 정보 삽입 [orderedBook테이블] => 장바구니 지우기 [cartItems테이블])
- 주문 목록 조회 (orders&delivery 테이블 join)
- 주문 상세 (orderedBook&books 테이블 join)
order , delivery 관계??
강사님이 orders와 delivery의 관계는 n:1이라고 하셨다. (ref : orders.delivery_id > delivery.id)
주문이 들어오면 delivery에 행이 계속 계속 생성되는게 아닌가? 배송정보가 똑같아서 중복이면 1이라고 봐서 n:1인건지 궁금하다. 더 찾아보고 추가해야할 것 같다.
테이블 설명
주문을 하면 order테이블에 하나의 행이 생긴다. 여기엔 주문일, 총 가격등을 저장한다. 배송정보는 테이블을 새로 만들어 저장.
but, 한번에 여러개의 책을 주문할 수 있다.
하지만 배열으로 book_id를 db에 저장할 수 없으니 orderedBook 테이블에 order.id를 가져가 해당 주문건에 대한 bookid와 수량을 저장한다.
구현 - 주문하기
주문하기
순서
delivery => order (delivery_id 필요) => orderedBook(order_id 필요)
Node.js는? - 논 블로킹
node.js는 논 블로킹 i/o - 비동기식이다.
비동기 뜻
특정 코드가 끝날때 까지 코드의 실행을 멈추지 않고 다음 코드를 먼저 실행하는 것을 의미
이전 시간이 오래걸리면 기다리지 않고 다음 코드를 실행한다.
비동기 처리
- 콜백함수 : 할 일 다하고, 다음 함수 실행(순서 맞춰서 이걸 뒤에 실행해달라)
- promise(resolve, reject)
- then &catch
- ES2017 promise=> async & await
PROMISE
함수 안에 성공하면 resolve-> 자동으로 then 을 불렀을 때 첫번째 함수, reject면 자동으로 두번째 함수 실행
let promise = new Promise(function (resolve, reject) {
setTimeout(() => resolve("완료"), 3000);
});
promise.then(
function (result) {
console.log(result);
},
function (error) {}
);
여러개의 then 실행하기 (promise chaining)
- then을 이어붙여 실행한다.
- 다음 then으로 넘어가기: return 이용.
let promise = new Promise(function (resolve, reject) {
setTimeout(() => resolve("완료"), 3000);
})
.then(
function (result) {
console.log(result);
return result + "2";
},
function (error) {}
)
.then(
function (result) {
console.log(result);
},
function (error) {}
);
결과 => 3초 뒤
async
- 1. 함수 만들기 : promise 객체를 좀 더 쉽고 편하게 사용할 수 있는 문법, 훨씬 간단해진 것을 볼 수 있다.
- 2. await 함수를 만들 수 있는 공간 제공
- 비동기 처리를 좀 더 쉽게.
- async 함수는 무조건 promise 객체를 반환
- 만약 반환값이 promise가 아니면, promise의 resolve()로 감싼다.
- 즉 return true였다면, 알아서 return Promise.resolve(true)로 반환 형식을 맞춰준다.
async function f() {
return true;
// return Promise.resolve(true);
}
f().then(
function (result) {
console.log("resolve");
},
function (error) {
console.log("error");
}
);
결과
await
- async 안에서만 사용
- promise 객체를 기다림
async function f() {
let promise = new Promise(function (resolve, reject) {
setTimeout(() => resolve("완료"), 3000);
});
let result = await promise;
console.log(result);
}
f();
결과 =>3초 뒤
한 핸들러에서 여러개의 conn.query 수행 => async와 await
다음으로 주문이 들어오면 다음을 수행해야한다.
1. delivery 테이블에 삽입
2. order 테이블 삽입 (삽입된 delivery_id 필요)
3. orderedBook 테이블에 삽입 (삽입된 order_id 필요)
그렇다면 conn.query를 여러개 수행해야한다.
하지만 node.js는 비동기방식이다. 즉, 연결 뒤 query문을 수행하는데 이를 기다려주지 않는 것이다.
이를 처리해보자. 먼저 데이터베이스와 연결하는 부분부터 바꿔준다.
mariadb.js
const mariadb = require("mysql2/promise");
const connection = async () => {
const conn = await mariadb.createConnection({
host: process.env.HOST,
user: process.env.USER,
password: process.env.PASSWORD,
database: process.env.DATABASE,
dateStrings: true,
});
return conn;
};
module.exports = connection;
mysql2 모듈에서 mysql2/promise로 promise로만 바꿔주면 사용할 수 있다!
먼저 연결하는 부분인 createConnection에서 연결을 할 때, 시간이 걸릴 것이므로 await을 통해 기다려주고, await을 사용하기 위해 async로 새로운 함수를 만들어 주었다.( promise함수가 됨) createConnection의 리턴값을 반환한다.
(지금까지 기다리지 않고도 이를 사용할 수 있었던 이유는 한 핸들러에서 하나의 쿼리문을 수행했기 때문에 기다리고 다음 내용을 수행할 필요가 없었기 때문이다.)
=> 이 부분 수정됨.
이해 안되는 점
그런데 나중에 이렇게 쓰려고 보니 promise는 excute 함수가 안먹힌다고 한다. ????
그럼 어쩌라는건지
=> promise가 아닌 콜백함수(connection)을 export하고 불러온다.
=> 콜백함수를 await 해준다. (근데 await하면 promise를 반환하는거 아니냐) => 연결되면 쿼리문 실행을 await한다.
궁금한 점
- mariada.js에서 await으로 돌려주는거랑 파일에서 await해서 사용하는거랑 뭐가 다른거지?
- 같은 파일에서는 되고 왜 다른 파일에서 할 때랑 결과가 다르지... 똑같이 await으로 연결하는데 다른 파일에 있으나 여기 있으나 같은 promise 객체일텐데.
https://sidorares.github.io/node-mysql2/docs#using-connection-pools
Quickstart | Quickstart
MySQL client for Node.js with focus on performance
sidorares.github.io
위는 mysql2에 대한 공식 문서이다.
json에 들어온 json 객체 배열을 순서대로 삽입하기 - 다중 INSERT
{
"items":[
{
"book_id":2,
"order_id":3,
"quantity":1
},{
"book_id":3,
"order_id":3,
"quantity":1
}
],
"delivery":{
"address":"경기도 이천",
"receiver":"받는이",
"contact": "010-9918-4567"
},
"first_book_title":"신데렐라",
"total_quantity": 3,
"total_price": 37000,
"user_id":1
}
위와 같이 items에 json 배열이 들어온 경우 하나의 객체마다 insert를 해줘야한다.
VALUES를 2차원 배열로 만들어 준 뒤 SQL문의 VALUES ? 의 ? 부분에 넣어주면 된다. [values]의 괄호 조심!
sql = `INSERT INTO orderedBook (order_id,book_id,quantity) VALUES ?`;
values = [];
items.forEach((item) => {
values.push([order_id, item.book_id, item.quantity]);
});
[results] = await conn.query(sql, [values]);
WHERE IN 사용하기
- 어떤 데이터들에 해당하는 것들을 고를 때 사용한다.
- WHERE IN이 들어가면 execute 가 먹지 않음 - query 사용하기
MYSQL 데이터 삭제하는 방법
1. DELETE
DELETE FROM 테이블명 (WHERE 조건)
: 조건 X - 모든 행 삭제
: 조건 O - 해당 행 삭제
2. DROP
DROP TABLE 테이블명
:테이블 삭제
3. TRUNCATE
TRUNCATE TABLE 테이블명
: 모든 행 삭제
DELETE VS TRUNCATE -
1. DELETE후 INSERT
- auto increment가 초기화되지 않아 id가 1부터 시작하지 않음
- WHERE 사용 가능
2. TRUNCATE 후 INSERT
- auto increment가 초기화되어 id가 1부터 시작
- WHERE 사용 X
DELETE 관련 발생 에러 - where 없이 삭제
원인
- DELETE를 하려고 하면 위와 같이 safe update mode이기 때문에 WHERE절 없이 사용할 수 없다는 에러가 뜬다.
해결 방법
- 다음 명령어를 통해서 일시적으로 해제가 가능하다.
set sql_safe_updates=0;
truncate 관련 발생 에러 - 외래키 제약 조건
원인
외래키를 사용하는 테이블의 내용을 삭제하려고 함. FK Constraint 외래 키 제약 조건에 따른 에러이다.
즉, 다른 테이블에서 delivery 테이블의 id를 외래키로 사용하고 있는데 delivery를 삭제하게 되면 무결성이 위반될 수 있으므로 뜬 것이다.
해결방법
set FOREIGN_KEY_CHECKS = 0;
truncate delivery;
set FOREIGN_KEY_CHECKS = 1;
참고
외래키 제약 조건이란?
FOREIGN KEY 제약 조건을 설정한 필드는 외래 키라고 부르며, 한 테이블을 다른 테이블과 연결해주는 역할을 합니다.
외래 키가 설정된 테이블에 레코드를 입력하면, 기준이 되는 테이블의 내용을 참조해서 레코드가 입력됩니다.
즉, FOREIGN KEY 제약 조건은 하나의 테이블을 다른 테이블에 의존하게 만듭니다.
나중에 더 알아볼 것
- query와 execute의 차이점 - 구동 원리와 관련 o
- createconnection과 createpool의 차이점 - 전자는 할 때마다 연결, 후자는 pool에 연결해둔걸 넣어놓음 / 후자 시간소모 적음 // promise(pool)와 콜백 방식 (connection) 의 차이점과 이후 사용법과 관련하여 설명
- prepared statements - ?와 []를 사용하는 이유 sql injection 예시 들어보기
- 참고(1,2,3) : https://velog.io/@jiumn/MySQL2-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%A1%9C-express-%EC%84%9C%EB%B2%84%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%97%B0%EA%B2%B0
- 참고(1,2): https://stackoverflow.com/questions/75773202/typeerror-connection-execute-is-not-a-function
- 참고(공식문서) https://sidorares.github.io/node-mysql2/docs#using-connection-pools