타임리프(Thymeleaf) 간단 정리

2025. 5. 9. 19:18Spring/MVC

1. 타임리프 선언

<!-- HTML 최상단에 타임리프 네임스페이스 선언이 필요 -->
<html xmlns:th="http://www.thymeleaf.org">

2. 타임리프 핵심

  • 핵심은 th:xxx 가 붙은 부분은 서버사이드에서 렌더링 되고, 기존 속성을 대체한다.
    • HTML 파일을 브라우저에서 직접 열면 th: 속성은 무시되고 원래 HTML만 보여짐 → 순수 HTML처럼 보임
    • 서버를 통해 렌더링하면 th: 속성이 적용된 최종 결과가 보여짐
  • JSP는 서버 렌더링 없이 브라우저에서 직접 열면 깨지지만, 타임리프는 안 깨짐
  • 이렇게 순수 HTML을 그대로 유지하면서 뷰 템플릿도 사용할 수 있는 타임리프의 특징을 네츄럴 템플릿이라 한다.

3. 속성 변경 - th:xxx

<!-- 정적 리소스 경로 변경 -->
<a href="/css/bootstrap.min.css"
   th:href="@{/css/bootstrap.min.css}">

<!-- 클릭 이벤트 속성 변경 -->
<button onclick="location.href='addForm.html'"
	th:onclick="|location.href='@{/basic/items/add}'|">등록</button>
  • 기존 HTML 속성을 동적으로 바꾸고 싶을 때 사용한다.
  • th:href, th:onclick, th:src 등 대부분의 속성에 th:를 붙여서 사용 가능
  • th:onclick 같이 JS 코드가 필요한 경우는 리터럴 대체 문법 (|...|) 을 사용하면 편리하게 작성할 수 있다 

4. 내용 변경 - th:text

<td th:text="${item.price}">10000</td>
  • th:text는 내부 텍스트를 ${...} 표현식 값으로 바꿔줌
  • 원래 있는 10000은 무시되고, 서버에서 렌더링한 값으로 대체됨

5. 변수 표현식 - ${...}

item.getPrice() → ${item.price}
  • 모델(Model)에 담긴 데이터나 타임리프 변수로 선언한 값을 출력할 때 사용
  • 자바의 getter 방식으로 접근

6. URL 링크 표현식 - @{...} 

<!-- 기본 사용 -->
<a th:href="@{/basic/items}">목록</a>

<!-- 경로 변수 포함 -->
<a th:href="@{/basic/items/{id}(id=${item.id})}">상세보기</a>

<!-- 리터럴 대체 --> 
<a th:href="@{|/basic/items/${item.id}|}">상세보기</a>

<!-- 쿼리 파라미터 포함 -->
<a th:href="@{/basic/items/{id}(id=${item.id}, query='test')}">상세보기</a>
  • URL 링크 표현식 @{...}을 사용하면 컨텍스트 경로를 자동으로 포함해준다 
  •  컨텍스트 경로: 브라우저에서 접속할 때, 웹 앱이 시작되는 하위 경로
    • 개발자가 application.properties (또는 yml) 같은 설정파일에서 지정할 수 있다
    • 기본 컨텍스트 경로는 / 루트 (localhost:8080/) 

7. 문자열 리터럴 대체 문법 - |...|

  • 타임리프에서 문자와 표현식 등은 분리되어 있기 때문에 + 기호를 통해 각각 더해서 사용해야 한다.
  • 하지만 리터럴 대체 문법을 사용하면, 더하기 없이 편리하게 사용할 수 있다.
<span th:text="'Welcome to our application, ' + ${user.name} + '!'">
→ <span th:text="|Welcome to our application, ${user.name}!|">

<button th:onclick="'location.href=' + '\'' + @{/basic/items/add} + '\''">
→ <button th:onclick="|location.href='@{/basic/items/add}'|">

8. 반복 출력 - th:each

<tr th:each="item : ${items}">
  <td th:text="${item.name}"></td>
</tr>
  • items는 Model에 담긴 리스트
  • 반복문을 돌면서 item 객체를 하나씩 꺼내 사용
  • HTML 태그를 반복해서 렌더링할 수 있음

9. 예시 코드 

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <link th:href="@{/css/bootstrap.min.css}"
            href="../../static/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container" style="max-width: 600px">
    <div class="py-5 text-center">
        <h2>상품 목록</h2> </div>
    <div class="row">
        <div class="col">
            <button class="btn btn-primary float-end"
                    onclick="location.href='addForm.html'"
                    th:onclick="|location.href='@{/basic/items/add}'|"
                    type="button">상품 등록</button>
        </div>
    </div>
    <hr class="my-4">
    <div>
        <table class="table">
            <thead>
            <tr>
                <th>ID</th>
                <th>상품명</th>
                <th>가격</th>
                <th>수량</th>
            </tr>
            </thead>
            <tbody>
            <tr th:each="item : ${items}">
                <td><a href="item.html" th:href="@{/basic/items/{itemId}(itemId=${item.id})}" th:text="${item.id}">회원id</a></td>
                <td><a href="item.html" th:href="@{|/basic/items/${item.id}|}" th:text="${item.itemName}">상품명</a></td>
                <td th:text="${item.price}">10000</td>
                <td th:text="${item.quantity}">10</td>
            </tr>
            </tbody>
        </table>
    </div>
</div> <!-- /container -->
</body>
</html>