-
[백견불야일타] 3장 Thymeleaf 학습하기SpringBoot/쇼핑몰 프로젝트 with JPA 2022. 8. 26. 01:41
- 미리 정의된 템플릿을 만들고 동적으로 HTML 페이지를 만들어서 클라이언트에 전달하는 방식
- 요청이 올 때마다 서버에서 새로운 HTML 페이지를 만들어 주기 때문에 서버 사이드 렌더링 방식이라고 함
- 서버 사이드 템플릿 엔진으로는 Thymeleaf, JSP, Freemarker, Groovy, Mustache 가 있음
thymeleafEx01.html
<!DOCTYPE html> <!-- thymeleaf 문법을 사용하기 위해서 추가 <--> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- ThymeleafExController의 model의 data라는 key 값에 담아준 값을 출력 <--> <p th:text="${data}">Hello Thymelaf!!</p> </body> </html>
th:text="${data}" - Thymeleaf 문법
ThymeleafExController.java
package com.shop.controller; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @Controller // 클라이언트 요청에 대해서 어떤 컨트롤러가 처리할지 매핑하는 어노테이션 // url에 "/thymeleaf" 경로로 오는 요청을 ThymeleafExController가 처리하도록 함 @RequestMapping(value = "/thymeleaf") public class ThymeleafExController { @GetMapping(value = "/ex01") public String thymeleafExample01(Model model) { // model 객체를 이용해 뷰에 전달한 데이터를 key, value 구조로 넣어줌 model.addAttribute("data", "타임리프 예제 입니다."); // templates 폴더를 기준으로 뷰의 위치와 이름(thymeleafEx01.html)을 반환 return "thymeleafEx/thymeleafEx01"; } }
- 이것이 Thymeleaf가 지향하는 'natural templates'
- 디자이너 또는 퍼블리셔는 자신이 작업한 내용을 html 파일로 바로 열어서 확인 가능하며, 개발자는 html 파일을 받아서 html 태그 안에 thymeleaf 문법을 추가하는 것만으로 동적으로 html 파일을 생성할 수 있음
- JSP는 html파일을 다시 JSP로 변경할 때 실수할 확률도 높고 많은 시간이 걸림
Spring Boot Devtools
- 애플리케이션 개발 시 유용한 기능들을 제공하는 모듈
- Automati Restart : classpath에 있는 파일이 변경될 때마다 애플리케이션을 자동으로 재시작해줌
- Live Reload: 정적 자원(html, css, js) 수정 시 새로 고침 없이 바로 적용
- Property Defaults: Thymeleaf는 기본적으로 성능을 향상시키기 위해서 캐싱 기능을 사용함. 하지만 개발하는 과정에서 캐싱 기능을 사용한다면 수정한 소스가 제대로 반영되지 않을 수 있기 때문에 cache의 기본값을 false로 설정할 수 있음
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency>
spring-boot-devtools 의존성 추가
2021.02 버전부터 compiler.automake.allow.when.app.running 이 없기 때문에 File - setting - Build, Execution, Deployment - compiler 를 열고 Build project automatically 체크 후 OK
application.properties
#Live Reload 기능 활성화 spring.devtools.livereload.enabled=true #Thymeleaf cache 사용 중지 spring.thymeleaf.cache = false
Thymeleaf 예제
th:text 예제
com.shop.dto.ItemDto
package com.shop.Dto; import lombok.Getter; import lombok.Setter; import java.time.LocalDateTime; @Getter @Setter public class ItemDto { private Long id; private String itemNm; private Integer price; private String itemDetail; private String sellStatCd; private LocalDateTime regTime; private LocalDateTime updateTime; }
데이터를 주고받을 때는 Entity 클래스 자체를 반환하면 안 되고 데이터 전달용 객체(DTO)를 생성해서 사용해야 함.
데이터베이스의 설계를 외부에 노출할 필요도 없으며, 요청과 응답 객체가 항상 엔티티와 같지 않기 때문.
com.shop.controller.ThymeleafExController.java
@GetMapping(value = "/ex02") public String thymeleafExample02(Model model){ ItemDto itemDto = new ItemDto(); itemDto.setItemDetail("상품 상세 설명"); itemDto.setItemNm("테스트 상품1"); itemDto.setPrice(10000); itemDto.setRegTime(LocalDateTime.now()); model.addAttribute("itemDto", itemDto); return "thymeleafEx/thymeleafEx02"; }
ItemDto 객체를 하나 생성 후 모델에 데이터를 담아서 뷰에 전달
resources/templates/thymeleafEx/thymeleafEx02.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>상품 데이터 출력 예제</h1> <div> 상품명 : <span th:text="${itemDto.itemNm}"></span> </div> <div> 상품상세설명 : <span th:text="${itemDto.itemDetail}"></span> </div> <div> 상품등록일 : <span th:text="${itemDto.regTime}"></span> </div> <div> 상품가격 : <span th:text="${itemDto.price}"></span> </div> </body> </html>
전달받은 itemDto 객체를 th:text를 이용하여 출력
th:each 예제
여러 개의 데이터를 가지고 있는 컬렉션 데이터를 화면에 출력
com.shop.controller.ThymeleafExController.java
@GetMapping(value = "/ex03") public String thymeleafExample03(Model model){ List<ItemDto> itemDtoList = new ArrayList<>(); // 화면에 출력할 10개의 itemDto 객체를 만들어서 itemDtoList에 넣어줌 for(int i=1; i<=10; i++){ ItemDto itemDto = new ItemDto(); itemDto.setItemDetail("상품 상세 설명" + i); itemDto.setItemNm("테스트 상품1" + i); itemDto.setPrice(1000*i); itemDto.setRegTime(LocalDateTime.now()); itemDtoList.add(itemDto); } //화면에서 출력할 itemDtoList를 model에 담아서 View에 전달 model.addAttribute("itemDtoList", itemDtoList); return "thymeleafEx/thymeleafEx02"; }
resources/templates/thymeleafEx/thymeleafEx03.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>상품 리스트 출력 예제</h1> <table border="1"> <thead> <tr> <td>순번</td> <td>상품명</td> <td>상품설명</td> <td>가격</td> <td>상품등록일</td> </tr> </thead> <tbody> <tr th:each="itemDto, status: ${itemDtoList}"> <td th:text="${status.index}"></td> <td th:text="${itemDto.itemNm}"></td> <td th:text="${itemDto.itemDetail}"></td> <td th:text="${itemDto.price}"></td> <td th:text="${itemDto.regTime}"></td> </tr> </tbody> </table> </body> </html>
- th:each를 이용하면 자바의 for문처럼 반복문 사용할 수 있음. 전달받은 itemDtoList에 있는 데이터를 하나씩 꺼내와서 itemDto에 담아줌. status에는 현재 반복에 대한 상태 데이터가 존재. 변수명은 status 대신 다른 것을 사용해도 됨
- <td th:text="${status.index}"></td> - 현재 순회하고 있는 데이터의 인덱스 출력
th:if, th:unless 예제
순번이 짝수이면 '짝수', 짝수가 아니면 '홀수' 를 출력( java에서 if else 조건 처리)
com.shop.controller.ThymeleafExController.java
@GetMapping(value = "/ex04") public String thymeleafExample04(Model model){ List<ItemDto> itemDtoList = new ArrayList<>(); // 화면에 출력할 10개의 itemDto 객체를 만들어서 itemDtoList에 넣어줌 for(int i=1; i<=10; i++){ ItemDto itemDto = new ItemDto(); itemDto.setItemDetail("상품 상세 설명" + i); itemDto.setItemNm("테스트 상품1" + i); itemDto.setPrice(1000*i); itemDto.setRegTime(LocalDateTime.now()); itemDtoList.add(itemDto); } //화면에서 출력할 itemDtoList를 model에 담아서 View에 전달 model.addAttribute("itemDtoList", itemDtoList); return "thymeleafEx/thymeleafEx04"; }
resources/templates/thymeleafEx/thymeleafEx03.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>상품 리스트 출력 예제</h1> <table border="1"> <thead> <tr> <td>순번</td> <td>상품명</td> <td>상품설명</td> <td>가격</td> <td>상품등록일</td> </tr> </thead> <tbody> <tr th:each="itemDto, status: ${itemDtoList}"> <td th:if="${status.even}" th:text="짝수"></td> <td th:unless="${status.even}" th:text="홀수"></td> <td th:text="${status.index}"></td> <td th:text="${itemDto.itemNm}"></td> <td th:text="${itemDto.itemDetail}"></td> <td th:text="${itemDto.price}"></td> <td th:text="${itemDto.regTime}"></td> </tr> </tbody> </table> </body> </html>
- <td th:if="${status.even}" th:text="짝수"></td> -> status에는 현재 반복에 대한 정보가 존재한다. 인덱스가 짝수일 경우 status.even은 true가 되고 순번에 '짝수' 출력
- <td th:unless="${status.even}" th:text="홀수"></td> -> 현재 인덱스가 짝수가 아닐 경우 '홀수' 출력
th:swtich, th:case 예제
여러개의 조건 처리
resources/templates/thymeleafEx/thymeleafEx04.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>상품 리스트 출력 예제</h1> <table border="1"> <thead> <tr> <td>순번</td> <td>상품명</td> <td>상품설명</td> <td>가격</td> <td>상품등록일</td> </tr> </thead> <tbody> <tr th:each="itemDto, status: ${itemDtoList}"> <td th:switch="${status.even}"> <span th:case=true>짝수</span> <span th:case=false>홀수</span> </td> <td th:text="${itemDto.itemNm}"></td> <td th:text="${itemDto.itemDetail}"></td> <td th:text="${itemDto.price}"></td> <td th:text="${itemDto.regTime}"></td> </tr> </tbody> </table> </body> </html>
th:href 예제
- 링크 처리 문법
- 'Absolute URL' - 이동할 서버의 URL을 입력 , 'http://' or 'https://' 로 시작
- 'Context-relative URL' - 가장 많이 사용되는 형식, 우리가 실행하는 애플리케이션의 서버 내부를 이동하는 방법
- 웹 애플리케이션 루트에 상대적인 URL을 입력, 상대경로는 URL의 프로토콜이나 호스트 이름을 지정하지 않는다.
com.shop.controller.ThymeleafExController.java
@GetMapping(value = "/ex05") public String thymeleafExample05(){ return "thymeleafEx/thymeleafEx05"; }
resources/templates/thymeleafEx/thymeleafEx05.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>Thymeleaf 링크처리 예제 페이지</h1> <div> <a th:href="@{/thymeleaf/ex01}">예제 1페이지 이동</a> </div> <div> <a th:href="@{https://www.thymeleaf.org/}">thymeleaf 공홈 이동</a> </div> </body> </html>
- <a th:href="@{/thymeleaf/ex01}">예제 1페이지 이동</a> -> "th:href=@{이동할 경로}" SpringBoot 에서는 애플리케이션의 루트가 "/"이다. 만약 애플리케이션의 루트가 "/shop" 으로 지정했다면 html파일에 생성되는 이동 경로는 "/shop/thymeleaf/ex01" 이다.
- <a th:href="@{https://www.thymeleaf.org/}">thymeleaf 공홈 이동</a> -> 절대 경로 입력
resources/templates/thymeleafEx/thymeleafEx05.html
<div> <a th:href="@{/thymeleaf/ex06(param1 = '파라미터 데이터1', param2 = '파라미터 데이터2')}">thymeleaf 파라미터 전달</a> </div>
- 해당 링크로 이동시 파라미터 값 전달해야 하는 경우 처리
- 전달할 매개변수를 입력한 결오 끝에 "(key=value)" 구조로 입력, 전달할 매개 변수 param1, param2
com.shop.controller.ThymeleafExController.java
@GetMapping(value = "/ex06") public String thymeleafExample06(String param1, String param2, Model model){ model.addAttribute("param1", param1); model.addAttribute("param2", param2); return "thymeleafEx/thymeleafEx06"; }
- 전달했던 매개 변수와 같은 이름의 String 변수 param1, param2를 파라미터로 설정하면 자동으로 데이터가 바인딩됨
- 매개 변수를 model에 담아서 View로 전달
resources/templates/thymeleafEx/thymeleafEx06.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>파라미터 전달 예제</h1> <div th:text="${param1}"></div> <div th:text="${param2}"></div> </body> </html>
- 전달받은 매개 변수 값을 출력
Thymeleaf 페이지 레이아웃
- 웹사이트의 header, footer, menu 등 공통적인 페이지 구성 요소들이 있는데 이런 영역들을 각각의 페이지마다 같은 소스코드를 넣는다면 변경이 일어날 때 마다 이를 포함하고 있는 모든 페이지를 수정해야 할 것이다. Thymeleaf 페이지 레이아웃 기능을 사용한다면 공통 요소 관리를 쉽게 할 수 있다.
pom.xml
<dependency> <groupId>nz.net.ultraq.thymeleaf</groupId> <artifactId>thymeleaf-layout-dialect</artifactId> <version>3.1.0</version> </dependency>
Thymeleaf Layout Dialect 의존성 추가
- 하나의 레이아웃을 여러 페이지에 똑같이 적용 가능
- 공통적으로 적용되는 레이아웃을 미리 만들어놓고 현재 작성 중인 페이지만 레이아웃에 끼워넣으면 됨
resource/templates/fragments/footer.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <div th:fragment="footer"> footer 영역입니다. </div> </html>
- 다른 페이지에 포함시킬 영역을 th:fragment로 선언. footer 영역을 fragment로 만듬
resource/templates/fragments/header.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <div th:fragment="header"> header 영역입니다. </div> </html>
- 다른 페이지에 포함시킬 영역을 th:fragment로 선언. header 영역을 fragment로 만듬
resource/templates/layouts/layout1.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> <head> <meta charset="UTF-8"> <title>Title</title> <th:block layout:fragment="script"></th:block> <th:block layout:fragment="css"></th:block> </head> <body> <div th:replace="fragments/header::header"></div> <div layout:fragment="content"> </div> <div th:replace="fragments/footer::footer"></div> </body> </html>
- xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> layout 기능 사용을 위해서 layout 네임스페이스 추가
- <div th:replace="fragments/header::header"></div> th:replace 속성은 해당 속성이 선언된 html 태그를 다른 html 파일로 치환하는 것으로 이해. fragments 폴더 아래의 header.html 파일의 "th:fragment=header" 영역을 가지고 옴
- <div layout:fragment="content"> layout에서 변경되는 영역을 fragment로 설정. 앞으로 쇼핑몰을 만들면서 만들 페이지는 이 영역에 들어감
- <div th:replace="fragments/footer::footer"></div> header 영역과 마찬가지로 fragments 폴더 아래의 footer.html 파일의 th:fragment="footer" 영역을 가지고 옴.
resources/templates/thymeleafEx/thymeleafEx07.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout=http://www.ultraq.net.nz/thymeleaf/layout layout:decorate="~{layouts/layout1}"> <div layout:fragment="content"> 본문 영역 입니다. </div> </html>
- layout:decorate="~{layouts/layout1}"> layouts 폴더 아래에 있는 layout1.html 파일을 적용하기 위해서 네임스페이스 추가
- <div layout:fragment="content"> layout1.html 파일의 <div layout:fragment="content"> 영역에 들어가는 영역
com.shop.controller.ThymeleafExController.java
@GetMapping(value = "/ex07") public String thymeleafExample07(){ return "thymeleafEx/thymeleafEx07"; }
- thymeleafEx07.html 파일에 따로 header 영역과 footer 영역을 지정하지 않았지만 작성한 내용이 layout1.html 파일에 포함돼 출력됨
- 이런식으로 공통 영역은 레이아웃으로 만들어 놓고 작성하는 페이지의 content만 변경하면 공통으로 들어가는 내용들을 쉽게 관리할 수 있음
부트스트랩으로 header, footer 영역 수정하기
내비게이션 바(상단의 웹 페이지 이동) 와 footer(하단의 해당 기업의 주소, 전화번호, 이메일 등의 정보)영역 만들기
BootStrap
- 트위터에서 만든 오픈소스로 웹사이트를 쉽게 만들 수 있게 도와주는 HTML, CSS, JS 프레임워크
BootStrap CDN
- Contents Delivery Network 는 물리적으로 멀리 떨어져 있는 사용자에게 콘텐츠를 좀 더 빠르게 제공하기 위한 서비스로 예를 들어 한국에서 미국 서버에 있는 css, javascript, 이미지 등의 리소스를 받기 위해서는 시간 지연이 발생하는데 한국에 같은 소스를 제공해주는 서버가 있다면 물리적 거리가 가깝기 때문에 좀 더 빠르게 받을 수 있다. 즉 일종의 캐시 서버를 두어서 컨텐츠를 빠르게 받을 수 있도록 하는 서비스
resource/templates/layouts/layout1.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- CSS only --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> <!-- JS, Popper.js, and jQuery --> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> <th:block layout:fragment="script"></th:block> <th:block layout:fragment="css"></th:block> </head> <body> <div th:replace="fragments/header::header"></div> <div layout:fragment="content"> </div> <div th:replace="fragments/footer::footer"></div> </body> </html>
resource/templates/fragments/header.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <div th:fragment="header"> <nav class="navbar navbar-expand-sm bg-primary navbar-dark"> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTogglerDemo03" aria-controls="navbarTogglerDemo03" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <a class="navbar-brand" href="/">Shop</a> <div class="collapse navbar-collapse" id="navbarTogglerDemo03"> <ul class="navbar-nav mr-auto mt-2 mt-lg-0"> <li class="nav-item" sec:authorize="hasAnyAuthority('ROLE_ADMIN')"> <a class="nav-link" href="/admin/item/new">상품 등록</a> </li> <li class="nav-item" sec:authorize="hasAnyAuthority('ROLE_ADMIN')"> <a class="nav-link" href="/admin/items">상품 관리</a> </li> <li class="nav-item" sec:authorize="isAuthenticated()"> <a class="nav-link" href="/cart">장바구니</a> </li> <li class="nav-item" sec:authorize="isAuthenticated()"> <a class="nav-link" href="/orders">구매이력</a> </li> <li class="nav-item" sec:authorize="isAnonymous()"> <a class="nav-link" href="/members/login">로그인</a> </li> <li class="nav-item" sec:authorize="isAuthenticated()"> <a class="nav-link" href="/members/logout">로그아웃</a> </li> </ul> <form class="form-inline my-2 my-lg-0" th:action="@{/}" method="get"> <input name="searchQuery" class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search"> <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Search</button> </form> </div> </nav> </div> </html>
- https://getbootstrap.com/ 방문 후 Navbar 코드 Header 영역에 추가후 쇼핑몰에 맞게 수정
resource/templates/fragments/footer.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <div class="footer" th:fragment="footer"> <footer class="page-footer font-small cyan darken-3"> <div class="footer-copyright text-center py-3"> 2022 Shopping Mall Example WebSite </div> </footer> </div> </html>
resources/static/css/layout1.css
html { position: relative; min-height: 100%; margin: 0; } body { min-height: 100%; } .footer { position: absolute; left: 0; right: 0; bottom: 0; width: 100%; padding: 15px 0; text-align: center; } .content{ margin-bottom:100px; margin-top: 50px; margin-left: 200px; margin-right: 200px; }
- footer 영역이 하단에 고정될 수 있도록 css 작성
resource/templates/layouts/layout1.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- CSS only --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> <link th:href="@{/css/layout1.css}" rel="stylesheet"> <!-- JS, Popper.js, and jQuery --> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> <th:block layout:fragment="script"></th:block> <th:block layout:fragment="css"></th:block> </head> <body> <div th:replace="fragments/header::header"></div> <div layout:fragment="content" class="content"> </div> <div th:replace="fragments/footer::footer"></div> </body> </html>
- <link th:href="@{/css/layout1.css}" rel="stylesheet"> , <div layout:fragment="content" class="content"> 추가 및 수정하여 CSS와 HTML 파일 연결
'SpringBoot > 쇼핑몰 프로젝트 with JPA' 카테고리의 다른 글
[백견불야일타] 6장 상품 등록 및 조회하기 (0) 2022.10.08 [백견불야일타] 5장 연간 관계 매핑 (1) 2022.09.20 [백견불야일타] 4장 스프링 시큐리티를 이용한 회원 가입 및 로그인 (0) 2022.09.07 [백견불야일타] 2장 Spring Data JPA (0) 2022.08.19 [백견불여일타] 1장 개발환경구축 (0) 2022.08.16