SPRING MVC6_웹페이지 만들기

(1) 프로젝트 생성

1. 프로젝트 생성

  • start.spring.io
프로젝트 선택
 Project: Gradle Project
 Language: Java
 Spring Boot: 2.4.x
Project Metadata
 Group: hello
 Artifact: springmvc
 Name: springmvc
 Package name: hello.springmvc
 Packaging: Jar (주의!)
 Java: 11
 Dependencies: Spring Web, Thymeleaf, Lombok

2. 웰컴페이지 추가

  • index.html
    : Jar 이용 시 /resources/static/index.hml 위치에 두면 welcome페이지 처리 ( localhost:8080 시 접속 )
<!DOCTYPE html>
<html>
<head>
 <meta charset="UTF-8">
 <title>Title</title>
</head>
<body>
<ul>
 <li>상품 관리
 <ul>
 <li><a href="/basic/items">상품 관리 - 기본</a></li>
 </ul>
 </li>
</ul>
</body>
</html>

(2) 요구사항 분석

1. 상품 도메인 모델

  • 상품 ID
  • 상품명
  • 가격
  • 수량

2. 상품 관리 기능

  • 상품 목록
  • 상품 상세
  • 상품 등록
  • 상품 수정

3. 서비스 제공 흐름


(3) 상품 도메인 개발

1. Item. 상품 객체

2. ItemRepository - 상품 저장소

3. ItemRepositoryTest - 상품 저장소 테스트

package hello.itemservice.domain.item;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

public class ItemRepositoryTest {

    ItemRepository itemRepository = new ItemRepository();

    @AfterEach
            void afterEach(){
        itemRepository.clearStore();
    }


    @Test
    void save(){
        //given
        Item item = new Item("itemA",10000,10);

        //when
        Item savedItem = itemRepository.save(item);

        //then
        Item findItem = itemRepository.findById(item.getId());
        assertThat(findItem).isEqualTo(savedItem);
    }

    @Test
    void findAll(){
        //given
        Item item1 = new Item("itemA",10000,10);
        Item item2 = new Item("itemB",20000,20);

        itemRepository.save(item1);
        itemRepository.save(item2);

        //when
        List<Item> result = itemRepository.findAll();

        //then
        assertThat(result.size()).isEqualTo(2);
        assertThat(result).contains(item1,item2);
    }

    @Test
    void updateItem(){
        //given
        Item item = new Item("itemA",10000,10);

        Item savedItem = itemRepository.save(item);
        Long itemId = savedItem.getId();

        //when
        Item updateParam = new Item("item2",20000,20);
        itemRepository.update(itemId,updateParam);
        //then

        Item findItem = itemRepository.findById(itemId);
        assertThat(findItem.getItemName()).isEqualTo(updateParam.getItemName());
        assertThat(findItem.getPrice()).isEqualTo(updateParam.getPrice());
        assertThat(findItem.getQuantity()).isEqualTo(updateParam.getQuantity());
    }
}

(4) 상품 서비스 HTML

1. 부트스트랩 적용

  • 부트스트랩 다운 및 추가
  • 부트스트랩이란?
부트스트랩(Bootstrap)은 웹사이트를 쉽게 만들 수 있게 도와주는 HTML, CSS, JS 프레임워크이다.
하나의 CSS로 휴대폰, 태블릿, 데스크탑까지 다양한 기기에서 작동한다. 다양한 기능을 제공하여 사용자가
쉽게 웹사이트를 제작, 유지, 보수할 수 있도록 도와준다
  1. HTML, css 파일 추가
  • items.html - 상품목록 HTML
<!DOCTYPE HTML>
<html>
<head>
 <meta charset="utf-8">
 <link href="../css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container" style="max-width: 600px">
 <div class="py-5 text-center">
 <h2>상품 목록</h2>
 </div>
 <div class="row">
 <div class="col">
 <button class="btn btn-primary float-end"
 onclick="location.href='addForm.html'" type="button">상품
등록</button>
 </div>
 </div>
 <hr class="my-4">
 <div>
 <table class="table">
 <thead>
 <tr>
 <th>ID</th>
 <th>상품명</th>
 <th>가격</th>
 <th>수량</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td><a href="item.html">1</a></td>
 <td><a href="item.html">테스트 상품1</a></td>
 <td>10000</td>
 <td>10</td>
 </tr>
 <tr>
 <td><a href="item.html">2</a></td>
 <td><a href="item.html">테스트 상품2</a></td>
 <td>20000</td>
 <td>20</td>
 </tr>
 </tbody>
 </table>
 </div>
</div> <!-- /container -->
</body>
</html>
  • item.html - 상품상세 HTML
<!DOCTYPE HTML>
<html>
<head>
 <meta charset="utf-8">
 <link href="../css/bootstrap.min.css" rel="stylesheet">
 <style>
 .container {
 max-width: 560px;
 }
 </style>
</head>
<body>
<div class="container">
 <div class="py-5 text-center">
 <h2>상품 상세</h2>
 </div>
 <div>
 <label for="itemId">상품 ID</label>
 <input type="text" id="itemId" name="itemId" class="form-control"
value="1" readonly>
 </div>
 <div>
 <label for="itemName">상품명</label>
 <input type="text" id="itemName" name="itemName" class="form-control"
value="상품A" readonly>
 </div>
 <div>
 <label for="price">가격</label>
 <input type="text" id="price" name="price" class="form-control"
value="10000" readonly>
 </div>
 <div>
 <label for="quantity">수량</label>
 <input type="text" id="quantity" name="quantity" class="form-control"
value="10" readonly>
 </div>
 <hr class="my-4">
 <div class="row">
 <div class="col">
 <button class="w-100 btn btn-primary btn-lg"
onclick="location.href='editForm.html'" type="button">상품 수정</button>
 </div>
 <div class="col">
 <button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='items.html'" type="button">목록으로</button>
 </div>
 </div>
</div> <!-- /container -->
</body>
</html>
  • addForm.html - 상품 등록 폼 HTML
<!DOCTYPE HTML>
<html>
<head>
 <meta charset="utf-8">
 <link href="../css/bootstrap.min.css" rel="stylesheet">
 <style>
 .container {
 max-width: 560px;
 }
 </style>
</head>
<body>
<div class="container">
 <div class="py-5 text-center">
 <h2>상품 등록 폼</h2>
 </div>
 <h4 class="mb-3">상품 입력</h4>
 <form action="item.html" method="post">
 <div>
 <label for="itemName">상품명</label>
 <input type="text" id="itemName" name="itemName" class="formcontrol" placeholder="이름을 입력하세요">
 </div>
 <div>
 <label for="price">가격</label>
 <input type="text" id="price" name="price" class="form-control"
placeholder="가격을 입력하세요">
 </div>
 <div>
 <label for="quantity">수량</label>
 <input type="text" id="quantity" name="quantity" class="formcontrol" placeholder="수량을 입력하세요">
 </div>
 <hr class="my-4">
 <div class="row">
 <div class="col">
 <button class="w-100 btn btn-primary btn-lg" type="submit">상품
등록</button>
 </div>
 <div class="col">
 <button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='items.html'" type="button">취소</button>
 </div>
 </div>
 </form>
</div> <!-- /container -->
</body>
</html>
  • editForm.html
<!DOCTYPE HTML>
<html>
<head>
 <meta charset="utf-8">
 <link href="../css/bootstrap.min.css" rel="stylesheet">
 <style>
 .container {
 max-width: 560px;
 }
 </style>
</head>
<body>
<div class="container">
 <div class="py-5 text-center">
 <h2>상품 수정 폼</h2>
 </div>
 <form action="item.html" method="post">
 <div>
 <label for="id">상품 ID</label>
 <input type="text" id="id" name="id" class="form-control" value="1"
readonly>
 </div>
 <div>
 <label for="itemName">상품명</label>
 <input type="text" id="itemName" name="itemName" class="formcontrol" value="상품A">
 </div>
 <div>
 <label for="price">가격</label>
 <input type="text" id="price" name="price" class="form-control"
value="10000">
 </div>
 <div>
 <label for="quantity">수량</label>
 <input type="text" id="quantity" name="quantity" class="formcontrol" value="10">
 </div>
 <hr class="my-4">
 <div class="row">
 <div class="col">
 <button class="w-100 btn btn-primary btn-lg" type="submit">저장
</button>
 </div>
 <div class="col">
 <button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='item.html'" type="button">취소</button>
 </div>
 </div>
 </form>
</div> <!-- /container -->
</body>
</html>

(5) 상품 목록 - 타임리프

1. 컨트롤러 적용

  • BasicItemController.java
  • 로직
    -> itemRepository에서 모든 상품 조회 후 모델에 담음
    --> 뷰 템플릿 호출 (return "basic/items")
  • @RequiredArgsConstructor
    : final 이 붙은 멤버변수만 사용해서 생성자를 자동으로 만들어줌
    -> 생성자가 1개만 있을 시 스프링이 해당 생성자에 @Autowired로 의존 관계 주입해줌 (생성 가능)
  • 테스트용 데이터 추가
    : @PostConstruct : 해당 빈의 의존관계가 모두 주입되고 나면 초기화 용도로 호출됨

2. 뷰 템플릿 적용

  • items.html 정적 HTML을 뷰 템플릿(templates) 영역으로 복사
/resources/static/items.html 
->
/resources/templates/basic/items.html
  • /items.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
 <meta charset="utf-8">
 <link href="../css/bootstrap.min.css"
 th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
</head>
<body>
<div class="container" style="max-width: 600px">
 <div class="py-5 text-center">
 <h2>상품 목록</h2>
 </div>
 <div class="row">
 <div class="col">
 <button class="btn btn-primary float-end"
onclick="location.href='addForm.html'"
 th:onclick="|location.href='@{/basic/items/add}'|"
type="button">상품 등록</button>
 </div>
 </div>
 <hr class="my-4">
 <div>
 <table class="table">
 <thead>
 <tr>
 <th>ID</th>
 <th>상품명</th>
 <th>가격</th>
 <th>수량</th>
 </tr>
 </thead>
 <tbody>
 <tr th:each="item : ${items}">
 <td><a href="item.html" th:href="@{/basic/items/{itemId}
(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
 <td><a href="item.html" th:href="@{|/basic/items/${item.id}|}"
th:text="${item.itemName}">상품명</a></td>
 <td th:text="${item.price}">10000</td>
 <td th:text="${item.quantity}">10</td>
 </tr>
 </tbody>
 </table>
 </div>
</div> <!-- /container -->
</body>
</html>

3. 타임리프 간단히 알아보기

네츄럴 템플릿 : 순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있는 타임리프의 특징을 일컫는다.

  • 타임리프 사용 선언
<html xmlns:th="http://www.thymeleaf.org">
  • 속성변경 - th:href
th:href="@{/css/bootstrap.min.css}"
  • href="value1" 을 th:href="value2" 의 값으로 변경한다.
  • 타임리프 뷰 템플릿을 거치게 되면 원래 값을 th:xxx 값으로 변경한다.
    -> 만약 값이 없다면 새로 생성한다.
  • HTML을 그대로 볼 때는 href 속성이 사용된다.
    -> 뷰 템플릿을 거치면 th:href 의 값이 href 로 대체되면서 동적으로 변경할 수 있다.
  • 대부분의 HTML 속성을 th:xxx 로 변경할 수 있다.
  • 타임리프 핵심
  • th:xxx 가 붙은 부분은 서버사이드에서 렌더링 되고, 기존 것을 대체한다.
    -> th:xxx 이 없으면 기존 html의 xxx 속성이 그대로 사용된다.
  • HTML을 파일로 직접 열었을 때, th:xxx 가 있어도 웹 브라우저는 ht: 속성을 알지 못하므로 무시한다.
    -> HTML을 파일 보기를 유지하면서 템플릿 기능도 할 수 있다.
  • URL 링크 표현식 - @{...},
th:href="@{/css/bootstrap.min.css}"
  • @{...}
    : 타임리프는 URL 링크를 사용하는 경우 @{...} 를 사용한다. 이것을 URL 링크 표현식이라 한다.
    : URL 링크 표현식을 사용하면 서블릿 컨텍스트를 자동으로 포함한다
  • 속성 변경 - th:onclick
onclick="location.href='addForm.html'"
->
th:onclick="|location.href='@{/basic/items/add}'|"
  • 리터럴 대체 - |...|
  • 타임리프에서 문자와 표현식 등은 분리되어 있기 때문에 더해서 사용해야 한다.
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
  • 다음과 같이 리터럴 대체 문법을 사용하면, 더하기 없이 편리하게 사용할 수 있다.
<span th:text="|Welcome to our application, ${user.name}!|">
  • 적용
location.href='/basic/items/add'
-> 기본 적용
th:onclick="'location.href=' + '\'' + @{/basic/items/add} + '\''"
-> 리터럴 적용
th:onclick="|location.href='@{/basic/items/add}'|"
  • 반복 출력 - th:each
<tr th:each="item : ${items}">
  • 반복은 th:each 를 사용한다.
    -> 모델에 포함된 items 컬렉션 데이터가 item 변수에 하나씩 포함되고, 반복문 안에서 item 변수를 사용할 수 있다.
  • 컬렉션의 수 만큼 하위 테그를 포함해서 생성된다.
  • 변수 표현식 - ${...}
<td th:text="${item.price}">10000</td>
  • 모델에 포함된 값이나, 타임리프 변수로 선언한 값을 조회할 수 있다.
  • 프로퍼티 접근법을 사용한다. ( item.getPrice() )
  • 내용 변경 - th:text
<td th:text="${item.price}">10000</td>
  • 내용의 값을 th:text 의 값으로 변경한다.
    -> 여기서는 10000을 ${item.price} 의 값으로 변경한다
  • URL 링크 표현식2 - @{...},
th:href="@{/basic/items/{itemId}(itemId=${item.id})}"
  • URL 링크 표현식을 사용하면 경로를 템플릿처럼 편리하게 사용할 수 있다.
  • 경로 변수( {itemId} ) 뿐만 아니라 쿼리 파라미터도 생성한다.
예) 
th:href="@{/basic/items/{itemId}(itemId=${item.id}, query='test')}"
->
생성 링크: http://localhost:8080/basic/items/1?query=test 
  • URL 링크 간단히
 th:href="@{|/basic/items/${item.id}|}"
  • 리터럴 대체 문법을 활용해서 간단히 사용할 수도 있다

(6) 웹 페이지 만들기 - 상품 상세

  • BasicItemController에 추가 - 상품 상세
  • 상품상세 뷰
    정적 HTML을 뷰 템플릿(templates) 영역으로 복사하고 다음과 같이 수정
/resources/static/item.html 
->
/resources/templates/basic/item.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
 <meta charset="utf-8">
 <link href="../css/bootstrap.min.css"
 th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
 <style>
 .container {
 max-width: 560px;
 }
 </style>
</head>
<body>
<div class="container">
 <div class="py-5 text-center">
 <h2>상품 상세</h2>
 </div>
 <div>
 <label for="itemId">상품 ID</label>
 <input type="text" id="itemId" name="itemId" class="form-control"
value="1" th:value="${item.id}" readonly>
 </div>
 <div>
 <label for="itemName">상품명</label>
 <input type="text" id="itemName" name="itemName" class="form-control"
value="상품A" th:value="${item.itemName}" readonly>
 </div>
 <div>
 <label for="price">가격</label>
 <input type="text" id="price" name="price" class="form-control"
value="10000" th:value="${item.price}" readonly>
 </div>
 <div>
 <label for="quantity">수량</label>
 <input type="text" id="quantity" name="quantity" class="form-control"
value="10" th:value="${item.quantity}" readonly>
 </div>
 <hr class="my-4">
 <div class="row">
 <div class="col">
 <button class="w-100 btn btn-primary btn-lg"
onclick="location.href='editForm.html'"
 th:onclick="|location.href='@{/basic/items/{itemId}/
edit(itemId=${item.id})}'|" type="button">상품 수정</button>
 </div>
 <div class="col">
 <button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='items.html'"
 th:onclick="|location.href='@{/basic/items}'|"
 type="button">목록으로</button>
 </div>
 </div>
</div> <!-- /container -->
</body>
</html>
  • 속성 변경 - th:value
th:value="${item.id}"

-> 모델에 있는 item 정보를 획득하고 프로퍼티 접근법으로 출력한다. ( item.getId() )
-> value 속성을 th:value 속성으로 변경한다.

  • 상품수정 링크
th:onclick="|location.href='@{/basic/items/{itemId}/edit(itemId=${item.id})}'|"
  • 목록으로 링크
th:onclick="|location.href='@{/basic/items}'|"  

(7) 웹 페이지 만들기 - 상품 등록 폼

  • BasicItemController에 추가 - 상품 등록 폼
  • 상품등록 폼 뷰
    정적 HTML을 뷰 템플릿(templates) 영역으로 복사하고 다음과 같이 수정
/resources/static/addForm.html 
->
/resources/templates/basic/addForm.html
  • addForm.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
 <meta charset="utf-8">
 <link href="../css/bootstrap.min.css"
 th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
 <style>
 .container {
 max-width: 560px;
 }
 </style>
</head>
<body>
<div class="container">
 <div class="py-5 text-center">
 <h2>상품 등록 폼</h2>
 </div>
 <h4 class="mb-3">상품 입력</h4>
 <form action="item.html" th:action method="post">
 <div>
 <label for="itemName">상품명</label>
 <input type="text" id="itemName" name="itemName" class="formcontrol" placeholder="이름을 입력하세요">
 </div>
 <div>
 <label for="price">가격</label>
 <input type="text" id="price" name="price" class="form-control"
placeholder="가격을 입력하세요">
 </div>
 <div>
 <label for="quantity">수량</label>
 <input type="text" id="quantity" name="quantity" class="formcontrol" placeholder="수량을 입력하세요">
 </div>
 <hr class="my-4">
 <div class="row">
 <div class="col">
 <button class="w-100 btn btn-primary btn-lg" type="submit">상품
등록</button>
 </div>
 <div class="col">
 <button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='items.html'"
 th:onclick="|location.href='@{/basic/items}'|"
 type="button">취소</button>
 </div>
 </div>
 </form>
</div> <!-- /container -->
</body>
</html>
  • 속성 변경 - th:action
th:action

-> HTML form에서 action 에 값이 없으면 현재 URL에 데이터를 전송한다.
-> 상품 등록 폼의 URL과 실제 상품 등록을 처리하는 URL을 똑같이 맞추고 HTTP 메서드로 두 기능을 구분한다.

상품 등록 폼: GET /basic/items/add
상품 등록 처리: POST /basic/items/add
  • 취소
    -> 취소시 상품 목록으로 이동한다.
th:onclick="|location.href='@{/basic/items}'|"

(8) 웹 페이지 만들기 - 상품 등록 처리 - @ModelAttribute

1. POST - HTML Form

  • content-type: application/x-www-form-urlencoded
  • 메시지 바디에 쿼리 파리미터 형식으로 전달
itemName=itemA&price=10000&quantity=10

-> 예) 회원 가입, 상품 주문, HTML Form 사용

  • 요청 파라미터 형식을 처리해야 하므로 @RequestParam 을 사용하자

2. 상품 등록 처리

  • addItemV1 - @RequestParam
  1. @RequestParam String itemName : itemName 요청 파라미터 데이터를 해당 변수에 받는다.
  2. Item 객체를 생성하고 itemRepository 를 통해서 저장한다.
  3. 저장된 item 을 모델에 담아서 뷰에 전달한다.
    ** 중요: 여기서는 상품 상세에서 사용한 item.html 뷰 템플릿을 그대로 재활용한다
  • addItemV2 - @ModelAttribute

  • @ModelAttribute - 요청 파라미터 처리
    : @ModelAttribute 는 Item 객체를 생성하고, 요청 파라미터의 값을 프로퍼티 접근법(setXxx)으로 입력해준다.
  • @ModelAttribute - Model 추가
    : 모델(Model)에 @ModelAttribute 로 지정한 객체를 자동으로 넣어준다.
    -> model.addAttribute("item", item) 가 주석처리
    되어 있어도 잘 동작하는 것을 확인할 수 있다.
  • 모델에 데이터를 담을 때는 이름이 필요하다.
    -> 이름은 @ModelAttribute 에 지정한 name(value) 속성을 사용한다.
    --> 만약 다음과 같이 @ModelAttribute 의 이름을 다르게 지정하면 다른 이름으로 모델에
    포함된다.
@ModelAttribute("hello") Item item 이름을 hello 로 지정
model.addAttribute("hello", item); 모델에 hello 이름으로 저장  
  • addItemV3 - ModelAttribute 이름 생략
  • @ModelAttribute 의 이름을 생략할 수 있다.
  • @ModelAttribute 의 이름을 생략하면 모델에 저장될 때 클래스명을 사용한다. 이때 클래스의 첫글자만 소문자로 변경해서 등록한다.
    -> 예) @ModelAttribute 클래스명 모델에 자동 추가되는 이름
Item -> item
HelloWorld -> helloWorld
  • addItemV4 - ModelAttribute 전체 생략
    • @ModelAttribute 자체도 생략가능하다. 대상 객체는 모델에 자동 등록된다.

(9) 웹 페이지 만들기 - 상품 수정

1. 상품 수정 - BasicItemController에 추가

2. 상품 수정 폼 뷰
정적 HTML을 뷰 템플릿(templates) 영역으로 복사하고 다음과 같이 수정

/resources/static/editForm.html 
->
/resources/templates/basic/editForm.html
  • editForm.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
 <meta charset="utf-8">
 <link href="../css/bootstrap.min.css"
 th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
 <style>
 .container {
 max-width: 560px;
 }
 </style>
</head>
<body>
<div class="container">
 <div class="py-5 text-center">
 <h2>상품 수정 폼</h2>
 </div>
 <form action="item.html" th:action method="post">
 <div>
 <label for="id">상품 ID</label>
 <input type="text" id="id" name="id" class="form-control" value="1"
th:value="${item.id}" readonly>
 </div>
 <div>
 <label for="itemName">상품명</label>
 <input type="text" id="itemName" name="itemName" class="formcontrol" value="상품A" th:value="${item.itemName}">
 </div>
 <div>
 <label for="price">가격</label>
 <input type="text" id="price" name="price" class="form-control"
th:value="${item.price}">
 </div>
 <div>
 <label for="quantity">수량</label>
 <input type="text" id="quantity" name="quantity" class="formcontrol" th:value="${item.quantity}">
 </div>
 <hr class="my-4">
 <div class="row">
 <div class="col">
 <button class="w-100 btn btn-primary btn-lg" type="submit">저장
</button>
 </div>
 <div class="col">
 <button class="w-100 btn btn-secondary btn-lg"
onclick="location.href='item.html'"
 th:onclick="|location.href='@{/basic/items/{itemId}(itemId=${item.id})}'|"
 type="button">취소</button>
 </div>
 </div>
 </form>
</div> <!-- /container -->
</body>
</html>

3. 상품 수정 개발

  • 상품 수정은 상품 등록과 전체 프로세스가 유사하다.
GET /items/{itemId}/edit : 상품 수정 폼
POST /items/{itemId}/edit : 상품 수정 처리
  • 리다이렉트
    : 상품 수정은 마지막에 뷰 템플릿을 호출하는 대신 상품 상세 화면으로 이동하도록 리다이렉트를 호출한다.
    : 스프링은 redirect:/... 으로 편리하게 리다이렉트를 지원한다.
redirect:/basic/items/{itemId}"
-> redirect:/basic/items/{itemId} {itemId} 는 @PathVariable Long itemId 의 값을 그대로 사용한다.   

참고

  • HTML Form 전송은 PUT, PATCH를 지원하지 않는다. GET, POST만 사용할 수 있다.
  • PUT, PATCH는 HTTP API 전송시에 사용
  • 스프링에서 HTTP POST로 Form 요청할 때 히든 필드를 통해서 PUT, PATCH 매핑을 사용하는 방법이 있지만, HTTP 요청상 POST 요청이다.

(10) 웹 페이지 만들기 - PRG Post/Redirect/Get

0. 이전 상품 등록 처리의 문제점

  • 새로고침 버튼 클릭 시 중복 등록 발생!!
    -> 이유는?
  • 전체 흐름

  • POST 등록 후 새로 고침
  1. 웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송한다.
  2. 상품 등록 폼에서 데이터를 입력하고 저장을 선택하면 POST /add + 상품 데이터를 서버로 전송한다.
  3. 이 상태에서 새로 고침을 또 선택하면 마지막에 전송한 POST /add + 상품 데이터를 서버로 다시 전송하게 된다.
    -> 내용은 같고, ID만 다른 상품 데이터가 계속 쌓이게 된다.

1. POST, Redirect GET

  • PRG 구조

  1. 웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송한다.
  2. 새로 고침 문제를 해결하기 위해 상품 저장 후에 상품 상세 화면으로 리다이렉트를 호출해준다.
  3. 웹 브라우저는 리다이렉트의 영향으로 상품 저장 후에 실제 상품 상세 화면으로 다시 이동한다.
    -> 따라서 마지막에 호출한 내용이 상품 상세 화면인 GET /items/{id} 가 되는 것이다.
  4. 이후 새로고침을 해도 상품 상세 화면으로 이동하게 되므로 새로 고침 문제를 해결할 수 있다

2. 소스 변경 - BasicItemController에 추가

"redirect:/basic/items/" + item.getId() redirect에서 +item.getId()

위 처럼 URL에 변수를 더해서 사용하는 것은 URL 인코딩이 안되기 때문에 위험하다.
-> 다음에 설명하는 RedirectAttributes 를 사용하자.


(11) 웹 페이지 만들기 - RedirectAttributes

1. RedirectAttributes 적용


좋은 웹페이지 즐겨찾기