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로 휴대폰, 태블릿, 데스크탑까지 다양한 기기에서 작동한다. 다양한 기능을 제공하여 사용자가
쉽게 웹사이트를 제작, 유지, 보수할 수 있도록 도와준다
- 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
- @RequestParam String itemName : itemName 요청 파라미터 데이터를 해당 변수에 받는다.
- Item 객체를 생성하고 itemRepository 를 통해서 저장한다.
- 저장된 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 등록 후 새로 고침
- 웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송한다.
- 상품 등록 폼에서 데이터를 입력하고 저장을 선택하면 POST /add + 상품 데이터를 서버로 전송한다.
- 이 상태에서 새로 고침을 또 선택하면 마지막에 전송한 POST /add + 상품 데이터를 서버로 다시 전송하게 된다.
-> 내용은 같고, ID만 다른 상품 데이터가 계속 쌓이게 된다.
1. POST, Redirect GET
- PRG 구조
- 웹 브라우저의 새로 고침은 마지막에 서버에 전송한 데이터를 다시 전송한다.
- 새로 고침 문제를 해결하기 위해 상품 저장 후에 상품 상세 화면으로 리다이렉트를 호출해준다.
- 웹 브라우저는 리다이렉트의 영향으로 상품 저장 후에 실제 상품 상세 화면으로 다시 이동한다.
-> 따라서 마지막에 호출한 내용이 상품 상세 화면인 GET /items/{id} 가 되는 것이다.- 이후 새로고침을 해도 상품 상세 화면으로 이동하게 되므로 새로 고침 문제를 해결할 수 있다
2. 소스 변경 - BasicItemController에 추가
"redirect:/basic/items/" + item.getId() redirect에서 +item.getId()
위 처럼 URL에 변수를 더해서 사용하는 것은 URL 인코딩이 안되기 때문에 위험하다.
-> 다음에 설명하는 RedirectAttributes 를 사용하자.
(11) 웹 페이지 만들기 - RedirectAttributes
1. RedirectAttributes 적용
Author And Source
이 문제에 관하여(SPRING MVC6_웹페이지 만들기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@hadoyaji/SPRING-MVC6웹페이지-만들기저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)