-
[스프링 MVC 1] 7. 스프링 MVC - 웹 페이지 만들기INFLEARN/스프링 MVC 1편 2021. 11. 5. 17:45
7. 스프링 MVC - 웹 페이지 만들기
[강의 정리] 김영한님 :: 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
1. 프로젝트 생성
# 프로젝트 생성
- Packaging은 Jar로
# Welcome 페이지
- 스프링 부트에서 Jar를 사용하기 위해 /resources/static 위치에 index.html파일을 두었음
- 스프링 부트가 Welcome 페이지로 처리해줌
2. 요구사항 분석
# 서비스 제공 흐름
- 상품 목록, 상세, 등록, 수정
3. 상품 도메인 개발
# Item - 상품 객체
- 핵심 도메인 같은 경우 @Data 사용하는거 위험할 수 있어서 @Getter, @Setter 등으로 분리하는게 좋음
# ItemRepository - 상품 저장소
- public Item save(Item item)
- public Item findById(Long id)
- public List<Item> findAll()
- public void update(Long itemId, Item updateParam)
# ItemRepositoryTest - 상품 저장소 테스트
- test 끝날때마다 실행 (초기화)
- junit 5부터는 public 필요없음
- void save()
- void findAll()
- void updateItem()
4. 상품 서비스 HTML
# 부트스트랩 (css)
- /resources/static/css/bootstrap.min.css 추가
- /resources/static에 넣어 두었으므로, 스프링 부트가 정적 리소스를 제공
- 단, 리소스가 공개되는 해당 폴더에 HTML을 넣어두면 실제 서비스에도 공개 되므로 주의 필요
# HTML 파일
- 상품 목록 : /resources/static/html/items.html
- 상품 상세 : /resources/static/html/item.html
- 상품 등록 폼 : /resources/static/html/addForm.html
- 상품 수정 폼 : /resources/static/html/editForm.html
5. 상품 목록 - 타임리프
# BasicItemController
- 컨트롤러 로직은 itemRepository에서 모든 상품을 조회한 다음에 모델에 담고 뷰 템플릿을 호출
- @RequiredArgsConstructor
- final이 붙은 멤버변수만 사용해서 생성자를 자동으로 만들어준다.
- public BasicItemController(ItemRepository itemRepository) { this.itemRepository = itemRepository; }
- 이렇게 생성자만 딱 1개만 있으면, 스프링이 해당 생성자에 @Autowired로 의존관계 주입
- 따라서 final 키워드를 빼면 안됨 (의존관계 주입 안됨)
- 테스트용 데이터 추가
- @PostConstruct : 해당 빈의 의존관계가 모두 주입되고 나면 초기화 용도로 호출
# [타임리프] 사용선언
- th로 사용
- <html xmlns:th="http://www.thymeleaf.org">
# [타임리프] 속성 변경 - th:href
- hreft="value1" -> th:href="value2"의 값으로 변경
- 타임리프 뷰 템플릿을 거치게 되면 원래 값을 th:xxx 값으로 변경
- HTML을 그대로 보면 href 속성 사용, 뷰 템플릿 거치면 th:href의 값이 href로 대체되면서 동적으로 변경
- <link href="../css/bootstrap.min.css"
th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
# [타임리프] 핵심
- th:xxx가 붙은 부분은 서버사이드에서 렌더링 되고 기존 것을 대체
- th:xxx가 없으면 html의 xxx속성 그대로 사용
- 그냥 html 파일 열었을 때와 뷰 템플릿일 때 다름
# [타임리프] URL 링크 표현식 - @{...}
- th:href="@{/css/bootstrap.min.css}
- @{...} : 타임리프의 URL 링크 표현식
# [타임리프] 속성 변경 - th:onclick
- onclick="location.href='items.html'"
th:onclick="|location.href='@{/basic/items}'|"
# [타임리프] 리터럴 대체 - |...|
- 타임리프에서 문자와 표현식 등은 분리되어 있기 때문에 더하기를 사용해야하는데, 리터럴 대체 문법을 사용하면 더하기 없이 사용 가능
- 결과 : location.href='/basic/items/add'
- 더하기 사용 : th:onclick="'location.href=' + '\'' + @{/basic/items/add} + '\''"
- 리터럴 대체 사용 : th:onclick="|location.href='@{/basic/items/add}'|"
# [타임리프] 반복 출력 - th:each
- 반복은 th:each를 사용하여 모델에 포함된 컬렉션 데이터(items)를 변수(item)에 하나씩 넣고 사용가능
- 컬렉션 수 만큼 <tr> .. </tr>이 하위 태그를 포함하여 생성됨
- <tr th:each="item : ${items}">
# [타임리프] 변수 표현식 - ${...}
- <td th:text="${item.price}">10000</td>
- $ 모델에 포함된 값이나, 타임리프 변수로 선언한 값을 조회할 수 있음
- 프로퍼티 접근법을 사용 (item.getPrice())
# [타임리프] 내용 변경 - th:text
- <td th:text="${item.price}">10000</td>
- 내용의 값(10000)을 th:text의 값(${item.price})으로 변경
# [타임리프] URL 링크 표현식2 - @{...}
- 상품 ID를 선택하는 링크
- th:href="@{/basic/items/{itemId}(itemId=${item.id})}"
- 경로 변수 {itemId} 뿐만 아니라, 쿼리 파라미터도 생성가능
- th:href="@{/basic/items/{itemId}(itemId=${item.id}, query='test')}"
- 생성링크 : http://localhost:8080/basic/items/1?query=test
# [타임리프] URL 링크 간단히
- 리터럴 대체 문법을 활용하여 URL 링크 걸 수 있음
- th:href="@{|/basic/items/${item.id}|}"
+) 참고
- 타임리프는 네츄럴 템플릿(natural templates) 이기 때문에, 순수 HTML 파일을 웹 브라우저에서 열어서 확인하고 그대로 유지하면서, 뷰 템플릿도 사용할 수 있음
6. 상품 상세
# 상품 상세 컨트롤러
- @PathVariable로 넘어온 상품 ID로 조회하고 모델에 담고, 뷰 템플릿 호출
# 상품 상세 뷰
- /resources/static/item.html => /resources/static/templates/basic/item.html
- 속성 변경, 상품수정 링크 변경, 목록으로 링크 변경
7. 상품 등록 폼
# 상품 등록 폼 컨트롤러 (GET)
- 단순 뷰 호출
# 상품 등록 폼 뷰
- /resources/static/addForm.html => /resources/static/templates/basic/addForm.html
# 상품 등록 처리 컨트롤러 (POST)
- 상품 등록 폼의 URL과 실제 상품 등록을 처리하는 URL을 똑같이 맞추고, HTTP 메서드로 두 기능을 구분
- @GetMapping("/add")
- @PostMapping("/add")
- @GetMapping("/add")
# 조합
- 중복 제거 가능 (클래스 레벨에 공통적인 부분 뺌)
8. 상품 등록 처리 - @ModelAttribute
# POST - HTML Form
- content-type: application/x-www-form-urlencoded
- 메시지 바디에 쿼리 파라미터 형식으로 전달 : itemName=itemA&price=10000&quantity=10
# addItemV1 - @RequestParam
- 요청 파라미터 형식을 처리해야 하므로 @RequestParam을 사용
- itemName, price, quantity 요청 파라미터 데이터를 해당 변수들에 받는다
- Item 객체를 생성하고 set을 한 후, itemRepository를 통해서 저장
- 저장된 item을 모델에 담아서 뷰에 전달
# addItemV2 - 상품 등록 처리 - @ModelAttribute
- 기존의 @RequestParam으로 변수를 하나씩 받아서 Item 생성하는 것이 불편 @ModelAttribute 사용해서 한 번에 처리 가능
- @ModelAttribute - 요청 파라미터 처리
- @ModelAttribute는 Item 객체를 생성하고, 요청 파라미터의 값을 프로퍼티 접근법(setXxx)로 입력
- @ModelAttribute - Model 추가
- Model에 @ModelAttribute로 지정한 객체를 자동으로 넣어줌
- model.addAttribute(..) 부분을 생략해도 동작함
# addItemV3 - 상품 등록 처리 - ModelAttribute 이름 생략
- @ModelAttribute name 생략 가능
- 생략시 model에 저장되는 name은 클래스명 첫 글자만 소문자로 변경해서 자동으로 등록 (ex. Item -> item)
# addItemV4 - 상품 등록 처리 - ModelAttribute 전체 생략
- @ModelAttribute 자체도 생략 가능
- 대상 객체는 모델에 자동 등록
9. 상품 수정
# 상품 수정 컨트롤러
- 수정에 필요한 정보를 조회하고, 수정용 폼 뷰를 호출
# 상품 수정 폼 뷰
- /resources/static/editForm.html => /resources/static/templates/basic/editForm.html
# 상품 수정 개발
- 상품 등록 폼의 URL과 실제 상품 등록을 처리하는 URL을 똑같이 맞추고, HTTP 메서드로 두 기능을 구분
- 상품 수정 폼 : GET /items/{itemId}/edit
- 상품 수정 처리 : POST /items/{itemId}/edit
- 리다이렉트
- 마지막에 뷰 템플릿을 호출하는 대신에 상품 상세 화면으로 이동
- 스프링은 redirect:/... 를 지원
- 컨트롤러에 매핑된 @PathVariable의 값은 redirect에서도 사용할 수 있음
- 로케이션이 실제로 이동 되는 것이며, URL 변경 (컨트롤러부터 다시 호출)
+) 참고
- HTML Form 전송은 PUT, PATCH를 지원하지 않음
- GET, POST만 사용할 수 있음
- PUT, PATCH는 HTTP API 전송시 사용
10. PRG Post/Redirect/Get
# 상품 중복 등록
- 지금까지 만들어놓은 상품 등록 처리 컨트롤러에서는 상품 등록을 완료하고 웹 브라우저의 새로그침 버튼을 클릭하면 상품이 계속해서 중복 등록 됨 (id값이 계속 올라가고, 같은 내용 상품 등록)
- 상품 저장을 하고 나면 뷰 템플릿을 호출하게 되는데 이 때 마지막으로 남은 URL을 계속 호출함
# POST 등록 후 새로고침
- 웹 브라우제의 새로 고침은 마지막에 서버에 전송한 데이터 재전송
- 상품 등록 폼에서 데이터를 입력하고 저장을 선택하면 POST /add + 상품 데이터를 서버에 전송하게 되고, 이 상태에서 새로고침하면 또 똑같이 POST /add + 상품 데이터를 서버에 전송하게 됨
- 새로고침 : POST /add
# POST, Redirect GET
- 상품 저장 후에 뷰 템플릿으로 이동하는 것이 아니라, 상품 상세 화면으로 리다이렉트 호출
- 즉, POST 후 실제 상품 상세 화면으로 이동하기 때문에,
- 새로고침을 하게 되면 가장 마지막에 호출했던 상품 상세 화면 GET /items/{id}가 호출됨
- 새로고침 : GET /items/{id}
- 이와 같은 문제 해결 방식을 PRG Post/Redirect/Get 이라 함
- 단, 아래와 같이 URL 변수를 더해서 사용하면 URL 인코딩이 되지 않으므로 RedirectAttributes를 사용해야 함
11. RedirectAttributes
# RedirectAttributes
- 리다이렉트 할 때 간단히 status = true를 추가하여, 뷰 템플릿에서 값이 있으면 메시지 출력
- RedirectAttributes를 사용하면 URL 인코딩, pathVariable, 쿼리 파라미터 처리 가능
- pathVariable 바인딩 : {itemId}
- 나머지는 쿼리 파라미터로 처리 : ?status=true
- http://localhost:8080/basic/items/3?status=true
# 저장 후 메시지 노출 (뷰 템플릿에 메시지 추가)
- <h2 th:if="${param.status}" th:text="'저장 완료'"></h2>
- th:if : 해당 조건이 참이면 실행 (status가 true 일 때)
- ${param.status} : param은 타임리프에서 쿼리 파라미터를 편리하게 조회할 수 있게 지원해주는 기능 (원래는 컨트롤러에서 모델에 직접 담고 값을 꺼내야함)
'INFLEARN > 스프링 MVC 1편' 카테고리의 다른 글
[스프링 MVC 1] 6. 스프링 MVC - 기본 기능 (0) 2021.11.05 [스프링 MVC 1] 5. 스프링 MVC - 구조 이해 (0) 2021.10.27 [스프링 MVC 1] 4. MVC 프레임워크 만들기 (0) 2021.10.23 [스프링 MVC 1] 3. 서블릿, JSP, MVC 패턴 (0) 2021.10.18 [스프링 MVC 1] 2. 서블릿 (0) 2021.10.16