🦁멋쟁이사자처럼 백엔드 부트캠프 13기 🦁
TIL 회고 - [37]일차
🚀37일차에는 스프링에서 뷰 템플릿 엔진을 제공하는 Thymeleaf 오픈소스 라이브러리에 대해 자세히 공부해볼 수 있었다.
다양한 실습을 통해 Thymeleaf의 문법과 형식에 대해서 배우고
컨트롤러로부터 View Name을 통해 연결시키고
출력해오는 것을 실습해볼 수 있었다.회고를 통해 이론적인 부분을 중점으로 정리해야겠다.
메소드 요청 방식
- 크게 GET방식, POST방식으로 나뉨
GET 방식
- GET정보를 URL로 전송 (공개해도 되는 정보일때 사용)
- 목적 : 데이터 검색을 위함 (URL을 통해 전송되는 모든 데이터는 쿼리 스트링의 일부로 URL에 포함)
➡️ex. localhost:8080/hello?name=myname - 데이터 제한 : URL길이 제한이 있어서 대부분 2048문자를 초과하지 못함
- 캐싱 : GET요청을 캐시 가능
➡️브라우저가 동일한 요청에 대해 캐시된 데이터를 “재사용할 수 있음” - 안전성과 멱등성 : 💡GET은 안전하고 멱등성을 가지고 있어서 요청을 여러번해도 동일한 결과 반환
➡️멱등성 : 연산을 여러번 적용하더라도 결과가 달라지지 않는 성질 - 활용 :
1. 웹 브라우저에서 검색 엔진에 쿼리를 입력할때 사용
2. 쇼핑물 상품 정보를 공유 시 상품의 카테고리와 코드에 따라 바로 해당 상품의 페이지를 가져옴
➡️만약 POST방식으로 보내는 웹사이트이면 URL이 상품코드를 참조하지 못하여 상품이 바로 보이지 않고
해당 페이지의 상품을 검색어로 직접 검색하여 찾아봐야한다.
3. 페이지에 함께 콘텐츠가 노출되어야할 때 사용
POST방식
- POST정보를 body를 통해서 전송
- 목적 : 서버에 데이터를 제출 시 사용 (서버의 상태나 데이터를 변경하기 위한 목적)
➡️ex. 폼 데이터 전송 - 데이터 제한 : GET방식보다는 본문(body)에 데이터를 포함시킬수 있어서 상대적으로 더 큰 데이터 전송 가능
- 캐싱 : POST 요청은 일반적으로는 캐시되지 않는다.
- 안전성과 멱등성이 존재하지 않음 (동일 POST 요청을 여러번 보내면 동일 아이템이 여러번 생성될 수 있음)
- 활용 :
1. 회원가입으로 사용자 등록 (서버의 데이터베이스에 새로운 회원 추가)
2. 제품 주문 (서버의 데이터베이스에 새로운 주문데이터 추가)
3. URL에 정보가 나오면 안될때 사용
요청과 응답 구조
요청구조
- 시작줄(HTTP메소드, 요청대상 URL 등)
+ 헤더(인증 정보, 캐시 정책 등)
+ 본문(Body: 서버로 보낼 데이터를 포함하지만 GET요청은 일반적으로는 본문을 포함하지 않는다)
응답구조
- 상태줄
+ 상태코드 (404, 200OK 등)
+ 상태메시지
+ 헤더 (콘텐츠 유형, 길이, 서버 정보, 쿠키 설정 등)
+ 본문(Body : 요청에 대한 응답 데이터를 포함하여 HTML, JSON, XML등 다양한 형식 가능)
➡️인증이나 인가가 되지않은 페이지 요청시 400번대 오류
➡️서버에서의 오류가 발생 시 500번대 오류
HTTP 요청구조
- 시작줄에 올 수 있는 것들
- HTTP메소드 : 클라이언트가 수행하고자 하는 동작 정의 (GET, POST, PUT, DELETE)
- 요청 URL : 자원의 위치 (ex. /index.html, /api/user 등)
- HTTP 버전 : ex. HTTP/1.1, HTTP/2.0
- GET요청 : 웹 페이지나 이미지 같은 자원 요청 시 사용 (ex. GET /index.html HTTP/1.1)
- POST 요청 : 서버에 정보를 제출(=폼데이터 보낼때) 시 사용 (ex. POST /submit-from HTTP/1.1)
HTTP 응답구조
- 시작줄에 올 수 있는 것들
- 상태 코드 : 응답의 상태를 나타내는 3자리 숫자 (200(성공), 404(찾을 수 없음), 500(서버오류))
ex. 200 OK : 요청이 성공적으로 처리되었음
ex. 404 Not Found : 요청한 자원을 서버에서 찾을 수 없음
ex. 500 Internal Server Error : 서버내부에서 오류가 발생하여 요청을처리할 수 없음
상태 코드 종류
- 정보 응답 : 100~199
- 성공 응답 : 200~299
- 리디렉션 메시지: 300~399
- 클라이언트 오류 : 400~499
- 서버오류 : 500~599
이러한 상태코드들은 웹 개발자가 네트워크 문제를 진단할 수 있도록 도와줌
HTTP 빈 줄 (Blank Line)
- HTTP 헤더와 본문을 구분할때 사용
- CRLF(Carriage Return Line Feed : \r\n으로 표현)
- 헤더의 끝과 본문의 시작을 명시적으로 나타내어
서버와 클라이언트에서 이 빈줄을 통해 헤더 데이터 종료와 본문 데이터 시작을 인식할 수 있게됨
❓요청구조 Body와 응답구조 Body의 차이
요청구조 Body는 주로 POST, PUT, PATCH 같은 메소드에서 사용되며
그 외로
1. 서버에 데이터를 제출하거나 서버 리소스 수정 시 사용
2. 폼 데이터, 파일 데이터, JSON, XML 등 서버로 전송하고자하는 데이터를 포함할때 사용
➡️사용자가 웹 폼에 정보를 입력하고 제출할때, 그 데이터가 HTTP요청의 본문을 통해 서버로 전송됨
➡️API 호출에서 JSON형태로 데이터를 서버로 전송하는 경우 그 JSON객체가 요청 본문에 포함됨
응답구조 Body는 주로 서버가 클라이언트의 요청에 대해 응답 데이터를 전송할때 사용되며
그 외로
1. HTML문서, 이미지 데이터, JSON객체, XML문서 등 요청에 대한 결과를 포함할때 사용
➡️웹 서버가 HTML페이지를 클라이언트에게 전송하는 경우 그 HTML 내용이 응답구조 Body에 포함됨
➡️REST API의 경우 요청에 대한 처리 결과나 조회한 데이터를 JSON형태로 응답구조 Body에 포함하여 반환함
정리하자면
요청본문(=요청구조 Body)은 클라이언트가 서버에 데이터 전달 시 사용하고
응답본문은 서버가 클라이트에게 데이터 전달 시 사용한다는 "목적의 차이"와
요청본문은 주로 클라이언트가 서버에게 어떤 행동을요청하거나 정보를 전달하기 위함과 달리
응답본문은 데이터 형식에서 주로 서버가 처리한 후의 결과나 필요한 정보를 클라이언트에게 전달하기 위한 것이라는
"데이터 형식"의 차이가 있다.
컨트롤러
- 컨트롤러는 Spring MVC 아키텍처에서 중심적인 역할을 하는 “컴포넌트”를 의미
- 웹 애플리케이션에서 사용자의 요청을 받아 그 결과를 처리하고 View에 전달하는 역할
- 즉 비즈니스 로직과 사용자 인터페이스 사이의 다리역할로 상호작용을 관리
컨트롤러의 기능
- @RequestMapping
➡️클라이언트로부터 들어오는 요청인 (URL, HTTP메소드 등)을 적절히 처리하고 매핑하기 위해 이 어노테이션 사용 - @RequestParam, @PathVariable, @ModelAttribute
➡️요청에서 데이터를 추출하고 필요 처리를 한 이후
결과 데이터를 모델 객체에 담아 View로 전달하는 이 어노테이션 활용 - View 선택 : 처리 결과에 따라 사용자에게 보여줄 뷰를 선택하고
이를 위해서 ModelAndView 객체를 반환하거나 View Name을 문자열로 지정
컨트롤러의 종류
- @Controller : 주로 JSP나 Thymeleaf같은 뷰 템플릿 엔진을 사용하여 HTML을 생성
➡️주로 웹 애플리케이션에서 데이터를 뷰에 전달하여 사용자가 볼 수 있는 페이지를 “동적으로 생성하는데 사용” - @RestController : RESTful 웹 서비스를 개발할때 사용되는 컨트롤러로 (@Controller + @ResponseBody)를 의미
➡️데이터를 JSON이나 XML형태로 클라이언트에게 바로 반환하는 등의 "데이터 API 서버를 구축하는데 사용"
▶️실습 - @Controller 사용
@Controller
public class todoController{
@GetMapping("/todo")
public String todo(){
return "todoPage";
}
}
- return "todoPage";
➡️todoPage.html을 찾지 못하면 오류가 발생할 것
▶️실습 - @Controller + @ResponseBody 사용
@Controller
public class todoController{
@GetMapping("/todo")
@ResponseBody
public String todo(){
return "todoPage";
}
}
- @ResponseBody 어노테이션을 추가하면 localhost:8080/todo 에 접속했을때
- "todoPage" 라는 문구가 화면에 출력될 것
▶️실습 - @RestController 사용
@RestController
public class todoController{
@GetMapping("/todo")
public String todo(){
return "todoPage";
}
}
- @Controller와 @ResponseBody가 결합된 형태인 @RestController를 사용하면
todoPage라는 문구가 화면에 출력되는 것을 확인할 수 있다. - 즉 위의 예제와 같은 기능을 하는 코드이다.
❓ModelAndView와 Model 객체 사용 비교하기
▶️실습 - ModelAndView 객체를 반환값으로 사용
@GetMapping("/greeting")
public ModelAndView greet(@RequestParam(name="name", required = false, defaultValue ="world") String name){
ModelAndView mv = new ModelAndView("greeting"); // 뷰 이름 설정
mv.addObject("name", name); // "name"이라는 키로 name이라는 값을 넣음
return mv;
}
- required = false
➡️필수적으로 name 속성을 받지 않아도 된다. - defaultValue = "world"
➡️name값을 가지지 못한 객체이면 "world"라는 기본값을 가지도록 한다. - 가져온 값을 String name에 넣는다.
▶️실습 - Model 객체를 사용하고 String을 반환값으로 사용
@GetMapping("/greeting2")
public String greet2(@RequestParam(name="name", required = false, defaultValue ="world") String name, Model model){
model.addAttribute("name", name); // "name"이라는 키로 name이라는 값을 넣음
return "greeting"; // 뷰의 이름을 반환함
}
- ModelAndView 객체를 반환하는 위의 코드와 같은 기능을 한다.
- 스프링은 내부적으로 같은 일을 수행한다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org/">
<head>
<meta charset="UTF-8">
<title>greeting</title>
</head>
<body>
<h1 th:text=" 'Please Say Hello to [' + ${name} + '] and All the Global fans!'"></h1>
</body>
</html>
- xmlns:th="http://www.thymeleaf.org/"
➡️Thymeleaf 사용하도록 name space를 명시 - required : 필수적으로 옵션을 받을 것이다라고 설정해주는 것
➡️required=true : 옵션을 필수적으로 받아야하고 없으면 오류 발생
(defaultValue=”world”처럼 기본값을 지정해주어도 name이 들어오지 않으면 오류 발생)
➡️required=false : 옵션을 필수적으로 받지 않아도 오류가 발생하지 않는다.
(defaultValue=”world”처럼 기본값이 지정되어있으면 기본값으로 출력)


- 검색 엔진에 쿼리 형식으로 ?name=Michael 속성 입력 시 동적으로 페이지에 적용된다.


- 검색 엔진에 쿼리 형식으로 ?name=Nunez 속성 입력 시 동적으로 페이지에 적용된다.


- 검색 엔진에 쿼리 형식으로 들어온 값이 없을때는
defaultValue = “world”처럼 지정해주었기때문에 world라는 값이 대체해서 동적으로 페이지에 적용된다.
➡️required = false인 경우 출력되는 결과이지만
➡️required =true이면 기본값을 지정해주어도 필수적으로 값이 들어오지 않으면 오류를 발생시킬 것이다.
시퀀스다이어그램을 통한 URL 요청의 흐름

- 1. 클라이언트의 (/greeting) URL 요청
- 2. DispatcherServlet에서 (/greeting) URL 요청에 대한 컨트롤러를 HanlderMapping을 통해 찾음
- 3. handlerMapping이 찾은 후 (return handlerInfo)를 DispatcherServlet에 전달
- 4. DispatcherServlet은 반환된 (handlerInfo)를 토대로 handleRequest 수행
- 5. HandlerAdapter는 MyController라는 (/greeting) URL이 담긴 클래스를 찾은 후 ModelAndView 객체를 만듦
- 6. ModelAndView 객체에 “name” Key에 (name) Value를 넣어 DispatcherServlet에게 ModelAndView 객체 반환
- 7. DispatcherServlet은 반환된 ModelAndView 객체를 토대로
(return "greeting";)에 따라 greeting 이름을 가진 View를 만들어달라고 ViewResolver에게 요청 - 8. ViewResolver는 View를 만든 후 View객체를 DispatcherServlet에게 반환
- 9. DispatcherServlet은 다시 View에게 Model 객체를 렌더링해달라고 요청
- 10. View는 HTML형식으로 렌더링된 화면을 DispatcherServlet에게 반환
- 11. DispatcherServlet은 요청이 처리되었으므로 클라이언트에게 결과 화면을 응답함 (response)
뷰 템플릿 View Template
- 웹 애플리케이션에서 사용자 인터페이스를 구성하는 도구
- 이 템플릿을 활용하여 서버에서 데이터를 동적으로 HTML페이지에 통합하여 웹 브라우저에 렌더링 가능
- 컨트롤러로부터 전달받은 Model 데이터를 사용하여 사용자에게 정보를 제공하는 HTML을 만들어 표시
- 공통 레이아웃, 헤더, 푸터, 네비게이션 바 등을 템플릿 파일에 정의하여 여러 페이지에 걸쳐 재사용가능
(ex. templates/common 디렉토리에 footer.html을 만들어서 재사용가능) - 디자인 변경이 필요할때 HTML코드를 한 곳에서 수정함으로써 전체 사이트의 레이아웃을 쉽게 업데이트 가능
뷰 템플릿 엔진
- View Template Engine (=View Template Container)
➡️ex. 서블릿 엔진 = 서블릿 컨테이너 - Thymeleaf
스프링 MVC와 자연스럽게 통합되어 자바개발자에게 친숙한기능으로
HTML을 서버와 클라이언트 양쪽에서 동일하게 사용할수 있도록 설계됨 - JSP (JavaServer Pages)
Java 기반의 뷰 기술로 동적 웹 페이지 생성에 사용됨
최근 JSP보다 더 현대적이고 유연한 Thymeleaf나 FreeMaker같은 다른 템플릿 엔진을 선호 - FreeMaker
유연성이 높고 강력한 템플릿 언어로, 주로 웹 애플리케이션 개발에 사용
Thymeleaf
- 타임리프 : 현대적인 서버 사이드 자바 템플릿 엔진
- HTMl ,XML, 자바스크립트, CSS 등을 생성하는데 사용
- 자연 템플릿을 지향하여 웹 브라우저에서
템플릿 파일을 정적파일로도 올바르게 표시할 수 있도록하여 개발과정을 도움
Thymeleaf 문법
표현식
- 변수 표현식 : ${…} 모델 속성을 HTML에 삽입
- 선택 변수 표현식 *{…} 선택된 객체에 대해 평가되며 th:object와 함께 사용
- 메시지 표현식 : #{…} 국제화메시지를 템플릿에 삽입
- 링크 URL 표현식 : @{…} URL을 동적으로 생성
- 리터럴 값 : ‘문자열’, + (문자열 결합), *, etc. 등 문자열, 숫자, 불린 값을 표현하고 연산
표준 속성 설정자
- xmlns:th="http://www.thymeleaf.org"
<html>태그 안에 선언해주는 설정
xmlns (=XML의 Name Space)
➡️xmlns:th
Name Space를 th로 가져가겠다는 의미
ex. th:text 처럼 쓰이면 Thymeleaf에서 쓰인 태그인 것을 인지 가능
<h1 th:text=" 'Hello' + ${user.name} + '!'"></h1>
- th:text
➡️태그의 텍스트 콘텐츠 설정
<tr th:each="user : ${users}">
- th:each
➡️반복자 역할로 리스트나 배열 등 순회할때 사용
<div th:if="${user.isAdmin()}"> 어드민이면 표시됨 </div>
- th:if, th:unless
➡️조건부 표시 (해당 값이 true이면 th:if문을 실행, false이면 th:unless문을 실행)
- th:include, th:replace
➡️한 템플릿의 특정 부분을 다른 템플릿 조각으로 포함하거나 대체할때 사용
URL처리
- th:href : 링크 생성에 사용
<a th:href="@{/login}">로그인</a>
▶️실습 - 간단한 웹페이지
@Controller
public class WelcomeController {
@GetMapping("/welcome")
public String welcome(Model model) {
model.addAttribute("welcomeMessage", "Welcome to our awesome website!");
List<Item> items = Arrays.asList(
new Item("Apple", 1.25),
new Item("Banana", 0.75),
new Item("Orange", 0.50)
);
model.addAttribute("items", items);
return "welcome";
}
@Getter
@AllArgsConstructor
static class Item{
private String name;
private double price;
}
}
- Model 객체를 통해 페이지의 제목 부분을 담당할 “welcomeMessage” Key 선언
- Key(welcomeMessage)에 대한 Value로 attributeValue에 위치한 문자열을 가져옴
- Arrays.asList()
➡️객체를 생성해서 바로 값을 간단하게 넣어서 사용할때 활용
넣어준 값을 통해 리스트를 새로 생성해주는 기능을 갖고 있는 것
List<Item> itemList = new ArrayList<>(); itemList.add(new Item("Apple", 1.25)); itemList.add(new Item("Banana", 0.75)); itemList.add(new Item("Orange", 0.75));
asList()를 통해 리스트를 생성하는 코드와 new ArrayList<>()로 각각 저장해주는 코드는 동일한 기능을 수행한다.
간단한 예시로 살펴보면 Arrays.asList()를 활용하여 String타입 리스트를 바로 만들어낼 수 있음// 문자열 리스트를 바로 만들어내는 기능 List<String> testList = Arrays.asList("a", "b", "c"); - return “welcome”;
➡️@GetMapping을 통해 (/welcome) URL 요청을 받으면 정보 처리 후 (welcome.html)을 찾도록 return - @AllArgsConstructor
➡️모든 속성의 생성자를 만든다.
public Item(String name, double price) { this.name = name; this.price =price; }
@AllArgsConstructor가 이 코드와 동일한 기능을 수행한다.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org/">
<head>
<meta charset="UTF-8">
<title>welcome</title>
</head>
<body>
<h1 th:text="${welcomeMessage}"></h1>
<ul>
<li th:each="item : ${items}" th:text="${item.name}" + '>> 가격 : ' + ${item.price}"></li>
</ul>
</body>
</html>
- ${welcomeMessage}
➡️Key로 접근해야하므로 WelcomeController에서 선언했던 Model객체의 addAttribute(“welcomeMessage”) Key가
일치해야 Key에 맞는 Value를 가져올 수 있다. - th:each
➡️반복문을 수행하고 ${items}는 WelcomeController에서 선언했던 Model 객체의 addAttribute(“items”) Key가
일치하여야 그 Value를 토대로 리스트를 가져온다. 리스트를 가져온 후
th:text="${item.name}"
item의 이름을 출력가능 - ${item.name}
Item클래스의 private String name 필드에 직접 접근한 것이 아니라
Item 클래스에 @Getter 어노테이션으로 인해 Getter메소드를 자동으로 호출하여 가져오는 것

- 정적인 HTML을 직접 작성하지 않고 컨트롤러로부터 값을 얻어와서 페이지에 동적으로 데이터를 생성
❓Arrays.asList()
➡️Arrays.asList()는 Arrays의 private 정적 클래스인 ArrayList를 리턴한다.
java.util.ArrayList 와는 달리 java.util.Arrays.ArrayList 클래스를 가지며
set(), get(), contains() 메소드를 가지지만 값을 추가하는 메소드는 가지지 못한다.
만약 asList()를 사용해서 set()을 통해 내용을 수정하면 원본 배열도 함께 바뀐다.
이러한 이유로 새로운 원소를 추가하거나 삭제 할 수 없다.
▶️실습 - footer 포함 시키기 : th:include
- templates/common/footer.html 작성
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>common footer</title>
</head>
<body>
<div th:fragment="footer">
<p>© 2025 My Website. All rights reserved.</p>
</div>
</body>
</html>
- welcome.html에 footer부분을 추가
<footer th:include="common/footer :: footer">Footer content</footer>
- th:include="common/footer :: footer"
➡️디렉토리 하위의 footer 에 있는 footer를 포함시킴 - :: footer
➡️fragment=”footer” 를 사용하겠다는 의미
즉 footer.html안에 있는 모든 콘텐츠를 가져오는 것이 아닌 fragment가 footer인 부분을 선택해서 가져옴 - $copy ;
© 을 의미
➡️©️ copyright 특수문자를 가져옴

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org/">
<head>
<meta charset="UTF-8">
<title>greeting</title>
</head>
<body>
<h1 th:text=" 'Please Say Hello to [' + ${name} + '] and All the Global fans!'"></h1>
<footer th:include="common/footer :: footer">Footer content</footer>
</body>
</html>
- footer는 재사용이 가능하므로 (/greeting) URL에도 활용할 수 있다.
- 이처럼 <body>태그 안에 <footer th:include="..."></footer>를 추가해줄 수 있다.

뷰 템플릿 + 데이터 모델
- 웹 애플리케이션의 중요한 부분으로 컨트롤러가 처리한 데이터를 사용자에게 표시 가능
- 컨트롤러는 모델 객체에 데이터를 추가하고 뷰 템플릿은 이 데이터를 사용하여 HTML을 동적으로 생성하는 것
데이터모델
- 데이터모델은 컨트롤러와 View 사이에서 데이터를 전달하는 컨테이너 역할
- 주로 Model, ModelMap, ModelAndView 객체를 사용하여 View에 데이터를 전달
- Model : 간단한 인터페이스로 뷰에 전달될 데이터를 추가하는 메소드를 제공
- ModelMap : Map에 기반한 구체적인 구현으로 속성을 이름-값 쌍으로 저장
- ModelAndView : 모델 데이터와 뷰의 이름을 동시에 포함하는 객체로 컨트롤러가 뷰와 모델을 함께 반환
❓컨트롤러를 나누는 이유
- 하나의 컨트롤러가 다 가지는 것보단 페이지가 연관된 것(=주요 기능)을 기준으로 나눠주는 것이
기능 분리에 있어서 URL로 접근하기 좋을 것이다. - ex. 로그인 관련 컨트롤러, 메인페이지 관련 컨트롤러, 아이템 컨트롤러, 장바구니 컨트롤러 등
▶️실습 - 과일 가게
products.html
<body>
<h1 th:text="${titleText}"></h1>
<header th:include="common/footer :: product_header"></header>
<ul>
<li th:each="product : ${products}" th:text="${product.id} + '번 진열대[' + ${product.name} + ']의 가격 : ' + ${product.price} + '원'"></li>
</ul>
</body>
- <header> 부분을 추가
footer.html
<body>
<div th:fragment="product_header">
<p>Header : 2025년도 업데이트된 상품입니다.</p>
</div>
<div th:fragment="footer">
<p>© 2025 My Website. All rights reserved.</p>
</div>
</body>
- footer에 product_header fragment를 추가하여 <header>로 활용한 것
ProductController
@Controller
public class ProductController {
final int MONEY_FORMATT_KOREA = 1000;
private List<Product> products = Arrays.asList(
new Product(1, "Apple", 1.20*MONEY_FORMATT_KOREA),
new Product(2, "Banana", 2.50*MONEY_FORMATT_KOREA),
new Product(3, "Cherry", 3.33*MONEY_FORMATT_KOREA)
};
@GetMapping("/products")
public String showProducts(Model model){
model.addAttribute("titleText", "-----상품 페이지-----");
model.addAttribute("products", products);
return "products";
}
@Getter
@AllArgsConstructor
static class Product{
private int id;
private String name;
private double price;
}
}
- 상품 정보를 가진 Product 클래스를 내부클래스로 만듦
- final int MONEY_FORMATT_KOREA = 1000;
➡️1000원 단위의 가격을 만들어보도록 구현. 다른 국가의 화폐 단위를 추가할 수 있도록 구현 가능 - @GetMapping("/products")
➡️localhost:8080/products URL로 접근 - List<Product> products = Arrays.asList()
➡️미리 리스트를 만든 후 그 리스트 showProducts() 메소드에서 사용 - model.addAttribute("products", products);
➡️“products”는 Key, products는 Value
products 리스트가 model 객체에 전달되어 그 객체를 addAttribute()로 추가한 후
return “products”처럼 뷰 이름을 반환 - 이 컨트롤러의 데이터를 뷰 이름(products)에 해당하는 HTML파일을 templates에서 찾아서 연결

다양한 Thymeleaf 템플릿 문법
@Controller
@RequestMapping("/exam")
public class ExamController {
@GetMapping("/example")
public String showExample(Model model){
model.addAttribute("username", "JuunB");
model.addAttribute("isAdmin", true);
model.addAttribute("languages", new String[]{"English", "Spanish", "Portugues"});
return "example";
}
}
- 컨트롤러에 @RequestMapping을 선언
➡️showExample()메소드에 접근하기 위해서는 localhost:8080/exam/example URL로 접근가능하게 된다. - 💡이 방법은 URL이 명확해지므로 조금 더 직관적이고 구분하기 쉬울 것
- 전체 컨트롤러에서 URL이 중복되면 동작하지 않으므로
중복되지 않도록 관련있는 페이지들끼리 연관되도록 URL 정책을 정해주는 것이 좋다.
정답은 없지만 일관성있는 URL 정책을 가지는 것이 좋다.
➡️ex. A 팀의 C팀원과 B팀원이 다른 URL 정책을 사용하면 일관성이 깨진다는 의미
<body>
<!-- 변수 표현식 -->
<p>Username : <span th:text="${username}"></span></p>
<p>isAdmin : <span th:text="${isAdmin}"></span></p>
<!-- 선택 변수 표현식 -->
<div th:object="${languages}">
<p>
Languages : <span th:text="*{[0]} + ', '"></span>
<span th:text="*{[1]} + ', '"></span>
<span th:text="*{[2]}"></span>
</p>
</div>
</body>
- <span th:text="*{[0]}"></span>
➡️object로 배열을 얻어왔으므로 $대신 *을 사용하며 배열의 0번째 인덱스를 꺼내도록 함

메시지 표현식

- resources → New → Resources Bundle 형식으로 만듦
- messages.properties 이름으로 생성
➡️(messages)는 정해진 이름이며 application.yml에서 basename을 바꿀 수도 있다.
messages.properties
welcome.message=Welcome Message!!
- (welcome.message) Key와 "Welcome Message!!" Value 작성
example.html
<!-- 메시지 표현식 -->
<p th:text="#{welcome.message}"></p>
- "#{welcome.message}"
➡️Key로 불러오는데 메시지 표현식을 불러오는 것이므로 (#) 사용

- 브라우저에 설정된 언어에 따라 메시지 표현을 다르게 설정 가능
- messages_en.properties 설정파일은 english 언어 설정
- messages_ko.properties 설정파일은 한국어 언어 설정을 넣는 것
기본 messages
welcome.message=[default] : Welcome Message!!
영어 messages
welcome.message=[english] : Welcome Message!!
한국어 messages
welcome.message=[korean] : 반가워요!!
application.yml
messages:
encoding: UIF-8
cache-duration: 0
- 설정값 추가
- basename: messages 속성은
만약 basename: menu 처럼 바꿔주주면 menu.properties를 찾아서 사용하게 되는 것


- messages_en.properties와 messages_ko.properties, messages.properties 설정 3개를 만들어주었지만
<!-- 메시지 표현식 -->
<p th:text="#{welcome.message}"></p>
- Key {welcome.message}만 써주어도 application.yml의 UTF-8 설정에 따라 브라우저의 설정에 맞춰서 messages_ko.properties를 적용
- 활용 :
사용자의 브라우저의 언어 설정에 따라 기본 메뉴나 환영 메시지, 종료 메시지 등
messages.properties 파일로 관리하여 동적으로 적용하도록 구현 가능
링크 표현식
example.html
<!-- 링크 표현식 -->
<a th:href="@{/products}">상품페이지로 이동</a>
- href 추가 : products페이지로 이동할 수 있는 하이퍼링크가 만들어짐


// 타임리프 코드
<a th:href="@{/products(name=${name})}">상품페이지로 이동</a>
// HTML코드
<a href="/products?name=grape">상품페이지로 이동></a>
- 두 코드는 동일한 기능 수행
- 하지만 HTML코드는 grape라는 이름으로만 정적 동작하지만,
타임리프 코드는 ProductController에서 name의 값을 동적으로 바꿔주면 동적으로 페이지에 표시되는 특징이 있다.
만약 타임리프 링크표현식의 속성이 여러개일 경우
<a th:href="@{/products(name=${name}, price=${price}) }">상품페이지로 이동</a>
- 쉼표를 통해서 여러 개의 속성을 가져올 수 있음
조건 표현식
<!-- 조건에 따른 값 -->
<div th:if="${isAdmin}">
<p><span th:text="${username}"></span>은 관리자입니다. (true일때 출력)</p>
</div>
<div th:unless="${isAdmin}">
<p><span th:text="${username}"></span>은 관리자가 아닙니다. (false일때 출력)</p>
</div>
- th:if 는 true일때 그 밑의 문장을 출력
- th:unless 는 false일때 그 밑의 문장을 출력

메시지 표현식에 다른 속성 추가
messages_en.properties
product.name=[english] : Apple
messages_ko.properties
product.name=[korean] : 사과
<!-- 메시지 표현식 -->
<p th:text="#{welcome.message}"></p>
<p th:text="#{product.name}"></p>

- 메시지뿐만 아니라 값에 따라서도 출력을 달리해줄 수 있음
🚀실습 - 유저 테이블 만들기
@Controller
public class UserController {
@GetMapping("/users")
public String showUsers(Model model){
List<User> users = Arrays.asList(
new User(101, "Duran", false, 21),
new User(102, "Kubo", false, 23),
new User(103, "Kerkez", true, 21),
new User(104, "Zubimendi", false, 25)
);
model.addAttribute("DocsTitle", "LEFT 회사 사원 목록표");
model.addAttribute("userList", users);
return "userPage";
}
@Getter
@AllArgsConstructor
class User{
private int empId;
private String empName;
private boolean isAdmin;
private int age;
}
}
<tbody>
<tr th:each="user : ${userList}">
<td th:text="${user.empId}">사원 번호</td>
<td th:text="${user.empName}">사원 이름</td>
<td th:text="${user.age}">사원 나이</td>
<td th:if="${user.isAdmin}" th:text="'관리자'">관리자입니다.</td>
<td th:unless="${user.isAdmin}" th:text="'일반사원'">일반사원입니다.</td>
</tr>
</tbody>

🚀 회고를 작성하다보니 다시 기억하고 싶은 것이 많아 글이 길어졌다.
그만큼 배운 것이 많았던 회고였다.
Spring 프레임워크를 처음배울때보다는 실습을 통해 Thymeleaf를 활용하는 방법 등을 더 익힐 수 있었다.
'Recording > 멋쟁이사자처럼 BE 13기' 카테고리의 다른 글
| [멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_39일차_"쿠키 / 세션" (1) | 2025.02.03 |
|---|---|
| [멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_38일차_"스프링 포워딩/리다이렉팅" (2) | 2025.01.24 |
| [멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_36일차_"스프링 MVC" (2) | 2025.01.22 |
| [멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_35일차_"스프링 AOP" (1) | 2025.01.21 |
| [멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_34일차_"스프링 Optional, Annotation" (2) | 2025.01.20 |