개발일지

Team project - 쇼핑몰 구현(3)

준서이 2024. 4. 29. 13:16

 

이번 포스트는 장바구니 테이블을 서술하겠다.

 

이 프로젝트를 하면서 제일 어려웠던 부분이었다.

Cart와  CartItem을 분류해서 작업하는 이유와 join에 join이 되있는 테이블을 찾아내는 걸 이해하는데 꽤 많은 시간이 걸렸다.ㅠㅠ 계속 하다보니 자연스럽게 받아들여지게 되었음. (그래도 결국은 이해를 해서 다행........)

 

시간을 제일 많이 잡아먹었던 Insert 부분.

오류는 발생하지 않았고, 프로그램이 제대로 구동되고 있어 원인을 찾느라 시간을 너무 많이 소비했다.

view에서는 나타나지 않았지만, 데이터베이스에 값이 제대로 들어가고 있었기에 service랑 controller을 다 뜯어보았다.^^

알고보니 Insert는 제대로 되고 있었고, Controller에서 list를 잘못 찾아와서 생긴 이슈였다. (중복으로 조회하였었다.)

 

선생님께서 쇼핑몰 프로젝트가 제일 어려운 프로젝트라고 하셨었는데,

프로젝트를 진행하면서 점점 그 말이 와 닿았다.

진짜 포기하고 싶을만큼 어려웠다.

(비전공자인 나의 한계인가 한참 생각함 ㅠㅠㅠㅠ 하지만 오기와 집착으로 찾아냄*-* 다 죽었어)

 

 

 

CartDTO

/*
작성자 : 정아름
작성일 : 24.02.19
작성내용 : 장바구니 구현
확인사항 : 테스트 완료
 */

package com.example.basic.DTO;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CartDTO {
    //장바구니 번호
    private Integer cartId;

    //회원 번호
    private Integer memberId;

    //등록일
    private LocalDateTime regDate;

    //수정일
    private LocalDateTime modDate;


    //추가사항
    //장바구니 상품 번호
    private Integer cartItemId;
}

 

 

 

 

CartItemDTO

/*
작성자 : 정아름
작성일 : 24.02.19
작성내용 : 장바구니 구현
확인사항 : 
 */

package com.example.basic.DTO;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class CartItemDTO {
    //장바구니 상품 번호
    private Integer cartItemId;

    //장바구니 상품 수량
    private Integer quantity;

    //등록일
    private LocalDateTime regDate;

    //수정일
    private LocalDateTime modDate;


    //추가사항
    //장바구니 번호
    private Integer cartId;

    //상품 번호
    private Integer productId;

    //상품 이름
    private String productName;

    //상품 이미지
    private String productImage;

    //상품 가격
    private Integer productPrice;

}

 

 

 

 

CartEntity

package com.example.basic.Entity;

/*
    작성자 : 정아름
    작성일 : 24.02.21
    수정사항 :
 */

import jakarta.persistence.*;
import lombok.*;


@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Builder
@Table(name = "cart")
@SequenceGenerator(name = "cart_SEQ", sequenceName = "cart_SEQ", initialValue = 1,
        allocationSize = 1)
public class CartEntity extends BaseEntity {
    //장바구니 번호
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "cart_SEQ")
    private Integer cartId;

    //다대일(N:1)관계를 정의
    //여러개의 주문이 하나의 회원 또는 상품에 해당할 수 있다.
    @ManyToOne(fetch = FetchType.LAZY)
    //외래키 정의
    //장바구니 테이블은 회원 테이블과 연관되어 있다.
    @JoinColumn(name = "member_id", nullable = false)
    private MemberEntity memberEntity;

}

 

 

 

CartItemEntity

package com.example.basic.Entity;

/*
    작성자 : 정아름
    작성일 : 24.02.21
    수정사항 :
 */

import jakarta.persistence.*;
import lombok.*;

@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Builder
@Table(name = "cartitem")
@SequenceGenerator(name = "cartitem_SEQ", sequenceName = "cartitem_SEQ", initialValue = 1,
        allocationSize = 1)
public class CartItemEntity extends BaseEntity {
    //장바구니 번호
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "cartitem_SEQ")
    private Integer cartItemId;

    //외래키 정의
    //장바구니상품 테이블은 장바구니 테이블과 연관되어 있다.
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "cart_id")
    private CartEntity cartEntity;

    //외래키 정의
    //장바구니상품 테이블은 상품 테이블과 연관되어 있다.
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "product_id")
    private ProductEntity productEntity;

    //장바구니 상품 수량
    private Integer quantity;

}

 

 

 

 

CartRepository

package com.example.basic.Repository;

import com.example.basic.Entity.CartEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface CartRepository extends JpaRepository<CartEntity, Integer> {

    //회원 찾기
    @Query(value = "SELECT s FROM CartEntity s WHERE s.memberEntity.memberId=:memberId")
    CartEntity findByMemberId(@Param("memberId") Integer memberId);

    @Query(value = "SELECT s FROM CartEntity s WHERE s.memberEntity.memberId=:memberId")
    List<CartEntity> findByMember(@Param("memberId") Integer memberId);
}

 

 

 

CartItemRepository

package com.example.basic.Repository;

import com.example.basic.Entity.CartItemEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.util.List;


@Repository
public interface CartItemRepository extends JpaRepository<CartItemEntity, Integer> {

    //장바구니 상품 찾기
    @Query(value = "SELECT w FROM CartItemEntity w WHERE w.cartEntity.cartId = :cartId")
    List<CartItemEntity> findAllByCartId(@Param("cartId") Integer cartId);

    @Query(value = "SELECT w FROM CartItemEntity w WHERE w.cartEntity.cartId = :cartId AND w.productEntity.productId = :productId")
    CartItemEntity findByCartIdAndProductId(Integer cartId, Integer productId);

    //장바구니 상품 찾기
    @Query(value = "SELECT w FROM CartItemEntity w WHERE w.cartEntity.cartId = :cartId")
    CartItemEntity findByCartId(Integer cartId);

    @Query(value = "select w FROM CartItemEntity w WHERE w.cartItemId = :cartItemId AND w.cartEntity.cartId = :cartId")
    CartItemEntity findByCartItemIdAndCartId(Integer cartItemId, Integer cartId);

    @Query(value = "select w FROM CartItemEntity w WHERE w.cartItemId = :cartItemId AND w.cartEntity.cartId = :cartId")
    CartItemEntity deleteById(Integer cartItemId, Integer cartId);

}

 

 

 

CartService

package com.example.basic.Service;

import com.example.basic.DTO.CartDTO;
import com.example.basic.DTO.CartItemDTO;
import com.example.basic.Entity.*;
import com.example.basic.Repository.CartItemRepository;
import com.example.basic.Repository.CartRepository;
import com.example.basic.Repository.MemberRepository;
import com.example.basic.Repository.ProductRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
@RequiredArgsConstructor
@Transactional
public class CartService {
    private final CartRepository cartRepository;
    private final ModelMapper modelMapper;
    private final MemberRepository memberRepository;
    private final CartItemRepository cartItemRepository;
    private final ProductRepository productRepository;

    //삽입
    public void cartInsert(Integer memberId, Integer productId, Integer quantity) {

        //회원 조회
        MemberEntity memberEntity = memberRepository.findById(memberId)
                .orElseThrow(() -> new IllegalArgumentException("Member not found"));

        // 회원의 장바구니 조회
        CartEntity cart = cartRepository.findByMemberId(memberEntity.getMemberId());
        if (cart == null) {
            cart = CartEntity.builder()
                    .memberEntity(memberEntity)
                    .build();
            cartRepository.save(cart);
        }

        ProductEntity product = productRepository.findById(productId).orElseThrow();

        // 장바구니에 상품이 이미 존재하는지 확인
        CartItemEntity cartItem = cartItemRepository.findByCartIdAndProductId(cart.getCartId(), product.getProductId());

        // 장바구니에 상품이 존재하지 않는다면 카트상품 생성 후 추가
        if (cartItem == null) {
            cartItem = CartItemEntity.builder()
                    .cartEntity(cart)
                    .productEntity(product)
                    .quantity(quantity)
                    .build();
            cartItemRepository.save(cartItem);
        } else {
            // 장바구니에 상품이 존재하면 수량만 변경
            cartItem.setQuantity(cartItem.getQuantity() + quantity);
            cartItemRepository.save(cartItem);
        }
    }


    //수정
    public void cartUpdate(Integer memberId, Integer quantity) {

        //회원 조회
        Optional<MemberEntity> member = memberRepository.findById(memberId);
        MemberEntity memberEntity = member.orElseThrow();

        //회원의 장바구니 조회
        CartEntity cart = cartRepository.findByMemberId(memberEntity.getMemberId());

        //장바구니 상품 조회
        CartItemEntity cartItem = cartItemRepository.findByCartId(
                cart.getCartId());

        if (cartItem != null) {
            cartItem.setQuantity(quantity);
            cartItemRepository.save(cartItem);
        }
    }

    //삭제
    public void cartDelete(Integer cartItemId, Integer cartId) {

        cartItemRepository.deleteById(cartItemId, cartId);
    }

    //전체 조회(장바구니 상품)
    public List<CartItemDTO> cartList(Integer memberId) {
        //회원의 장바구니 조회
        CartEntity cart = cartRepository.findByMemberId(memberId);

        //장바구니 상품 찾기
        List<CartItemEntity> cartEntities = cartItemRepository.findAllByCartId(cart.getCartId());

        //장바구니에 상품이 존재하면
        if (cartEntities != null) {
            //상품 정보 담기
            List<CartItemDTO> cartItemDTOS = new ArrayList<>();
            for (CartItemEntity cartItemEntity : cartEntities) {
                //상품 리스트에 상품이 존재하면
                if (cartItemEntity != null) {

                    CartItemDTO cartItemDTO = modelMapper.map(cartItemEntity, CartItemDTO.class);
                    cartItemDTOS.add(cartItemDTO);
                }
            }
            return cartItemDTOS;
        } else {
            //장바구니에 상품이 존재하지 않으면
            return Collections.emptyList();
        }
    }

    //장바구니 아이템 개별조회
    public CartItemDTO cartItemDetail(Integer cartItemId, Integer cartId) {
        CartItemEntity cartItemEntity = cartItemRepository.findByCartItemIdAndCartId(cartItemId, cartId);

        CartItemDTO cartItemDTO = modelMapper.map(cartItemEntity, CartItemDTO.class);

        return cartItemDTO;
    }

    //회원의 장바구니 개별조회
    public CartDTO cartDetail(Integer memberId) {

        //회원의 장바구니 생성
        CartEntity cart = cartRepository.findByMemberId(memberId);

        CartDTO cartDTO = modelMapper.map(cart, CartDTO.class);

        return cartDTO;
    }
}

 

 

 

@Transactional

모든 작업들이 성공적으로 완료되어야 작업 묶음의 결과를 적용하고, 어떤 작업에서 오류가 발생했을 때는 이전에 있던 모든 작업들이 성공적이었더라도 없었던 일처럼 완전히 되돌리는 것이 트랜잭션의 개념이다.

데이터베이스를 다룰 때 트랜잭션을 적용하면 데이터 추가, 수정, 삭제 등으로 이루어진 작업을 처리하던 중 오류가 발생했을 때 모든 작업들을 원상태로 되돌릴 수 있다. 모든 작업들이 성공해야만 최종적으로 데이터베이스에 반영하도록 한다.

@Transactional이 붙은 메서드는 메서드가 포함하고 있는 작업 중에 하나라도 실패할 경우 전체 작업을 취소한다.

 

 

 

CartController


/*
    설명 : 상품관리의 목록, 수정, 삭제, 조회로 이동하는 페이지 영역
    입력값 : /cart/list, /cart/insert, /cart/update, /cart/delete, /cart/detail
    출력값 : cart/list, cart/insert, cart/update, cart/detail
    작성일 : 24.02.22
    작성자 : 정아름
    수정사항 : 상품관리의 전체 목록 페이지는 list 처리 하기로 함
 */

package com.example.basic.Controller;

import com.example.basic.DTO.CartDTO;
import com.example.basic.DTO.CartItemDTO;
import com.example.basic.DTO.MemberDTO;
import com.example.basic.DTO.ProductDTO;
import com.example.basic.Service.CartService;
import com.example.basic.Service.MemberService;
import com.example.basic.Service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.List;

@Controller
@RequiredArgsConstructor
public class CartController {
    //주입
    private final CartService cartService;
    private final MemberService memberService;
    private final ProductService productService;

    //전체 조회
    @GetMapping("/cart/list")
    public String listForm(@AuthenticationPrincipal User user, Model model,
                           RedirectAttributes redirectAttributes) throws NullPointerException {
        //회원 정보가 있으면
        //로그인 정보로 회원 조회
        MemberDTO memberDTO = memberService.detail(user.getUsername());
        if (memberDTO != null) {

            //회원의 장바구니 목록 조회
            List<CartItemDTO> cartItemDTO = cartService.cartList(memberDTO.getMemberId());

            //장바구니에 들어있는 상품의 총 가격
            int totalPrice = 0;
            for (CartItemDTO cartItem : cartItemDTO) {
                totalPrice += cartItem.getQuantity() * cartItem.getProductPrice();
            }
            if (cartItemDTO.isEmpty()) {

                redirectAttributes.addFlashAttribute("successMessage",
                        "장바구니가 비어있습니다.");
            }

            model.addAttribute("totalPrice", totalPrice);
            model.addAttribute("totalCount", cartItemDTO);
            model.addAttribute("mid", memberDTO.getMemberId());
            model.addAttribute("list", cartItemDTO);

            return "cart/list";
            //회원 정보가 없으면 상품 목록 페이지로 이동
        } else {
            return "redirect:/product/list";
        }
    }

    //삽입
    @PostMapping("/cart/insert/{id}")
    public String insertProc(@PathVariable Integer id,
                             @RequestParam Integer quantity,
                             @AuthenticationPrincipal User user,
                             RedirectAttributes redirectAttributes) {

        //회원 정보가 없으면 상품 목록 페이지로 이동
        if (user == null) {
            return "redirect:/product/list";
        }

        //유저 정보 가져오기
        String memberEmail = user.getUsername();
        Integer memberId = memberService.detail(memberEmail).getMemberId();

        ProductDTO productDTO = productService.productDetail(id);

        //선택한 수량이 상품의 재고량보다 많을 때 목록 페이지로 이동
        if (productDTO == null || quantity > productDTO.getQuantityCount()) {
            redirectAttributes.addFlashAttribute("errorMessage",
                    "재고량이 부족합니다.");
            return "redirect:/product/detail/" + id;
        }

        cartService.cartInsert(memberId, productDTO.getProductId(), quantity);

        redirectAttributes.addFlashAttribute("successMessage",
                "장바구니에 저장되었습니다.");

        return "redirect:/cart/list";
    }

    //수정
    @GetMapping("/cart/update")
    public String updateForm(Integer mid, Integer id, Model model) {

        CartDTO cartDTO = cartService.cartDetail(mid);
        CartItemDTO cartItemDTO = cartService.cartItemDetail(id, cartDTO.getCartId());

        model.addAttribute("mid", mid);
        model.addAttribute("cartItem", cartItemDTO);

        return "cart/update";
    }

    @PostMapping("/cart/update")
    public String updateProc(Integer mid, CartItemDTO cartItemDTO,
                             RedirectAttributes redirectAttributes) {
        cartService.cartUpdate(mid, cartItemDTO.getQuantity());

        redirectAttributes.addFlashAttribute("successMessage",
                "장바구니가 수정되었습니다.");

        return "redirect:/cart/list";
    }

    //삭제
    @GetMapping("/cart/delete")
    public String deleteProc(Integer mid, Integer id, RedirectAttributes redirectAttributes) {

        CartDTO cartDTO = cartService.cartDetail(mid);

        cartService.cartDelete(id, cartDTO.getCartId());

        redirectAttributes.addFlashAttribute("successMessage",
                "장바구니 상품이 삭제되었습니다.");

        return "redirect:/cart/list";
    }
}

 

 

View에 처리할 때 상품의 개수만큼 상품의 가격에 곱해 총 금액에 나타내주었다.

 

 

 

cart/list

<!--
파일명 : cart/list
작성자 : 정아름
작성일 : 24.02.21
수정사항 : 글씨체 적용
          장바구니 화면에 표시할 내역들 다시 확인하기.
-->
<!DOCTYPE html>
<html lang="ko"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/main}"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <title>SalPick</title>
    <!-- bootstrap -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
    <!-- google fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&display=swap" rel="stylesheet">
    <!-- google icon -->
    <link rel="stylesheet"
          href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0"/>
    <style>
        body {
            font-family: Verdana, sans-serif;
            font-size: 15px;
        }

        h2 {
            font-family: "Nanum Pen Script", cursive;
            font-weight: 400;
            font-size: 50px;
            font-style: normal;
        }
    </style>
</head>
<body>
<div layout:fragment="content">
    <div class="row">
        <!-- 여백 -->
        <div class="col-sm-3"></div>
        <!-- 장바구니 상품 목록-->
        <div class="col-sm-6">
            <div class="container mt-5 mb-5">
                <h2>장바구니 목록</h2>
                <div class="row">
                    <div class="card">
                        <div class="card-body">
                            <table class="table table-hover">
                                <thead>
                                <tr>
                                    <th>No</th>
                                    <th>상품이미지</th>
                                    <th>상품명</th>
                                    <th>상품코드번호</th>
                                    <th>상품수량</th>
                                    <th>상품가격</th>
                                    <th>총 금액</th>
                                    <th>작업</th>
                                </tr>
                                </thead>
                                <tbody>
                                <tr th:each="data:${list}">
                                    <td th:text="${data.cartItemId}">cartItemId</td>
                                    <td>
                                        <img class="img1" th:src="|/images/product/@{${data.productImage}}|"
                                             alt="이미지 없음"
                                             width="50%">
                                    </td>
                                    <td>
                                        <a th:text="${data.productName}"
                                           th:href="@{/product/detail(id=${data.productId})}">
                                            productName
                                        </a>
                                    </td>
                                    <td th:text="${data.productId}">productId</td>
                                    <td th:text="${data.quantity}">quantity</td>
                                    <td th:text="${data.productPrice}">productPrice</td>
                                    <td th:text="${data.quantity}*(${data.productPrice})"></td>
                                    <td>
                                        <button type="button" class="btn btn-outline-success btn-sm"
                                                th:onclick="|location.href='@{/cart/update(mid=${mid}, id=${data.cartItemId})}'|"
                                                sec:authorize="hasAnyRole('USER', 'ADMIN')">
                                            변경
                                        </button>
                                        <button type="button" class="btn btn-outline-danger btn-sm"
                                                th:onclick="|location.href='@{/cart/delete(mid=${mid}, id=${data.cartItemId})}'|"
                                                sec:authorize="hasAnyRole('USER', 'ADMIN')">
                                            삭제
                                        </button>
                                        <button type="button" class="btn btn-outline-primary btn-sm"
                                                th:onclick="|location.href='@{/order/insert(mid=${mid}, id=${data.productId})}'|"
                                                sec:authorize="hasAnyRole('USER', 'ADMIN')">
                                            주문
                                        </button>
                                    </td>
                                </tr>
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <!-- 여백 -->
        <div class="col-sm-3"></div>
    </div>
</div>
<script th:inline="javascript">
    /* 작업 성공했을 때 성공메세지 창을 출력 */
    var successMessage = /*[[ ${successMessage} ]]*/ null;
    if (successMessage) {
        alert(successMessage);
    }
</script>
</body>
</html>

 

 

 

cart/update

<!--
파일명 : cart/list
작성자 : 정아름
작성일 : 24.02.21
수정사항 : input 태그로 수정하고 변수명 확인.
-->
<!DOCTYPE html>
<html lang="ko"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
      layout:decorate="~{layouts/main}"
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
    <meta charset="UTF-8">
    <title>SalPick</title>
    <!-- bootstrap -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
    <!-- google fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&display=swap" rel="stylesheet">
    <!-- google icon -->
    <link rel="stylesheet"
          href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0"/>
    <style>
        body {
            font-family: Verdana, sans-serif;
            font-size: 15px;
        }

        h2 {
            font-family: "Nanum Pen Script", cursive;
            font-weight: 400;
            font-size: 50px;
            font-style: normal;
        }
    </style>
</head>
<body>
<div layout:fragment="content">
    <div class="row">
        <!-- 여백 -->
        <div class="col-sm-3"></div>
        <!-- 장바구니 상품 목록-->
        <div class="col-sm-6">
            <div class="container mt-5 mb-5">
                <h2>장바구니 수정</h2>
                <div class="container mt-3 mb-3">
                    <form th:action="@{/cart/update(mid=${mid}, id=${cartItem.cartItemId})}" method="post">
                        <input type="hidden" name="mid" th:value="${mid}">
                        <input type="hidden" name="id" th:value="${cartItem.cartItemId}">
                        <div class="card">
                            <div class="card-body">
                                <label for="quantity" class="form-label">수량 변경 : </label>
                                <input type="number" class="form-control" id="quantity" name="quantity">
                            </div>
                        </div>
                        <button type="submit" class="btn btn-outline-success float-end"
                                sec:authorize="hasAnyRole('USER', 'ADMIN')">
                            저장
                        </button>
                    </form>
                </div>
            </div>
        </div>
        <!-- 여백 -->
        <div class="col-sm-3"></div>
    </div>
</div>
<script th:inline="javascript">
    /* 작업 성공했을 때 성공메세지 창을 출력 */
    var successMessage = /*[[ ${successMessage} ]]*/ null;
    if (successMessage) {
        alert(successMessage);
    }
</script>
</body>
</html>

 

 

 

 

화면 캡쳐

장바구니 페이지(화면구성은 미흡하지만 나타난 게 어디인가... ㅜㅜ)