carts API를 구현하던 중, 장바구니에 해당 상품이 이미 담겨져 있으면 Update를 해야하고, 해당 상품이 담겨져있지 않다면 Insert를 해야하는 상황이 생겼다.
때문에 나는 처음에는 아래와 같이 작성하였었다.
// cartService.js
const addCartInDetailPage = async (userId, productId) => {
const cartCheck = await cartDao.cartCheck(userId, productId);
if (!cartCheck) {
await cartDao.addNewProductInCart(userId, productId);
return "addNewProductInCart";
}
if (cartCheck) {
await cartDao.plusOneQuantity(userId, productId);
return "plusOneQuantity";
}
};
cartCheck이라는 메서드를 만들어, 장바구니에 해당 유저가 해당 상품을 담아두었는지 확인하고, 해당 상품을 담아두었었다면 plusOneQunatity를 하고, 해당 상품을 담아두지 않았다면 addNewProductInCart를 하는 방법을 택했었다.
하지만 호준님의 피드백은 이렇게 왔었다.
사실 지금도 무슨 말인지 잘 모르겠다 ... ^^ ...
그래서 이런 저런 구글링을 하다보니 UPSERT라는 구문이 있다는 것을 알게 되었고, 내가 기존에 작성한 코드를 모두 지운 후 아래와 같이 코드를 작성해보았다.
// cartService.js
const addCart = async (userId, productId) => {
return await cartDao.addCart(userId, productId);
};
// cartDao.js
const addCart = async (userId, productId) => {
try {
return await appDataSource.query(
`INSERT INTO
carts (user_id, product_id, quantity)
VALUES (?, ?, 1)
ON DUPLICATE KEY UPDATE
user_id = ?, product_id = ?, quantity = quantity + 1;`,
[userId, productId, userId, productId]
);
} catch (err) {
console.log(err);
const error = new Error("Can't add product in cart!");
error.statusCode = 500;
}
};
보기에도 훨씬 가독성이 높아졌다.
INSERT INTO
carts (user_id, product_id, quantity)
VALUES (?, ?, 1)
ON DUPLICATE KEY UPDATE
user_id = ?, product_id = ?, quantity = quantity + 1;
이 부분을 하나씩 뜯어보자면,
carts라는 테이블의 user_id, product_id, quantity 칼럼에 해당 value를 insert한다. 하지만 duplicate key가 있다면, quantity를 하나만 올려서 update한다는 의미이다.
UPSERT 구문을 사용하기 위해선 uniqe key가 제약조건으로 걸려있어야 하는데, 나는 이 부분에서도 많은 고민을 했다. 대부분의 구글링 검색 결과에서는 email, 혹은 id에 unique key를 걸어두었기 때문이었고, 한가지의 칼럼에만 unique key를 걸어두는 것은 내가 구현하고자하는 것과는 거리가 멀었기 때문이다.
그러다가 user_id와 product_id를 동시에 Unique key로 걸어두는 건 어떨까 싶어서 table 생성을 이렇게 해보았다.
CREATE TABLE carts (
id INT NOT NULL AUTO_INCREMENT,
user_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY unique_index (user_id, product_id),
FOREIGN KEY (user_id) REFERENCES users (id),
FOREIGN KEY (product_id) REFERENCES products (id)
ON DELETE CASCADE
);
여기에서 주목해야할 곳은 UNIQUE KEY unique_index (user_id, product_id)
이 부분이다. user_id와 product_id를 동시에 unique key로 제약조건을 걸어주었고, 이렇게 하니 내가 원하는 것을 구현할 수 있었다!
user_id 7번이 product_id 4번을 장바구니에 추가를 시도한 경우, 동시에 이미 해당 상품이 장바구니 데이터베이스에 있던 경우, 위의 스크린샷과 같이 데이터베이스에 잘 update 되어 들어간다!
그리고... upsert를 이용하여 다시 커밋했는데... 호준님의 댓글...
나 잊지 않을 거야...
일부러 말씀 안 해주는 이유가 뭔데요 ㅠ 너무해 ㅠ
그래도 덕분에 공부했습니다! 항상 감사드립니다! 호준님!
호준님ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ 후 저도 가은님이 upsert 알려주신 덕분에 멘토님 리뷰 한방에 통과했습니당 🙌