관련포스팅 : # Spring - 미니쇼핑몰 상품관리 구현해보기(상품목록, 상세보기)
앞선 포스팅에서는 미니쇼핑몰의 상품목록과 상세보기를 구현해보았는데 이번에는 장바구니 기능을 구현해보았다. 장바구니에서 구현된 구체적인 기능은 상품추가, 수량변경, 삭제가 있다. 이 기능들은 앞서 구현해보았던 것과 거의 동일하기 때문에 새롭게 알게된 내용들 위주로 정리해보자.
DB(Oracle) 장바구니 테이블 생성, 외래키 설정 SQL
-- 장바구니 테이블 생성
CREATE TABLE tbl_cart(
cart_id NUMBER NOT NULL PRIMARY KEY,
user_id VARCHAR2(50) NOT NULL,
product_id NUMBER NOT NULL,
amount NUMBER DEFAULT 0
);
-- 장바구니 테이블 시퀀스 생성
CREATE SEQUENCE seq_cart START WiTH 10 INCREMENT BY 1;
COMMIT;
-- 장바구니 테이블 제약조건(외래키) 생성
ALTER TABLE tbl_cart ADD CONSTRAINT cart_userid_fk FOREIGN KEY(user_id) REFERENCES tbl_member(user_id);
ALTER TABLE tbl_cart ADD CONSTRAINT cart_product_fk FOREIGN KEY(product_id) REFERENCES tbl_product(product_id);
COMMIT;
상품 테이블과 장바구니 테이블의 관계도
View
1. productList.jsp(제품상세화면) : 상품 장바구니에 담기
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>상품 상세정보</title>
<%@ include file="../include/header.jsp" %>
</head>
<body>
<%@ include file="../include/menu.jsp" %>
<h2>상품 상세정보</h2>
<table border="1">
<tr>
<td>
<img src="${path}/images/${vo.productUrl}" width="340" height="300">
</td>
<td>
<table border="1" style="height: 300px; width: 400px;">
<tr align="center">
<td>상품명</td>
<td>${vo.productName}</td>
</tr>
<tr align="center">
<td>가격</td>
<td><fmt:formatNumber value="${vo.productPrice}" pattern="###,###,###"/></td>
</tr>
<tr align="center">
<td>상품소개</td>
<td>${vo.productDesc}</td>
</tr>
<tr align="center">
<td colspan="2">
<form name="form1" method="post" action="${path}/shop/cart/insert.do">
<input type="hidden" name="productId" value="${vo.productId}">
<select name="amount">
<c:forEach begin="1" end="10" var="i">
<option value="${i}">${i}</option>
</c:forEach>
</select> 개
<input type="submit" value="장바구니에 담기">
</form>
<a href="${path}/shop/product/list.do">상품목록</a>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
<form name="form1" method="post" action="${path}/shop/cart/insert.do">
- 상품을 장바구니에 추가시키기 위해 상품id번호, 수량을
form
태그 전송
<input type="hidden" name="productId" value="${vo.productId}">
- 현재의 상품ID를 입력받기 위해
hidden
속성으로 처리<select name="amount"> <c:forEach begin="1" end="10" var="i"> <option value="${i}">${i}</option> </c:forEach> </select>
select
태그를forEach
문으로 1~10까지 수량을 선택할 수 있도록 처리
2. cartList.jsp(장바구니 화면)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>상품장바구니 목록</title>
<%@ include file="../include/header.jsp" %>
<script>
$(document).ready(function(){
// 리스트 페이지로 이동
$("#btnList").click(function(){
location.href="${path}/shop/product/list.do";
});
});
</script>
</head>
<body>
<%@ include file="../include/menu.jsp" %>
<h2>장바구니 확인</h2>
<c:choose>
<c:when test="${map.count == 0}">
장바구니가 비어있습니다.
</c:when>
<c:otherwise>
<form name="form1" id="form1" method="post" action="${path}/shop/cart/update.do">
<table border="1">
<tr>
<th>상품명</th>
<th>단가</th>
<th>수량</th>
<th>금액</th>
<th>취소</th>
</tr>
<c:forEach var="row" items="${map.list}" varStatus="i">
<tr>
<td>
${row.productName}
</td>
<td style="width: 80px" align="right">
<fmt:formatNumber pattern="###,###,###" value="${row.productPrice}"/>
</td>
<td>
<input type="number" style="width: 40px" name="amount" value="${row.amount}" min="1">
<input type="hidden" name="productId" value="${row.productId}">
</td>
<td style="width: 100px" align="right">
<fmt:formatNumber pattern="###,###,###" value="${row.money}"/>
</td>
<td>
<a href="${path}/shop/cart/delete.do?cartId=${row.cartId}">삭제</a>
</td>
</tr>
</c:forEach>
<tr>
<td colspan="5" align="right">
장바구니 금액 합계 : <fmt:formatNumber pattern="###,###,###" value="${map.sumMoney}"/><br>
배송료 : ${map.fee}<br>
전체 주문금액 :<fmt:formatNumber pattern="###,###,###" value="${map.allSum}"/>
</td>
</tr>
</table>
<input type="hidden" name="count" value="${map.count}">
<button type="submit" id="btnUpdate">수정</button>
</form>
</c:otherwise>
</c:choose>
<button type="button" id="btnList">상품목록</button>
</body>
</html>
<c:choose> <c:when test="${map.count == 0}">장바구니가 비어있습니다.</c:when> <c:otherwise>장바구니 목록 출력</c:otherwise> </c:choose>
- 장바구니에 담긴 상품이 0이면 “장바구니가 비었습니다.”라는 문구를 출력해주고, 0이 아니면 장바구니 목록을 출력해준다.
<c:forEach var="row" items="${map.list}" varStatus="i">장바구니 레코드</c:forEach>
- forEach 반목문을 통해 장바구니 레코드를 출력
<input type="number" name="amount" value="${row.amount}" min="1" style="width: 40px">
<input type="hidden" name="productId" value="${row.productId}">
- 상품수량 변경, 최소값을 1로 설정해주고, 상품수량 변경을 위해 상품ID번호를
hidden
속성으로 입력name
속성을 배열첨자([])로 처리하지 않은 이유는 다음과 같다. 동일한name
속성이 반복적으로 입력되면 서버에서 배열로 쌓아서 작업을 처리해주기 때문이다. 즉 다시말하자면 화면으로부터 동일한name
속성의 값들이 CartController의update()
(장바구니수정)메서드의 매개변수int[] amount, int[] productId
에 배열로 저장되어 처리된다.
<form name="form1" id="form1" method="post" action="${path}/shop/cart/update.do"></form>
- 장바구니에 담긴 상품수량 업데이트 처리
<a href="${path}/shop/cart/delete.do?cartId=${row.cartId}">삭제</a>
- 장바구니에 담긴 상품 삭제 처리
Controller(흐름제어)
CartController : 장바구니 관련 Controller
@Controller
@RequestMapping("/shop/cart/*")
public class CartController {
@Inject
CartService cartService;
// 1. 장바구니 추가
@RequestMapping("insert.do")
public String insert(@ModelAttribute CartVO vo, HttpSession session){
String userId = (String) session.getAttribute("userId");
vo.setUserId(userId);
// 장바구니에 기존 상품이 있는지 검사
int count = cartService.countCart(vo.getProductId(), userId);
count == 0 ? cartService.updateCart(vo) : cartService.insert(vo);
if(count == 0){
// 없으면 insert
cartService.insert(vo);
} else {
// 있으면 update
cartService.updateCart(vo);
}
return "redirect:/shop/cart/list.do";
}
// 2. 장바구니 목록
@RequestMapping("list.do")
public ModelAndView list(HttpSession session, ModelAndView mav){
String userId = (String) session.getAttribute("userId"); // session에 저장된 userId
Map<String, Object> map = new HashMap<String, Object>();
List<CartVO> list = cartService.listCart(userId); // 장바구니 정보
int sumMoney = cartService.sumMoney(userId); // 장바구니 전체 금액 호출
// 장바구니 전체 긍액에 따라 배송비 구분
// 배송료(10만원이상 => 무료, 미만 => 2500원)
int fee = sumMoney >= 100000 ? 0 : 2500;
map.put("list", list); // 장바구니 정보를 map에 저장
map.put("count", list.size()); // 장바구니 상품의 유무
map.put("sumMoney", sumMoney); // 장바구니 전체 금액
map.put("fee", fee); // 배송금액
map.put("allSum", sumMoney+fee); // 주문 상품 전체 금액
mav.setViewName("shop/cartList"); // view(jsp)의 이름 저장
mav.addObject("map", map); // map 변수 저장
return mav;
}
// 3. 장바구니 삭제
@RequestMapping("delete.do")
public String delete(@RequestParam int cartId){
cartService.delete(cartId);
return "redirect:/shop/cart/list.do";
}
// 4. 장바구니 수정
@RequestMapping("update.do")
public String update(@RequestParam int[] amount, @RequestParam int[] productId, HttpSession session) {
// session의 id
String userId = (String) session.getAttribute("userId");
// 레코드의 갯수 만큼 반복문 실행
for(int i=0; i<productId.length; i++){
CartVO vo = new CartVO();
vo.setUserId(userId);
vo.setAmount(amount[i]);
vo.setProductId(productId[i]);
cartService.modifyCart(vo);
}
return "redirect:/shop/cart/list.do";
}
}
String userId = (String) session.getAttribute("userId");
- 삭제처리를 제외한 모든 메서드에서 session의 id값을 저장
@RequestMapping("insert.do")
: 장바구니에 상품 입력처리 매핑
- 장바구니에 추가하려는 상품이 목록에 있는지 유무를 검사하여 없으면 insert처리, 없으면 update처리
@RequestMapping("list.do")
: 장바구니 상품 목록처리 매핑
- 변수
list
에 장바구니 리스트 객체를 저장- 변수
sumMoney
에 장바구니에 담긴 전체 상품의 금액을 저장int fee = sumMoney >= 100000 ? 0 : 2500;
삼항연사자 :(조건식) ? (true) : (false)
장바구니 전체 금액에 따라 배송비 지불여부를 구분 (10만원이상일 경우 무료, 이하면 2500원)
@RequestMapping("delete.do")
: 장바구니 상품 레코드 삭제처리 매핑
- 레코드 삭제처리 후 장바구니 목록으로 리다이렉트
@RequestMapping("update.do")
: 장바구니 수량 변경처리 매핑
- 장바구니 수정 처리작업을 장바구니 목록의 레코드의 길이만큼 반복문을 수행
Service(비지니스로직, DB연동 이외의 핵심업무처리)
CartServiceImpl - CartService인터페이스를 구현한 메서드
@Service
public class CartServiceImpl implements CartService {
@Inject
CartDAO cartDao;
// 1. 장바구니 추가
@Override
public void insert(CartVO vo) {
cartDao.insert(vo);
}
// 2. 장바구니 목록
@Override
public List<CartVO> listCart(String userId) {
return cartDao.listCart(userId);
}
// 3. 장바구니 삭제
@Override
public void delete(int cartId) {
cartDao.delete(cartId);
}
// 4. 장바구니 수정
@Override
public void modifyCart(CartVO vo) {
cartDao.modifyCart(vo);
}
// 5. 장바구니 금액 합계
@Override
public int sumMoney(String userId) {
return cartDao.sumMoney(userId);
}
// 6. 장바구니 상품 확인
@Override
public int countCart(int productId, String userId) {
return cartDao.countCart(productId, userId);
}
// 7. 장바구니 상품 수량 변경
@Override
public void updateCart(CartVO vo) {
cartDao.updateCart(vo);
}
}
insert(CartVO vo)
: cartDao의 장바구니 상품추가 메서드 호출listCart(String userId)
: cartDao의 장바구니 상품목록처리 메서드 호출, 리턴delete(int cartId)
: cartDao의 장바구니 상품삭제처리 메서드 호출sumMoney(String userId)
: cartDao의 장바구니 상품전체 금액을 구하는 메서드 호출, 리턴countCart(int productId, String userId)
: cartDao의 장바구니 동일한 상품이 있는지 확인하는 메서드 호출, 리턴
modifyCart(CartVO vo)
: cartDao의 장바구니 상품수량 변경처리 메서드 호출updateCart(CartVO vo)
: cartDao의 동일한 상품일 경우 상품수량을 합산하는 메서드 호출
modifyCart()
를 호출하는 경우는 장바구니 화면에서 상품수량을 변경할 때updateCart()
를 호출하는 경우는 상품목록 화면에서 동일한 상품을 장바구니에 담았을 때
DAO(비지니스 로직, DB연동 작업처리)
CartDAOImpl - CartDAO인터페이스를 구현한 클래스
@Repository
public class CartDAOImpl implements CartDAO {
@Inject
SqlSession sqlSession;
// 1. 장바구니 추가
@Override
public void insert(CartVO vo) {
sqlSession.insert("cart.insertCart", vo);
}
// 2. 장바구니 목록
@Override
public List<CartVO> listCart(String userId) {
return sqlSession.selectList("cart.listCart", userId);
}
// 3. 장바구니 삭제
@Override
public void delete(int cartId) {
sqlSession.delete("cart.deleteCart", cartId);
}
// 4. 장바구니 수정
@Override
public void modifyCart(CartVO vo) {
sqlSession.update("cart.modifyCart", vo);
}
// 5. 장바구니 금액 합계
@Override
public int sumMoney(String userId) {
sqlSession.selectOne("cart.sumMoney", userId);
return sqlSession.selectOne("cart.sumMoney", userId);
}
// 6. 장바구니 동일한 상품 레코드 확인
@Override
public int countCart(int productId, String userId) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("productId", productId);
map.put("userId", userId);
return sqlSession.selectOne("cart.countCart", map);
}
// 7. 장바구니 상품수량 변경
@Override
public void updateCart(CartVO vo) {
sqlSession.update("cart.sumCart", vo);
}
}
insert(CartVO vo)
: 장바구니 상품 insertlistCart(String userId)
: 장바구니 상품목록 select조회한 결과를 리턴delete(int cartId)
: 장바구니 상품 deletemodifyCart(CartVO vo)
: 장바구니 상품수량 updatesumMoney(String userId)
: 장바구니 상품전체 금액 select조회한 결과를 리턴countCart(int productId, String userId)
: 장바구니에 동일한 상품이 있는지 select조회한 결과를 리턴updateCart(CartVO vo)
: 동일한 상품일 경우 수량을 합산하여 update
MybatisMapper
cartMapper - 장바구니 관련 mapper
<mapper namespace="cart">
<!-- 1. 장바구니 추가 -->
<insert id="insertCart">
INSERT INTO tbl_cart(
cart_id, user_id, product_id, amount
) VALUES (
seq_cart.NEXTVAL, #{userId}, #{productId}, #{amount}
)
</insert>
<!-- 2. 장바구니 목록 -->
<select id="listCart" resultType="com.example.spring02.model.shop.dto.CartVO">
SELECT
c.cart_id AS cartId,
c.user_id AS userId,
p.product_id As productId,
m.user_name AS userName,
p.product_name AS productName,
c.amount,
p.product_price AS productPrice,
(product_price * amount) money
FROM
tbl_member m, tbl_product p, tbl_cart c
WHERE m.user_id = c.user_id
AND p.product_id = c.product_id
AND c.user_id = #{userId}
ORDER BY
c.cart_id
</select>
<!-- 3. 장바구니 전체 금액 -->
<select id="sumMoney" resultType="int">
SELECT NVL(SUM(product_price * amount), 0) money
FROM tbl_cart c, tbl_product p
WHERE c.product_id = p.product_id AND c.user_id = #{userId}
</select>
<!-- 4. 장바구니 수정 -->
<update id="modifyCart">
UPDATE tbl_cart
SET amount = #{amount}
WHERE user_id= #{userId}
AND product_id = #{productId}
</update>
<!-- 5. 장바구니 삭제 -->
<delete id="deleteCart">
DELETE FROM tbl_cart
WHERE cart_id = #{cartId}
</delete>
<!-- 6. 장바구니에 동일한 상품 레코드 확인 -->
<select id="countCart" resultType="int">
SELECT COUNT(*)
FROM tbl_cart
WHERE user_id= #{userId}
AND product_id = #{productId}
</select>
<!-- 7. 장바구니에 동일한 상품이 존재하면 수정 -->
<update id="updateCart">
UPDATE tbl_cart
SET amount = amount + #{amount}
WHERE user_id= #{userId}
AND product_id = #{productId}
</update>
</mapper>
insertCart
: 장바구니 insertlistCart
: 장바구니 전체 목록을 selectsumMoney
: 장바구니에 담긴 상품과 수량 전체를 합산한 결과를 select, 장바구니가 비어있으면 0deleteCart
: 장바구니 deletecountCart
: 장바구니 동일한 상품 레코드가 있으면 select count
modifyCart
: 장바구니 수량 update(수량 덮어쓰기)updateCart
: 장바구니 동일한 상품 레코드가 있으면 기존의 수량과 입력받은 수량을 합산하여 update(수량 합산하기)