저번에 타임 리프가 뭔지 알아 봤으니까, 이번에는 타임리프의 기능에 대해서 알아보자 !
타임리프는 HTML을 기반으로 하고 있기 때문에, HTML에 기능이 몇개 정도 추가 되었다고 생각하면 된다.
참고로 타임리프를 사용하기 위해서는 <html xmlns:th="http://www.thymeleaf.org"> 태그가 필요한 점을 까먹지 말자.
흐름을 찾아가기 위한 컨트롤러
package hello.thymeleaf.basic;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.Data;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/basic")
public class BasicController {
@GetMapping("text-basic")
public String textBasic(Model model) {
model.addAttribute("data", "Hello <b>Spring!</b>");
return "basic/text-basic";
}
@GetMapping("text-unescaped")
public String textUnescaped(Model model) {
model.addAttribute("data", "Hello <b>Spring!</b>");
return "basic/text-unescaped";
}
@GetMapping("/variable")
public String variable(Model model) {
User userA = new User("userA", 10);
User userB = new User("userB", 20);
List<User> list = new ArrayList<>();
list.add(userA);
list.add(userB);
Map<String, User> map = new HashMap<>();
map.put("userA", userA);
map.put("userB", userB);
model.addAttribute("user", userA);
model.addAttribute("users", list);
model.addAttribute("userMap", map);
return "basic/variable";
}
@GetMapping("/basic-objects")
public String basicObjects(Model model, HttpServletRequest request,
HttpServletResponse response, HttpSession session) {
session.setAttribute("sessionData", "Hello Session");
model.addAttribute("request", request);
model.addAttribute("response", response);
model.addAttribute("servletContext", request.getServletContext());
return "basic/basic-objects";
}
@Component("helloBean")
static class HelloBean {
public String hello(String data) {
return "Hello " + data;
}
}
@GetMapping("/date")
public String date(Model model) {
model.addAttribute("localDateTime", LocalDateTime.now());
return "basic/date";
}
@GetMapping("/link")
public String link(Model model) {
model.addAttribute("param1", "data1");
model.addAttribute("param2", "data2");
return "basic/link";
}
@Data
static class User {
private String username;
private int age;
public User(String username, int age) {
this.username = username;
this.age = age;
}
}
}
Text 출력하기
HTML 콘텐츠에 데이터를 출력하기 위해서는 th:text 를 사용하자.
<span th:text="${data}">
HTML 태그의 속성이 아닌 콘텐츠 영역 안에 직접 데이터를 출력하고 싶으면 [[..]] 를 사용하자.
[[${data}]]
사용 예시
<!DOCTYPE html>
<html xmlns:th = "http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>컨텐츠에 데이터 출력하기</h1>
<ul>
<li>th:text 사용 <span th:text="${data}"></span></li>
<li>컨텐츠 안에서 직접 출력하기 = [[${data}]]</li>
</ul>
</body>
</html>

Escape 처리
HTMl 문서는 <나 > 등 특수문자를 기반으로 정의한다.
그래서 항상 특수문자를 사용할때는 주의해야한다.
위 결과 예시를 볼 때, 우리는 글자를 bold 처리 하기 위한 <b> 태그를 정의 했을 뿐인데, 컴파일 후 웹 소스 코드를 보면 다음과 같다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>컨텐츠에 데이터 출력하기</h1>
<ul>
<li>th:text 사용 <span>Hello <b>Spring!</b></span></li>
<li>컨텐츠 안에서 직접 출력하기 = Hello <b>Spring!</b></li>
</ul>
</body>
</html>
우리의 목적과는 다르게 <b> 태그 부분이 그대로 나오고, < > 부분이 < 와 > 로 나오는 것을 볼 수 있다.
HTML 엔티티
기본적으로 웹 브라우저는 <를 태그의 시작으로 인식하기 때문에, 우리는 이런 기호를 문자로 표현할 수 있는 방법이 필요하다.
이 방법을 HTML 엔티티라고 하고, 이렇게 HTML에서 사용하는 특수 문자를 HTML 엔티티로 변경하는 것을 Escape 처리 라고 한다.
타임리프의 th:text 혹은 [[..]]등은 기본적으로 이스케이프를 지원한다.

Unescape 처리
이스케이프가 필요 없는 상황이 있을 수 있다.
다음과 같이 작성하면 언익스케이프 처리를 할 수 있다
th:text => th:utext
[[..]] => [(..)]
<!DOCTYPE html>
<html xmlns:th = "http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>text vs utext</h1>
<ul>
<li>th:text 사용 <span th:text="${data}"></span></li>
<li>th:utext 사용 <span th:utext="${data}"></span></li>
</ul>
<h1><span th:inline="none">[[...]] vs [(..)]</span></h1>
<ul>
<li><span th:inline="none">[[...]] = </span>[[${data}]]</li>
<li><span th:inline="none">[(..)] = </span>[(${data})]</li>
</ul>
</body>
</html>

변수 - SpringEL
타임 리프에서 변수 사용시 변수 표현식을 쓴다.
${data}
그리고 이 변수 표현식에는 SpringEL 이라는 스프링이 제공하는 표현식이 사용 가능하다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>SpringEL 표현식</h1>
<ul>Object
<li>${user.username} = <span th:text="${user.username}"></span></li>
<li>${user['username']} = <span th:text="${user['username']}"></span></li>
<li>${user.getUsername()} = <span th:text="${user.getUsername()}"></span></li>
</ul>
<ul>List
<li>${users[0].username} = <span th:text="${users[0].username}"></span></li>
<li>${users[0]['username']} = <span th:text="${users[0]['username']}"></span>
</li>
<li>${users[0].getUsername()} = <span th:text="${users[0].getUsername()}"></span>
</li>
</ul>
<ul>Map
<li>${userMap['userA'].username} = <span th:text="${userMap['userA'].username}"></span></li>
<li>${userMap['userA']['username']} = <span th:text="${userMap['userA']['username']}"></span></li>
<li>${userMap['userA'].getUsername()} = <span th:text="${userMap['userA'].getUsername()}"></span></li>
</ul>
<h1>지역 변수 - (th:with)</h1>
<div th:with="first=${users[0]}">
<p>처음 사람의 이름은 <span th:text="${first.username}"></span></p>
</div>
</body>
</html>
변수를 사용하는 방법이 약 3가지 정도가 있는데, 결국 모두 프로퍼티 접근법으로 귀결 된다.
형태가 대부분 모델로 넘어온 데이터의 키값(객체).필드명 인데, 이 때 필드명은 getXXX가 실행 된다고 보면 된다.
지역 변수도 th:with 를 통해 사용 가능한데, 이름이 지역 변수인 만큼, 스코프는 해당 변수를 선언한 태그 내에서만 사용가능하다.

기본 객체
타임리프는 기본으로 제공하는 객체들이 존재한다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>식 기본 객체 (Expression Basic Objects)</h1>
<ul>
<li>request = <span th:text="${request}"></span></li>
<li>response = <span th:text="${response}"></span></li>
<li>session = <span th:text="${session}"></span></li>
<li>servletContext = <span th:text="${servletContext}"></span></li>
<li>locale = <span th:text="${#locale}"></span></li>
</ul>
<h1>편의 객체</h1>
<ul>
<li>Request Parameter = <span th:text="${param.paramData}"></span></li>
<li>session = <span th:text="${session.sessionData}"></span></li>
<li>spring bean = <span th:text="${@helloBean.hello('Spring!')}"></span></li>
</ul>
</body>
</html>
기본 객체는 쉬우니 넘어가고, 편의 기능을 보자
URL에 쿼리 파라미터로 넘어오는 파라미터를 쉽게 조회 할 수 있고, 세션 데이터, 그리고 ${@{스프링빈}} 문법을 통해 스프링 빈도 직접 조회할 수 있다.

유틸리티 객체와 날짜
컨트롤러에서 모델에 LocalDateTime.now() 객체를 넘기면 해당 객체의 다음과 같은 데이터들을 조회 할 수 있다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>LocalDateTime</h1>
<ul>
<li>default = <span th:text="${localDateTime}"></span></li>
<li>yyyy-MM-dd HH:mm:ss = <span th:text="${#temporals.format(localDateTime,'yyyy-MM-dd HH:mm:ss')}"></span></li>
</ul>
<h1>LocalDateTime - Utils</h1>
<ul>
<li>${#temporals.day(localDateTime)} = <span th:text="${#temporals.day(localDateTime)}"></span></li>
<li>${#temporals.month(localDateTime)} = <span th:text="${#temporals.month(localDateTime)}"></span></li>
<li>${#temporals.monthName(localDateTime)} = <span th:text="${#temporals.monthName(localDateTime)}"></span></li>
<li>${#temporals.monthNameShort(localDateTime)} = <span th:text="${#temporals.monthNameShort(localDateTime)}"></span></li>
<li>${#temporals.year(localDateTime)} = <span th:text="${#temporals.year(localDateTime)}"></span></li>
<li>${#temporals.dayOfWeek(localDateTime)} = <span th:text="${#temporals.dayOfWeek(localDateTime)}"></span></li>
<li>${#temporals.dayOfWeekName(localDateTime)} = <span th:text="${#temporals.dayOfWeekName(localDateTime)}"></span></li>
<li>${#temporals.dayOfWeekNameShort(localDateTime)} = <span th:text="${#temporals.dayOfWeekNameShort(localDateTime)}"></span></li>
<li>${#temporals.hour(localDateTime)} = <span th:text="${#temporals.hour(localDateTime)}"></span></li>
<li>${#temporals.minute(localDateTime)} = <span th:text="${#temporals.minute(localDateTime)}"></span></li>
<li>${#temporals.second(localDateTime)} = <span th:text="${#temporals.second(localDateTime)}"></span></li>
<li>${#temporals.nanosecond(localDateTime)} = <span th:text="${#temporals.nanosecond(localDateTime)}"></span></li>
</ul>
</body>
</html>

URL 링크
URL 을 생성할 때는 @{URL 주소} 문법을 쓰자
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>URL 링크</h1>
<ul>
<li><a th:href="@{/hello}">basic url - 가장 단순한 표현</a></li>
<li><a th:href="@{/hello(param1=${param1}, param2=${param2})}">hello query param - 링크에 쿼리 파라미터 넣기</a></li>
<li><a th:href="@{/hello/{param1}/{param2}(param1=${param1}, param2=${param2})}">path variable</a></li>
<li><a th:href="@{/hello/{param1}(param1=${param1}, param2=${param2})}">path variable + query parameter = 남는 경로 변수는 쿼리 파라미터로 치환</a></li>
</ul>
</body>
</html>
현재 도메인을 기준으로 /hello 경로로 이동한다.
여러 파라미터도 편리하게 조정할 수 있다.
() = 쿼리 파라미터
{} = 경로 변수
${} = 모델의 값

문자 리터럴
리터럴이란 소스 코드 상에 고정된 값을 의미한다.
타임 리프에는 다음과 같은 리터럴들이 존재한다.
- 문자 : 'hello'
- 숫자 : 10
- boolean : ture, false
- null : null
우리가 주의해야할 점은 타임리프에서 항상 문자 리터럴은 작은 따옴표로 감싸야한다는 것이다!!!!!
<span th:text="hello world!"></span>
이딴식으로 쓰면 의미 없는 토큰으로 인식해서 출력도 안되고 심지어 예외까지 터진다 ㅋㅋㅋ
하지만 모든 문자를 항상 작은 따옴표로 감싸는건 조금 귀찮으니까 타임리프에서 기능을 제공하는데 밑에서 보자.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>리터럴</h1>
<ul>
<!-- 다음 주석 풀면 예외 발생-->
<!-- <li>"hello world!" = <span th:text="hello world!"></span></li>-->
<li>'hello world!' = <span th:text="'hello world!'"></span></li>
<li>'hello ' + ${data} = <span th:text="'hello ' + ${data}"></span></li>
<li>리터럴 대체 |hello ${data}| = <span th:text="|hello ${data}|"></span></li>
</ul>
</body>
</html>
+ 기호를 써서 자바 문자처럼 잘 붙이는 방법도 있겠으나, | 이 안에 문자 입력 | 처럼 리터럴 대체 문법을 쓰면 || 안에 내용을 모두 문자로 인식하여 편리하게 사용 가능하다.
연산
연산은 자바랑 비슷해서 크게 어려운 내용은 없고, 아까 말한 HTML 엔티티로 익스케이프 되는 부분만 조심하면 된다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li>산술 연산
<ul>
<li>10 + 2 = <span th:text="10 + 2"></span></li>
<li>10 % 2 == 0 <span th:text="10 % 2 == 0"></span></li>
</ul>
</li>
<li>비교 연산
<ul>
<li>1 > 10 = <span th:text="1 > 10"></span></li>
<li>1 gt 10 = <span th:text="1 gt 10"></span></li>
<li>1 >= 10 = <span th:text="1 >= 10"></span></li>
<li>1 ge 10 = <span th:text="1 ge 10"></span></li>
<li>1 == 10 = <span th:text="1 == 10"></span></li>
<li>1 != 10 = <span th:text="1 != 10"></span></li>
</ul>
</li>
<li>조건식
<ul>
<li>(10 % 2 == 0)? '짝수':'홀수' = <span th:text="(10 % 2 == 0)? '짝수':'홀수'"></span></li>
</ul>
</li>
<li>Elvis 연산자
<ul>
<li>${data}?: '데이터가 없습니다' = <span th:text="${data}?: '데이터가 없습니다'"></span> </li>
<li>${nullData}?: '데이터가 없습니다' = <span th:text="${nullData}?: '데이터가 없습니다'"></span> </li>
</ul>
</li>
<li>No-Operation
<ul>
<li>${data}?: _ = <span th:text="${data}?: _">데이터가 없습니다</span> </li>
<li>${data}?: _ = <span th:text="${nullData}?: _">데이터가 없습니다</span> </li>
</ul>
</li>
</ul>
</body>
</html>
조건식은 마치 삼항연산자 같다.
Elvis 연산자 (?:)
데이터가 있으면 해당 데이터를 출력하고, 없거나 null 이면 설정해둔 디폴트 값 출력('데이터가 없습니다').
No-Operation( _ )
데이터가 없어서 _ 가 선택된 경우 설정해둔 HTML 기본 설정을 활용할 수 있다.
즉, _가 선택되면 타임리프가 무효화 된다고 생각하면 된다.

'Spring > Thymeleaf' 카테고리의 다른 글
| [Thymeleaf] 타임리프와 스프링의 통합 1 - 입력 폼 처리하기 (0) | 2025.06.29 |
|---|---|
| [Thymeleaf] 타임리프 기본 기능 4 - 템플릿 조각과 레이아웃 (0) | 2025.06.29 |
| [Thymeleaf] 타임리프 기본 기능 3 - 자바 스크립트 인라인 (0) | 2025.06.29 |
| [Thymeleaf] 타임리프 기본 기능 2 - 속성 값 설정, 반복, 조건부 평가, 주석, 블록 (0) | 2025.06.28 |
| [Thymeleaf] Thymeleaf를 소개합니다 ! (0) | 2025.06.25 |