프로젝트 생성
- 스프링 MVC의 웹 관련 기능을 이해하기 위해 프로젝트를 생성해보자.
- 스프링 이니셜라이저를 통해 프로젝트를 생성하자.
- 프로젝트 선택
- Project
- Language
- Spring Boot
- Project Metadata
- Group
- Artifact
- Name
- Package name
- Packaging
- Java
- Dependencies
- Spring Web
- Thymeleaf
- Lombok
Welcome 페이지 추가
src/main/resources/static/index.html
요구사항 분석
- 간단한 상품 관리 서비스를 만들어보자.
- 상품 도메인 모델
- 상품 관리 기능
상품 도메인 개발
상품 객체
상품 저장소
상품 저장소 테스트
- Ctrl + Shift + T를 통해서 테스트를 생성하자.
상품 서비스 HTML
부트스트랩
https://getbootstrap.com/docs/5.0/getting-started/download/
로 이동- Compiled CSS and JS 영역의 Download 버튼을 선택해서 파일 다운로드
- 압축 해제
bootstrap.min.css
를 복사해서 src/main/resources/static/css
경로에 추가하기
상품 목록
src/main/resources/static/html/items.html
상품 목록
src/main/resources/static/html/items.html
상품 상세
src/main/resources/static/html/item.html
상품 등록 폼
src/main/resources/static/html/addForm.html
상품 수정 폼
src/main/resources/static/html/editForm.html
상품 목록 - 타임리프
컨트롤러
- 컨트롤러 로직은 itemRepository에서 모든 상품을 조회한 다음에 모델에 담는다. 그리고 뷰 템플릿을 호출한다
- @RequiredArgsConstructor를 통해서 final 이 붙은 멤버변수만 사용해서 생성자를 자동으로 만들어준다.
- @PostConstruct를 통해서 테스트용 데이터를 추가한다.
- @PostConstruct는 해당 빈의 의존관계가 모두 주입되고 나면 호출된다.
타임리프 적용
src/main//resources/static/html/items.html
를 복사해서 src/main//resources/templates/basic/items.html
로 추가한다.- 그런 다음에 items.html을 아래와 같이 수정한다.
타임리프 간단히 알아보기
- html 태그에
xmlns:th="http://www.thymeleaf.org"
를 추가하면 해당 페이지에는 타임리프가 적용된다. - 타임리프 동작 방식
- 해당 html 파일에 타임리프가 적용됬다면 th:xxx로 속성을 사용할 수 있다.
- th:xxx는 랜더링 시에 xxx라는 속성으로 변환된다.
- 만약 해당 html 파일 내부에 xxx와 th:xxx가 있다면 타임리프가 더 상위로 적용되서 최종적으로는 th:xxx가 적용된다.
- th:xxx는 서버 사이드에서 랜더링된다.
- 참고로 th:xxx는 정규 html의 속성이 아니기 때문에 html 파일을 직접 열어서 확인해보면 th:xxx 속성은 동작하지 않는 것을 학인할 수 있다.
- URL 링크 표현식
@{...}
처럼 사용한다.- URL 링크 표현식을 사용하면 서블릿 컨텍스트를 자동으로 포함한다.
- 경로 변수를 사용할 수 있다.
th:href="@{/basic/items/{itemId}(itemId=${item.id})}"
- URL 경로에서 경로 변수를 사용할 위치에
{}
로 감싼 변수명을 명시한다. - 그리고 경로 마지막에
()
를 명시하고 그 안에 속성명=값
처럼 경로 변수의 값을 정의한다.- 경로 변수는 여러 개 사용할 수 있다.
- 각 경로 변수는 쉼표로 구분한다.
- 경로 변수를 간략하게 처리할 수도 있다.
th:href="@{|/basic/items/${item.id}|}"
- 리터럴 대체
|...|
처럼 사용한다.- 타임리프에서 문자와 표현식 등은 분리되어 있기 때문에 더해서 사용해야 한다.
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
- 다음과 같이 리터럴 대체 문법을 사용하면, 더하기 없이 편리하게 사용할 수 있다.
<span th:text="|Welcome to our application, ${user.name}!|">
- 반복문
th:each="변수명 : ${컬렉션명}"
처럼 사용한다.- 컬렉션명에는 말 그대로 컬렉션의 이름이 들어간다.
- 컨트롤러 단에서 모델에 추가한 이름 그대로 적용된다.
- 변수명은 말 그대로 변수명이다.
- 일반적인 자바의 for-each를 생각하면 된다.
변수명.속성명
으로 값을 꺼내서 쓸 수 있다.
- 하위 태그도 함께 반복된다.
- 예시 : tr에 th:each 사용 시 tr과 tr 아래의 td도 함께 반복
- 변수 표현식
${...}
처럼 사용한다.- 모델에 포함된 값이나, 타임리프 변수로 선언한 값을 조회할 수 있다.
- 프로퍼티 접근법을 사용한다.
- 내용 변경
th:text=${...}
처럼 사용한다.- 기존 html 태그의 값을 덮어씌운다.
- 만약
<td th:text="${item.price}">10000</td>
이 있을 때 item.price의 값이 0이라면 최종 출력은 0이 된다.
- 조건문
th:if="조건문"
처럼 사용한다.- th:if 내부의 조건문의 결과가 참일 때만 해당 영역이 랜더링된다.
상품 상세
컨트롤러
- 스프링 부트 3.2 이상이면 아래 컨트롤러 메소드에서 오류가 발생할 것이다.
- 스프링 부트 3.2부터 자바 컴파일러에
-parameters
옵션을 넣어주어야 애노테이션의 이름을 생략할 수 있다.
타임리프 적용
src/main//resources/static/html/item.html
를 복사해서 src/main//resources/templates/basic/item.html
로 추가한다.- 그런 다음에 item.html을 아래와 같이 수정한다.
상품 등록 폼
컨트롤러
타임리프 적용
src/main//resources/static/html/addForm.html
를 복사해서 src/main//resources/templates/basic/addForm.html
로 추가한다.- 그런 다음에 addForm.html을 아래와 같이 수정한다.
상품 등록 처리 - @ModelAttribute
- 상품 등록 폼에서 전달된 데이터로 실제 상품을 등록해보자.
- 데이터는 상품 등록 폼에서 POST 방식으로 서버에 데이터를 전달한다.
POST localhost:8080/basic/items/add
content-type: application/x-www-form-urlencoded
컨트롤러 v1
- 테스트 시 실제로 상품이 잘 저장되는 것이 확인된다.
- 다만 @RequestParam으로 변수를 하나하나 받아서 Item을 생성하는 과정이 불편하다.
컨트롤러 v2
- 이번에는 addItemV1을 주석 처리한 후에 아래 코드를 적용해보자.
- @ModelAttribute을 통해서 전달된 데이터가 자동으로 Item에 대입되도록 수정했다.
- @ModelAttribute를 사용했기 때문에 자동으로 model에 “item”이라고 추가된다.
컨트롤러 v3
- 이번에는 addItemV2을 주석 처리한 후에 아래 코드를 적용해보자.
- @ModelAttribute에서 속성명을 빼버렸고, 또한 파라미터에서 Model 자체를 빼버렸다.
- 그래도 잘 동작하면서 모델에 item으로 등록된 것을 확인할 수 있다.
- 핸들러 어댑터쪽에서 자동으로 모델에 클래스명으로 Item을 등록했다.
- 기본은 클래스명으로 등록이지만 대신에 첫글자는 소문자로 변환해서 등록한다.
컨트롤러 v4
- 이번에는 addItemV3을 주석 처리한 후에 아래 코드를 적용해보자.
- @ModelAttribute 자체를 생략해버렸지만 핸들러 어댑터가 알아서 잘 처리해준다.
상품 수정
컨트롤러 (GET)
타임리프 적용
src/main//resources/static/html/editForm.html
를 복사해서 src/main//resources/templates/basic/editForm.html
로 추가한다.- 그런 다음에 editForm.html을 아래와 같이 수정한다.
컨트롤러 (POST)
- 상품 정보를 수정한 뒤에 해당 상품의 상세 페이지로 리다이렉트하게 했다.
- 상품 수정 페이지로 이동하기 위한 경로 변수는 리다이렉트할 때도 사용할 수 있다.
PRG (Post/Redirect/Get)
- 사실 지금까지 개발한 상품 등록 처리 컨트롤러는 심각한 분제가 있다.
- 등록한 후에 새로고침을 진행하면 계속 상품이 추가로 등록된다.
- 이는 상품 등록 후 보여지는 페이지가 POST로 상품을 등록하면서 진입한 페이지에서 새로고침하기 때문에 발생한 문제다.
- 이를 해결 하기 위해서는 상품을 등록하고 난 다음에 다른 페이지로 리다이렉트 시키면 된다.
- 리다이렉트하게 되면 리다이렉트 대상 페이지를 GET을 접근하게 되면서 해당 페이지에서 새로고침하게 되도 조회만 하기 때문에 문제가 발생하지 않는다.
- 이처럼 상품을 등록(POST) 후 리다이렉트(REDIRECT)시켜서 조회(GET) 페이지로 보내는 방식을 Post/Redirect/Get, 줄여서 PRG라고 한다.
컨트롤러 v5
- addItemV4을 주석 처리한 후에 아래 코드를 적용해보자.
RedirectAttributes
- PRG 방식을 통해 상품을 저장하고 상품 상세 화면이로 리다이렉트를 시켰다.
- 이는 개발적인 관점에서는 맞다.
- 다만 사용자 입장에서는 저장이 잘 된 것인지 아닌지 확신이 들지 않는다.
- 이번에는 이런 부가적인 사항도 만족시켜보자.
컨트롤러 v6
- addItemV5을 주석 처리한 후에 아래 코드를 적용해보자.
- RedirectAttributes라는 것이 추가되었다.
- RedirectAttributes는 리다이렉트될 때 전달할 값을 정의할 수 있게 해준다.
- 또한 RedirectAttributes를 사용하면 정의한 속성을 리다이렉트할 때 경로 변수로 사용할 수 있다.
- 추가적으로 URL 인코딩도 해준다.
- itemId를 통해 해당 상품의 고유번호를 찾아냈다.
- status를 통해 해당 상품이 잘 저장됬다는 것을 명시했다.
뷰 템플릿 메시지 추가
- 이번에는 다시
src/main//resources/templates/basic/item.html
에 가서 적당한 위치에 해당 내용을 추가해주자.
- 서버를 다시 실행해서 상품을 등록해보면 해당 메시지가 잘 보이는 것을 알 수 있다.
출처