🌿 스프링/스프링 MVC 2편

타임리프 기본 기능 과 스프링

le2donguk 2026. 2. 14. 17:09

타임 리프의 기본 기능

th:text , th:utext

<span th:text="${data}">
<span th:utext="${data}">

 

서버에서 전달된 데이터를 출력하고 싶을 때 타임리프는 th:text , th:utext를 쓴다

하지만 HTML 은 이스케이프가 존재한다 

개발자는 th:text를 통해 <b>hellp</b> 를 출력하고 싶지만 실제 html이 < ,>를 만나면 특수문자로 인식해 다음과 같이 바꾼다

  • <  → $&lt 
  • > → &gt

이렇게 HTML에서 사용하는 특수문자를 Entity로 변환하는 것을 Escape라고 한다 

그래서 utext를 사용하면 이런 Escape에 빠지지 않고 있는 문자 그대로 출력할 수 있다 

 

 

SpringEL

타임리프에서 변수를 사용할 때는 변수 표현식을 사용한다 

${...}

 

해당 변수 표현식을 통해서 Object(보통 Model) , List , Map , RequestParam에 접근할 수 있다

  • Object
    • ${user.username}
    • ${user ['username']}
    • ${user.getUsername()}
  • List
    • ${users [0]. username}
    • ${users [0]['username']}
    • ${users [0]. getUsername()}
  • Map
    • ${userMap ['userA']. username}
    • ${userMap ['userA']['username']}
    • ${userMap ['userA']. getUsername()}
참고
타임 리프에서 단순하게 ${data}를 쓰면 SpringEL에 우선순위에 따라 해당 변수를 탐색하기 시작한다
탐색의 우선순위는 다음과 같다

1. Request Attribute
2. Session Attribute
3. Application Attribute
4. Request Parameter

&{data}만 해도 우선순위를 따라서 알아서 찾게 할 수 있지만 우선순위를 타기 싫고  탐색범위를 지정할 수 있게 할 수 있다
${username} 모든 scope 뒤짐
${param.username} request parameter만 봄
${session.username} session만 봄

 

 

기본 객체들

원래는 parameter를 View template에 넘기려면 Model에 param을 넘기고 템플릿에서 랜더링 해야 하는데

타임 리프는 이러한 점을 해결하기 위해 편의 객체를 제공한다

  • HTTP 요청 파라미터 접근 : param
    • ${param.paramData}
  • Http 세션 접근 session
    • ${session.sessionData}
  • 스프링 빈 접근 : @
    • ${@빈이름. 빈메서드}

 

URL

1. 보통의 URL

타임리프에선 URL 링크를 생성할 때 @{...} 문법을 사용하면 된다

단순하게 URL 링크를 걸어주고 싶으면 다음과 같이 사용하면 된다

@{/hello/hi/my/name/}

 

2. 쿼리 파라미터의 URL

쿼리 파라미터를 추가하고 싶으면 다음과 같은 문법을 제공한다

@{/hello (param1=${param1}, param2=${param2}) }

 

추가하고 싶은 쿼리 파라미터가 있으면 URL 뒤에 (...) 안에 선언하면 된다

위에 예시의 최종 모습 다음과 같다

/hello/data1/data2

 

3. PathVariable의 URL

@{/hello/{param1}/{param2} (param1=${param1}, param2=${param2}) }

 

PathVariable은 Controller에서 사용하는 문법처럼 {}을 사용하여 URL을 구성하고

변수에 대한 정보는 (...) 안에 정의하면 된다 

최종 모습은 다음과 같다 

/hello/data1/data2

 

PathVariable + 쿼리파리미터의 URL

마지막으로 PathVariable과 쿼리파라미터를 같이 쓴 경우 다음과 같이 선언하면 된다

@{/hello/{param1}(param1=${param1}, param2=${param2})}

 

 

리터럴

타임 리프에서 문자열은 ''로 감싸야한다  예시를 보자면

<span th:text="'hello world'"></span>

 

이렇게 감싸야하는데 만약 문자와 변수를 같이 쓰고 싶다면 Java에 + 연산과 같이 다음과 같이 써야 한다

<span th:text="'hello ' + ${user.username}"></span>

 

이렇게 하면 불편하다.. 그래서 나온 게 Literal Substitution 문법이다 |.... |

<span th:text="|hello ${user.username}|"></span>

 

 

 

반복

th:each`

<tr th:each="user : ${users}">`

 

반복 문법 사용 시 오른쪽 컬렉션 (${users})의 값을 하나씩 꺼내서 왼쪽 변수 (user)에 담아서 태그를 반복 실행 한다

여기에 다양한 기능을 제공하는데 참고만 하고 나중에 사용할 일 있으면 매뉴얼을 보고 사용해 보자

참고
  • index : 0부터 시작하는 값
  • count : 1부터 시작하는 값
  • size : 전체 사이즈
  • even, odd : 홀수, 짝수 여부 (boolean)
  • first , last : 처음 마지막 여부(boolean)
  • current : 현재 객체

 

IF

th:if

 

if 안에 조건을 만족하면 해당 th:if 가 선언된 태그가 보이고

if 안에 조건을 만족하지 못하면 해당 태그 자체가 랜더링이 안된다 (사라진다)

 

블록

반복문을 할 때 태그마다 th:each를 걸어주면 너무 불편하다 그래서 반복할 대상을 하나의 Block으로 잡아주는 태그가 존재한다

<th:block th:each="user : ${users}">
	    <div>
        	사용자 이름1 <span th:text="${user.username}"></span>
			사용자 나이1 <span th:text="${user.age}"></span>
    	</div>
    	<div>
			요약 <span th:text="${user.username} + ' / ' + ${user.age}"></span>
    	</div>
</th:block>

 

th:block 은 유일하게 HTML 태그가 아닌 타임리프가 제공하는 유일한 자체 태그다

 

템플릿 조각(fragment)

웹 페이지를 개발할 때는 공통 영역이 많이 있다.

예를 들어서 상단 영역이나 하단 영역, 좌측 카테고리 등등 여러 페이지에서 함께 사용하는 영역들이 있다.

이런 부분을 코드를 복사해서 사용한다면 변경 시 여러 페이지를 다 수정해야 하므로 상당히 비효율 적이다.

타임리프는 이런 문제를 해결하기 위해 템플릿 조각과 레이아웃 기능을 지원한다

 

템플릿 조각 fragment를 가져오는 방법은 2가지가 있다

 

1. insert

<div th:insert="~{template/fragment/footer :: copy}"></div>

이렇게 가져오면 <div> </div> 태그 안쪽에 가져온 <footer></footer> 태그가 들어가게 된다

최종 모습은 다음과 같다

<div><footer>푸터 자리 입니다.</footer></div>

 

 

2. repalce

반면 Replace는 태그 안쪽에 껴 넣는 게 아니라 가져온 자리를 그냥 대체해 보인다 

fragment를 가져올 때

<div th:replace="~{template/fragment/footer :: copy}"></div>

 

이렇게 가져오면 최종적인 모습은 다음과 같이

<div></div> 태그 자체를 교체해 버린다

 

 

 

템플릿 레이아웃

이전에 Fragment는 전체 모양(구조)은 내가 가지고 있고 Fragment를 통해 코드 조각을 가져다 사용하기 위함이었다면

템플릿 레이아웃은 반대로 전체 모양(구조)은 외부에 정해져 있고 그 모양에 맞춰서 내 코드를 껴 넣는 것!

 

 

타임 리프-스프링 통합

th:object

<form action="item.html" th:action th:object="${item}" method="post">

해당 Form 태그에서 사용할 바인딩 객체를 지정한다

해당 form에서 th:object를 item으로 잡았으므로 이렇게 선언한 form 안에서 다음과 같은 의미가 생긴다

*{itemName} = ${item.itemName}

 

 

th:field

th:field는 정말 많은 일은 한다 여기서는 그것의 일부만을 설명하겠지만 이후에 등장하는 th:field는 정말 많은 일을 해준다

여기서는 간단하게 다음과 같은 일을 한다고만 알고 있고 추후 알아보자

th:field="*{price}"
  1. name 자동생성
  2. id 자동생성
  3. value 자동출력 
  4. 검증 오류시 사용자 입력값 복원(reject value)
  5. 타입별로 다르게 동작
    1. text → value 세팅
    2. checkbox → checked 자동처리, 히든 필드 자동 생성
    3. radio → 선택 상태 자동 처리
    4. select → 선택값 자동 selected 처리

 

 

체크 박스 

 

HTML Checkbox의 문제점 

  • 체크 시 → open=on 전송됨
  • 미체크 시 → 값 자체가 전송 안됨 (null)

HTML의 false 가 아니라 필드 자체가 그냥 사라진다

이를 위해 모든 요청을 항상 보내는 Hidden Field를 이용한다

<input type="hidden" name="_open" value="on">

 

만약 체크 박스에서 체크를 안 하면? open : "on"과  _open : "on" 둘 다 보내짐

체크 박스에서 체크를 하면?항상 보내지는 숨어 있는 태그 _open : "on"만 보내짐 

 

_open:on 만 보내지면?  Spring이 해당 값을 false로 바꾸고

open:on,_open:on 둘 다 보내지면?  on을 true로 바꾸면 된다 

 

하지만 이 모든 일을 타임 리프의 th:field 가 해결해 준다

<input type="checkbox" th:field="*{open}">

 

체크박스 - 멀티

멀티 박스에서 th:field를 사용하면 checked와 히든 필드를 자동 생성 해주는 기능을 가진다

 <div>등록 지역</div>
    <div th:each="region : ${regions}" class="form-check form-check-inline">
        <input type="checkbox" th:field="${item.regions}" th:value="$
        		{region.key}" class="form-check-input" disabled>
        <label th:for="${#ids.prev('regions')}"
				th:text="${region.value}" class="form-check-label">서울</label>
    </div>

 

th:field가 th:value 값과 th:field 값을 하나하나 비교해서 checked 상태를 설정해 준다

 

라디오 버튼

<div th:each="type : ${itemTypes}">
    <input type="radio"
           th:field="*{itemType}"
           th:value="${type.name()}">

    <label th:for="${#ids.prev('itemType')}"
           th:text="${type.description}">

여기도 th:value 값과 th:field 값을 비교해서 선택 여부를 자동으로 처리해 준다 

 

 

'🌿 스프링 > 스프링 MVC 2편' 카테고리의 다른 글

서블릿 예외 처리와 오류 페이지  (0) 2026.02.15
서블릿 필터와 스블릿 인터셉터  (0) 2026.02.15
로그인 처리- 쿠키,세션  (0) 2026.02.14
Validation  (0) 2026.02.14
메시지 , 국제화 기능  (0) 2026.02.14