on my way

코드로 배우는 스프링 부트 웹 프로젝트 03 : 스프링 MVC와 Thymeleaf 본문

Computer Science/Spring

코드로 배우는 스프링 부트 웹 프로젝트 03 : 스프링 MVC와 Thymeleaf

wingbeat 2024. 7. 31. 17:18
반응형

Spring Boot와 Thymeleaf를 이용한 화면 구성

이번 포스팅에서는 Spring Boot와 Thymeleaf를 사용하여 웹 애플리케이션의 화면을 구성하는 방법에 대해 알아보겠다. Thymeleaf는 JSP를 대체하는 템플릿 엔진으로, 화면을 구성하는 데 편리한 다양한 기능을 제공한다.

Thymeleaf란?

Thymeleaf는 서버 사이드 자바 템플릿 엔진으로, HTML, XML, JavaScript, CSS 등을 처리할 수 있는 템플릿을 제공한다. 주로 Spring Framework와 함께 사용되며, 자연스러운 HTML을 유지하면서 동적인 웹 페이지를 쉽게 생성할 수 있게 해준다.

특징:

  • HTML 템플릿: HTML 파일을 그대로 사용하며, Thymeleaf 속성을 추가하여 동적인 콘텐츠를 생성한다.
  • 표현식 사용: ${} 표기법을 사용하여 모델 데이터에 접근할 수 있다.
  • 서버 사이드 처리: 서버에서 데이터를 처리하여 클라이언트에 동적 HTML을 전달한다.

Thymeleaf를 선택한 이유

여러 템플릿 엔진 중에서 Thymeleaf를 선택한 이유는 다음과 같다:

  • 표현식의 유사성: JSP와 유사하게 ${} 표현식을 별도의 처리 없이 사용할 수 있다.
  • JavaScript와의 호환성: Model에 담긴 객체를 화면에서 JavaScript로 처리하기 편리하다.
  • 추가 개발 필요 없음: 연산이나 포맷과 관련된 기능을 추가적인 개발 없이 지원한다.
  • 확장자 사용 없음: .html 파일로 생성하는 데 문제가 없고 별도의 확장자를 이용하지 않는다.

학습 내용

이번 장에서 학습할 내용은 다음과 같다:

  • Thymeleaf를 이용한 화면 출력과 반복, 제어 처리
  • 기본 객체를 이용한 날짜 시간 처리
  • 레이아웃 기능을 활용한 템플릿 구성

프로젝트 생성

Thymeleaf를 사용하기 위해 Spring Boot 프로젝트를 생성할 때, Spring Web과 Thymeleaf 의존성을 추가한다.

설정 파일 수정

Thymeleaf는 기본적으로 서버에서 결과를 만들어 브라우저로 전송한다.

변경 후 결과를 보관하지 않도록 application.properties 파일에 다음과 같이 설정한다.

spring.thymeleaf.cache=false

컨트롤러 생성

프로젝트 내에 controller 패키지를 생성하고, 예제를 위한 SampleController를 추가한다.

package org.zerock.ex3.controller;

import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/sample")
@Log4j2
public class SampleController {
    @GetMapping("/ex1")
    public void ex1() {
        log.info("ex1.............................");
    }
}

SampleController에는 동작을 확인하기 위해 @Log4j2를 적용했다.

@Log4j2는 Lombok의 기능으로, 스프링 부트가 로그 라이브러리 중 Log4j2를 기본으로 사용하기 때문에 별도의 설정 없이 적용할 수 있다.

 

HTML 파일 추가

templates 폴더 내에 sample 폴더를 생성하고, ex1.html 파일을 추가한다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1 th:text="${'Hello World'}"></h1>
</body>
</html>

프로젝트를 실행하고 브라우저에서 /sample/ex1을 확인하면 "Hello World"가 출력된다.

 

Thymeleaf의 기본 사용법

Thymeleaf는 JSP와 유사하게 사용할 수 있으며, HTML 구조를 그대로 유지하면서 필요한 동작이나 값을 추가하는 방식이다.

예제를 통해 제어문과 루프, 삼항 연산자 등의 표현식을 알아보겠다.

 

DTO 클래스 추가

dto 패키지를 생성하고, SampleDTO 클래스를 추가한다.

package org.zerock.ex3.dto;

import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@Builder(toBuilder = true)
public class SampleDTO {
    private Long sno;
    private String first;
    private String last;
    private LocalDateTime regTime;
}

DTO란?

DTO(Data Transfer Object)는 계층 간 데이터 전송을 위한 객체이다.

주로 데이터베이스에서 조회한 데이터를 서비스 계층에서 컨트롤러로 전달하거나, 사용자로부터 입력받은 데이터를 컨트롤러에서 서비스 계층으로 전달할 때 사용된다.

특징:

  • 단순 객체: 필드와 getter/setter 메서드만 가진 단순한 객체이다.
  • 전송 용도: 데이터 전송을 목적으로 사용되며, 비즈니스 로직을 포함하지 않는다.

Builder란?

Builder 패턴은 객체 생성 시 생성자나 정적 팩토리 메서드에 너무 많은 매개변수를 넘기는 것을 피하기 위해 사용되는 패턴이다.

Lombok 라이브러리는 @Builder 애노테이션을 제공하여 Builder 패턴을 쉽게 구현할 수 있게 해준다.

특징:

  • 가독성 향상: 가독성 높은 코드로 객체를 생성할 수 있다.
  • 유연성 제공: 필요한 필드만 설정하여 객체를 생성할 수 있다.

 

컨트롤러 수정

SampleController에서 SampleDTO 객체를 Model에 추가하여 전달한다.

package org.zerock.ex3.controller;

import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Controller
@RequestMapping("/sample")
@Log4j2
public class SampleController {
    @GetMapping("/ex2")
    public void exModel(Model model) {
        List<SampleDTO> list = IntStream.rangeClosed(1, 20).asLongStream()
                .mapToObj(i -> SampleDTO.builder()
                        .sno(i)
                        .first("First.." + i)
                        .last("Last.." + i)
                        .regTime(LocalDateTime.now())
                        .build())
                .collect(Collectors.toList());
        model.addAttribute("list", list);
    }
}

이 메서드는 SampleDTO 타입의 객체 20개를 추가하고, 이를 Model에 담아 전달한다.

model.addAttribute란?

model.addAttribute는 Spring MVC에서 컨트롤러가 뷰로 데이터를 전달하기 위해 사용하는 메서드이다.

Model 객체에 데이터를 추가하면, 해당 데이터는 뷰에서 접근할 수 있게 된다.

특징:

  • 데이터 전달: 컨트롤러에서 생성한 데이터를 뷰에 전달한다.
  • 키-값 쌍: 데이터를 키-값 쌍으로 저장한다.

IntStream이란?

IntStream은 Java 8에서 도입된 스트림 API의 한 종류로, 원시형 int 값의 스트림을 다룰 수 있게 해준다.

스트림 API는 함수형 프로그래밍을 지원하며, 컬렉션 데이터를 처리하는 데 유용하다.

특징:

  • 연산: 중간 연산(map, filter 등)과 최종 연산(forEach, collect 등)을 사용하여 데이터를 처리할 수 있다.
  • 병렬 처리: 병렬 스트림을 사용하여 멀티스레드 환경에서 데이터 처리 성능을 향상시킬 수 있다.

 

페이지네이션과 정렬

Thymeleaf는 페이징과 정렬을 쉽게 처리할 수 있다.

다음은 MemoRepository와 이를 테스트하는 메서드이다.

package org.zerock.ex2.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.zerock.ex2.entity.Memo;

import java.util.List;

public interface MemoRepository extends JpaRepository<Memo, Long> {
    Memo findByMemoText(String memoText);
    List<Memo> findByMemoTextContaining(String memoText);
    List<Memo> findByMemoTextContainingOrMemoTextContaining(String memoText1, String memoText2);
    Page<Memo> findByMnoBetween(Long from, Long to, Pageable pageable);
    void deleteByMnoLessThan(Long mno);
}

 

JPA란?

JPA(Java Persistence API)는 자바 애플리케이션에서 관계형 데이터베이스를 관리하는 데 사용되는 자바 표준 API이다.

JPA는 데이터베이스와 객체 지향 프로그래밍 간의 간극을 줄여주며, Hibernate와 같은 ORM 프레임워크를 통해 구현된다.

 

특징:

  • ORM 지원: 객체와 관계형 데이터베이스 간의 매핑을 지원한다.
  • 쿼리 작성: JPQL(Java Persistence Query Language)을 사용하여 데이터베이스 쿼리를 작성할 수 있다.
  • 트랜잭션 관리: 데이터베이스 트랜잭션을 쉽게 관리할 수 있다.

 

테스트 메서드: testQueryMethodWithPageable()

@Test
public void testQueryMethodWithPageable() {
    Pageable pageable = PageRequest.of(1, 10, Sort.by("mno").descending());
    Page<Memo> result = memoRepository.findByMnoBetween(10L, 50L, pageable);
    result.get().forEach(m -> System.out.println(m));
}

이 메서드는 특정 범위의 Memo 엔티티를 페이징 처리하여 조회하고 결과를 출력한다.

PageRequest.of(1, 10, Sort.by("mno").descending())를 사용하여 두 번째 페이지(0부터 시작), 한 페이지당 10개의 데이터를 mno 기준 내림차순으로 조회한다.

 

지금까지 Spring Boot와 Thymeleaf를 사용하여 웹 애플리케이션의 화면을 구성하는 방법을 배웠다.

Thymeleaf의 다양한 기능을 활용하여 HTML 구조를 유지하면서 동적인 콘텐츠를 쉽게 추가할 수 있다.


Thymeleaf를 이용한 반복문과 제어문 처리

Thymeleaf를 이용하여 HTML에서 반복문과 제어문을 처리하는 방법을 소개한다.

Thymeleaf는 직관적인 문법과 다양한 기능을 제공하여, 웹 페이지의 동적 콘텐츠를 쉽게 생성할 수 있다.

1. 반복문 처리

Thymeleaf에서 반복문을 처리하기 위해 th:each 속성을 사용한다.

이 속성은 HTML 요소를 반복하여 렌더링할 수 있게 해준다.

예제: 반복문 처리

templates/sample 폴더에 ex2.html 파일을 추가한다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        <li th:each="dto : ${list}">[[${dto}]]</li>
    </ul>
</body>
</html>
  • th:each="dto : ${list}": list라는 모델 데이터를 반복하여 dto 변수에 할당하고, 각 dto<li> 태그 안에 출력한다.
  • [[${dto}]]: 인라인 표현식으로, dto 객체를 문자열로 출력한다.

실행 결과

브라우저에서 /sample/ex2 URL을 확인하면, SampleDTO 객체 목록이 출력된다.

2. 반복문의 상태 객체

반복문에서는 상태(state) 객체를 이용하여 순번이나 인덱스 번호, 홀수/짝수 등을 지정할 수 있다.

예제: 상태 객체 사용

ex2.html 파일을 다음과 같이 수정한다.

<ul>
    <li th:each="dto, state : ${list}">[[${state.index}]] --- [[${dto}]]</li>
</ul>
  • state: 반복문의 상태를 나타내는 객체로, indexcount 속성을 사용할 수 있다.
  • state.index: 0부터 시작하는 인덱스 번호를 반환한다.

실행 결과

브라우저에서 /sample/ex2 URL을 확인하면, 각 SampleDTO 객체의 인덱스 번호가 함께 출력된다.

3. 제어문 처리

Thymeleaf에서는 th:if, th:unless 속성이나 삼항 연산자를 이용하여 제어문을 처리할 수 있다.

예제: th:if 속성 사용

ex2.html 파일의 일부를 다음과 같이 수정한다.

<ul>
    <li th:each="dto, state : ${list}" th:if="${dto.sno % 5 == 0}">[[${dto}]]</li>
</ul>
  • th:if="${dto.sno % 5 == 0}": dto.sno가 5의 배수인 경우에만 해당 <li> 태그를 렌더링한다.

실행 결과

브라우저에서 /sample/ex2 URL을 확인하면, sno가 5의 배수인 SampleDTO 객체만 출력된다.

예제: 삼항 연산자 사용

삼항 연산자를 사용하여 조건에 따라 다른 값을 출력할 수 있다.

<ul>
    <li th:each="dto, state : ${list}" th:text="${dto.sno % 5 == 0} ? ${dto.sno} : ${dto.first}"></li>
</ul>
  • th:text="${dto.sno % 5 == 0} ? ${dto.sno} : ${dto.first}": dto.sno가 5의 배수이면 dto.sno를 출력하고, 그렇지 않으면 dto.first를 출력한다.

실행 결과

브라우저에서 /sample/ex2 URL을 확인하면, sno가 5의 배수인 경우에는 sno를, 그렇지 않은 경우에는 first를 출력한다.

4. CSS 클래스 적용

특정 조건에 따라 CSS 클래스를 적용할 수 있다.

<style>
    .target {
        background-color: red;
    }
</style>
<ul>
    <li th:each="dto, state : ${list}" th:class="${dto.sno % 5 == 0} ? 'target' : ''" th:text="${dto}"></li>
</ul>
  • th:class="${dto.sno % 5 == 0} ? 'target' : ''": dto.sno가 5의 배수이면 target 클래스를 적용하고, 그렇지 않으면 클래스를 적용하지 않는다.

실행 결과

브라우저에서 /sample/ex2 URL을 확인하면, sno가 5의 배수인 경우에만 빨간색 배경이 적용된 것을 볼 수 있다.

5. Inline 속성

th:inline="javascript" 속성을 사용하여 JavaScript 코드에서 Thymeleaf 표현식을 사용할 수 있다.

예제: JavaScript에서 Thymeleaf 사용

SampleController에 다음 코드를 추가한다.

@GetMapping("/exInline")
public String exInline(RedirectAttributes redirectAttributes) {
    SampleDTO dto = SampleDTO.builder()
            .sno(100L)
            .first("First..100")
            .last("Last..100")
            .regTime(LocalDateTime.now())
            .build();
    redirectAttributes.addFlashAttribute("result", "success");
    redirectAttributes.addFlashAttribute("dto", dto);
    return "redirect:/sample/ex3";
}

ex3.html 파일을 다음과 같이 작성한다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1 th:text="${result}"></h1>
    <h1 th:text="${dto}"></h1>
    <script th:inline="javascript">
        var msg = [[${result}]];
        var dto = [[${dto}]];
    </script>
</body>
</html>
  • th:inline="javascript": Thymeleaf 표현식을 JavaScript 코드에서 사용할 수 있게 해준다.
  • [[${result}]]: JavaScript 코드에서 Thymeleaf 표현식을 사용하여 result 값을 변수에 할당한다.

실행 결과

브라우저에서 /sample/exInline URL을 확인하면, JavaScript 코드에서 resultdto 값이 출력된다.

 

6. th:block

th:block은 HTML 태그 없이 Thymeleaf 속성을 사용할 수 있게 해주는 유용한 기능이다.

예제: th:block 사용

<ul>
    <th:block th:each="dto : ${list}">
        <li th:text="${dto.sno % 5 == 0} ? ${dto.sno} : ${dto.first}"></li>
    </th:block>
</ul>
  • th:block: 실제 HTML 태그로 렌더링되지 않으므로, 반복문 등에서 유용하게 사용할 수 있다.

실행 결과

브라우저에서 /sample/ex2 URL을 확인하면, th:block을 사용한 반복문이 정상적으로 작동하는 것을 볼 수 있다.


Thymeleaf에서 링크 처리 및 기본 객체 사용

지금부터 Thymeleaf를 사용하여 링크를 처리하는 방법과 기본 객체를 이용하는 방법에 대해 알아보겠습니다.

이를 통해 웹 페이지의 동적 콘텐츠를 쉽게 관리할 수 있습니다.

1. 링크 처리

Thymeleaf에서는 @{} 구문을 사용하여 링크를 처리합니다.

특히 파라미터를 전달해야 하는 상황에서 가독성 좋은 코드를 작성할 수 있습니다. 아래는 예제 코드입니다.

SampleController 클래스 일부 수정

기존의 SampleControllerexModel() 메서드를 재사용하여 아래와 같이 수정합니다.

@GetMapping({"/ex2", "/exLink"})
public void exModel(Model model) {
    List<SampleDTO> list = IntStream.rangeClosed(1, 20).asLongStream()
            .mapToObj(i -> {
                SampleDTO dto = SampleDTO.builder()
                        .sno(i)
                        .first("First.." + i)
                        .last("Last.." + i)
                        .regTime(LocalDateTime.now())
                        .build();
                return dto;
            }).collect(Collectors.toList());
    model.addAttribute("list", list);
}

@GetMapping에는 배열을 이용해 하나 이상의 URL을 처리할 수 있도록 했습니다.

/sample/exLink 경로를 처리할 수 있는 구조로 변경했습니다.

 

exLink.html 파일 작성

templates/sample 폴더에 exLink.html 파일을 다음과 같이 작성합니다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <ul>
        <li th:each="dto : ${list}">
            <a th:href="@{/sample/exView}">[[${dto}]]</a>
        </li>
    </ul>
</body>
</html>

실행 결과

브라우저에서 /sample/exLink URL을 확인하면, SampleDTO 객체 목록이 링크로 출력됩니다.

 

2. 파라미터 추가

링크에 파라미터를 추가하기 위해 @{} 구문에서 ()를 사용하여 파라미터의 이름과 값을 지정합니다.

예제: 파라미터 추가

exLink.html 파일의 내용을 다음과 같이 수정합니다.

<ul>
    <li th:each="dto : ${list}">
        <a th:href="@{/sample/exView(sno=${dto.sno})}">[[${dto}]]</a>
    </li>
</ul>

실행 결과

브라우저에서 생성된 링크는 다음과 같이 구성됩니다.

<a href="/sample/exView?sno=1">SampleDTO(sno=1, ...)</a>

 

3. 경로 파라미터

파라미터를 URL 경로에 포함시킬 수도 있습니다.

예제: 경로 파라미터 추가

exLink.html 파일의 내용을 다음과 같이 수정합니다.

<ul>
    <li th:each="dto : ${list}">
        <a th:href="@{/sample/exView/{sno}(sno=${dto.sno})}">[[${dto}]]</a>
    </li>
</ul>

실행 결과

브라우저에서 생성된 링크는 다음과 같이 구성됩니다.

<a href="/sample/exView/1">SampleDTO(sno=1, ...)</a>

 

4. Thymeleaf의 기본 객체와 LocalDateTime

Thymeleaf는 여러 종류의 기본 객체를 지원합니다. 기본 객체는 문자열, 숫자, 날짜 등을 쉽게 처리할 수 있도록 도와줍니다.

예제: 숫자 포맷팅

sno를 5자리로 포맷팅하여 출력합니다.

<ul>
    <li th:each="dto : ${list}">[[${#numbers.formatInteger(dto.sno, 5)}]]</li>
</ul>

실행 결과

숫자가 5자리로 포맷팅되어 출력됩니다.

 

예제: LocalDateTime 포맷팅

Java 8의 LocalDateTime을 처리하기 위해 추가 라이브러리를 사용합니다.

build.gradle 파일에 의존성을 추가합니다.

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    compile group: 'org.thymeleaf.extras', name: 'thymeleaf-extras-java8time'
}

exLink.html 파일의 내용을 다음과 같이 수정합니다.

<ul>
    <li th:each="dto : ${list}">[[${dto.sno}]] - [[${#temporals.format(dto.regTime, 'yyyy/MM/dd')}]]</li>
</ul>

실행 결과

브라우저에서 LocalDateTime이 포맷팅되어 출력됩니다.

지금까지 Thymeleaf를 이용한 링크 처리 및 기본 객체 사용 방법에 대해 알아보았습니다.

이를 통해 웹 페이지의 동적 콘텐츠를 더욱 효율적으로 관리할 수 있습니다.  

 

 


\

그림 해석: Thymeleaf의 링크 처리와 기본 객체 사용 결과

1. 링크 처리 결과

  • 설명: 이 그림은 브라우저에서 /sample/exLink URL을 통해 접근했을 때, SampleDTO 객체들이 링크로 출력된 결과를 보여줍니다.
  • 링크 구조: 각 링크는 SampleDTO 객체의 sno, first, last, regTime 속성 값을 포함합니다.
  • 링크 예시:
    • SampleDTO(sno=1, first=First..1, last=Last..1, regTime=2020-10-08T11:25:13.451590100)
    • SampleDTO(sno=2, first=First..2, last=Last..2, regTime=2020-10-08T11:25:13.451590100)
  • 링크 생성: 각 SampleDTO 객체는 <a> 태그로 감싸져 있으며, href 속성은 /sample/exView로 설정되어 있습니다.

2. 경로 파라미터 추가 결과

  • 설명: 이 그림은 경로 파라미터를 추가한 링크를 보여줍니다.
  • 파라미터 구조: 각 링크는 SampleDTO 객체의 sno 값을 경로 파라미터로 포함합니다.
  • 파라미터 예시:
    • <a href="/sample/exView/1">SampleDTO(sno=1, first=First..1, last=Last..1, regTime=2020-10-08T11:25:13.451590100)</a>
    • <a href="/sample/exView/2">SampleDTO(sno=2, first=First..2, last=Last..2, regTime=2020-10-08T11:25:13.451590100)</a>
  • 경로 파라미터: 링크는 href 속성에 sno 값을 포함하여 /sample/exView/{sno} 형태로 생성됩니다.

3. 숫자 포맷팅 결과

  • 설명: 이 그림은 SampleDTO 객체의 sno 값을 5자리로 포맷팅한 결과를 보여줍니다.
  • 숫자 포맷팅: #numbers.formatInteger 메서드를 사용하여 sno 값을 5자리로 포맷팅합니다.
  • 포맷팅 예시:
    • 00001
    • 00002
    • 00003

4. LocalDateTime 포맷팅 결과

  • 설명: 이 그림은 SampleDTO 객체의 regTime 값을 yyyy/MM/dd 형식으로 포맷팅한 결과를 보여줍니다.
  • 날짜 포맷팅: #temporals.format 메서드를 사용하여 regTime 값을 포맷팅합니다.
  • 포맷팅 예시:
    • 2020/10/08
    • 2020/10/08
    • 2020/10/08

요약

  • 링크 처리: Thymeleaf의 @{} 구문을 사용하여 SampleDTO 객체를 링크로 출력하고, 경로 파라미터를 포함시킬 수 있습니다.
  • 기본 객체 사용: #numbers#temporals와 같은 기본 객체를 사용하여 숫자와 날짜를 손쉽게 포맷팅할 수 있습니다.
  • 결과 확인: 브라우저에서 각 결과를 확인하여 동적으로 생성된 링크와 포맷팅된 데이터를 확인할 수 있습니다.

이 그림들은 Thymeleaf의 다양한 기능을 활용하여 동적인 웹 페이지를 쉽게 생성하고 관리할 수 있음을 보여줍니다.  


3.4 Thymeleaf의 레이아웃 처리 방법

Thymeleaf는 JSP의 include와 유사한 기능을 제공하며, 파라미터를 전달하여 내용을 포함하는 형태로 사용할 수 있습니다.

1. 레이아웃 처리 방식

Thymeleaf의 레이아웃 기능은 크게 두 가지 형태로 사용할 수 있습니다:

  1. 특정 부분을 외부 혹은 내부에서 가져와서 포함하는 형태 (th:insert, th:replace)
  2. 특정한 부분을 파라미터로 전달하여 내용에 포함하는 형태

1.1 Include 방식의 처리

Thymeleaf에서는 th:insertth:replace라는 기능을 사용하여 특정 부분을 다른 내용으로 변경할 수 있습니다. th:replace는 기존의 내용을 완전히 대체하는 방식이고, th:insert는 기존 내용의 바깥쪽 태그는 그대로 유지하면서 추가되는 방식입니다.

SampleController에 메서드 추가

@GetMapping("/exLayout1")
public void exLayout1() {
    log.info("exLayout1...");
}

fragments 폴더와 fragment1.html 파일 작성

templates/fragments 폴더에 fragment1.html 파일을 추가합니다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div th:fragment="part1">
        <h2>Part 1</h2>
    </div>
    <div th:fragment="part2">
        <h2>Part 2</h2>
    </div>
    <div th:fragment="part3">
        <h2>Part 3</h2>
    </div>
</body>
</html>

 

exLayout1.html 파일 작성

templates/sample 폴더에 exLayout1.html 파일을 추가합니다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>Fragment Test</h1>
    <h2>Layout 1 - 1</h2>
    <div th:replace="fragments/fragment1 :: part1"></div>
    <h2>Layout 1 - 2</h2>
    <div th:insert="fragments/fragment1 :: part2"></div>
    <h2>Layout 1 - 3</h2>
    <th:block th:replace="fragments/fragment1 :: part3"></th:block>
</body>
</html>

 

실행 결과

브라우저에서 /sample/exLayout1 URL을 확인하면, 다음과 같은 결과를 볼 수 있습니다.

th:insert를 이용하는 경우 <div> 태그 내에 다시 <div> 태그가 생성된 것을 확인할 수 있습니다.

th:replace를 이용하면 <div> 태그가 완전히 대체됩니다.

 

1.2 파일 전체를 사용하는 예제

fragment2.html 파일 작성

templates/fragments 폴더에 fragment2.html 파일을 추가합니다.

<div>
    <hr/>
    <h2>Fragment2 File</h2>
    <h2>Fragment2 File</h2>
    <h2>Fragment2 File</h2>
    <hr/>
</div>

 

exLayout1.html 파일 수정

fragment2.html 전체를 가져오는 부분을 추가합니다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>Fragment Test</h1>
    <div style="border: 1px solid blue;">
        <th:block th:replace="fragments/fragment2"></th:block>
    </div>
    <h2>Layout 1 - 1</h2>
    <div th:replace="fragments/fragment1 :: part1"></div>
    <h2>Layout 1 - 2</h2>
    <div th:replace="fragments/fragment1 :: part2"></div>
    <h2>Layout 1 - 3</h2>
    <th:block th:replace="fragments/fragment1 :: part3"></th:block>
</body>
</html>

실행 결과

브라우저에서 /sample/exLayout1 URL을 확인하면, fragment2.html의 내용 전체가 포함된 것을 확인할 수 있습니다.

 

2. 파라미터 방식의 처리

Thymeleaf를 이용하면 특정한 태그를 파라미터처럼 전달하여 다른 th:fragment에서 사용할 수 있습니다.

SampleController 수정

@GetMapping({"/exLayout1", "/exLayout2"})
public void exLayout1() {
    log.info("exLayout...");
}

fragment3.html 파일 작성

templates/fragments 폴더에 fragment3.html 파일을 추가합니다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div th:fragment="target(first, second)">
        <style>
            .c1 { background-color: red; }
            .c2 { background-color: blue; }
        </style>
        <div class="c1">
            <th:block th:replace="${first}"></th:block>
        </div>
        <div class="c2">
            <th:block th:replace="${second}"></th:block>
        </div>
    </div>
</body>
</html>

exLayout2.html 파일 작성

templates/sample 폴더에 exLayout2.html 파일을 추가합니다.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <th:block th:replace="fragments/fragment3 :: target(this :: #ulFirst, this :: #ulSecond)">
        <ul id="ulFirst">
            <li>AAA</li>
            <li>BBB</li>
            <li>CCC</li>
        </ul>
        <ul id="ulSecond">
            <li>111</li>
            <li>222</li>
            <li>333</li>
        </ul>
    </th:block>
</body>
</html>

실행 결과

브라우저에서 /sample/exLayout2 URL을 확인하면, fragment3.html의 내용에 exLayout2.html에서 전달된 태그들이 포함되어 출력된 것을 확인할 수 있습니다.

요약

  • Thymeleaf의 레이아웃 처리: th:insertth:replace를 이용하여 HTML 조각을 포함할 수 있습니다.
  • 파라미터 전달: 특정한 태그를 파라미터로 전달하여 다른 th:fragment에서 사용할 수 있습니다.
  • 레이아웃 관리: 이러한 기능을 활용하여 HTML 레이아웃을 효율적으로 관리할 수 있습니다.

지금까지 Thymeleaf의 레이아웃 처리 방법에 대해 알아보았습니다.

이를 통해 웹 페이지의 레이아웃을 더 효율적으로 관리하고, 재사용 가능한 HTML 조각을 작성할 수 있습니다.

앞으로도 다양한 예제를 통해 Thymeleaf의 강력한 기능을 더 많이 익혀보겠습니다.

 

Thymeleaf로 레이아웃 템플릿 만들기

Thymeleaf를 사용하면 파라미터로 필요한 영역을 전달하여 레이아웃 전체를 하나의 페이지로 구성할 수 있습니다.

이를 통해 공통의 레이아웃을 쉽게 관리할 수 있습니다.

이번 포스팅에서는 이러한 구조를 실습하고, 이후 예제에서 사용하는 레이아웃을 완성해 보겠습니다.

1. 레이아웃 템플릿 만들기

1.1 레이아웃 파일 작성

먼저, templates 폴더 내에 layout 폴더를 생성하고 layout1.html 파일을 작성합니다.

src
└── main
    └── resources
        └── templates
            └── layout
                └── layout1.html

layout1.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <style>
        * { margin: 0; padding: 0; }
        .header { width:100vw; height: 20vh; background-color: aqua; }
        .content { width: 100vw; height: 70vh; background-color: lightgray; }
        .footer { width: 100vw; height: 10vh; background-color: green; }
    </style>
    <div class="header"><h1>HEADER</h1></div>
    <div class="content"><h1>CONTENT</h1></div>
    <div class="footer"><h1>FOOTER</h1></div>
</body>
</html>

결과

layout1.html은 간단한 스타일을 사용하여 색상과 크기를 지정했습니다.

브라우저에서 확인하면 다음과 같은 모습으로 출력됩니다:

1.2 템플릿 파일 수정

실제 개발에서는 중간의 CONTENT 영역이 다른 내용으로 변경되어야 합니다.

이를 위해 layout1.html을 템플릿으로 만들고, 중간의 CONTENT 영역을 변경 가능하도록 수정합니다.

layout1.html 수정

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:fragment="setContent(content)">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <style>
        * { margin: 0; padding: 0; }
        .header { width:100vw; height: 20vh; background-color: aqua; }
        .content { width: 100vw; height: 70vh; background-color: lightgray; }
        .footer { width: 100vw; height: 10vh; background-color: green; }
    </style>
    <div class="header"><h1>HEADER</h1></div>
    <div class="content">
        <th:block th:replace="${content}"></th:block>
    </div>
    <div class="footer"><h1>FOOTER</h1></div>
</body>
</th:block>
</html>

이제 상단에는 th:fragmentsetContent를 지정하고 content라는 파라미터를 받을 수 있도록 했습니다.

중간 부분은 th:replace를 이용하여 파라미터로 전달되는 content를 출력하도록 수정했습니다.

 

1.3 컨트롤러 수정

SampleController/exTemplate 경로를 처리하는 코드를 작성합니다.

@GetMapping({"/exLayout1", "/exLayout2", "/exTemplate"})
public void exLayout1() {
    log.info("exLayout...");
}

 

1.4 템플릿 파일 추가

templates/sample 폴더에 exTemplate.html 파일을 추가합니다.

src
└── main
    └── resources
        └── templates
            └── sample
                └── exTemplate.html

exTemplate.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/layout1 :: setContent(~{this::content})}">
    <th:block th:fragment="content">
        <h1>exTemplate Page</h1>
    </th:block>
</th:block>

이 파일은 전체 내용은 layout1.html로 처리하고, 파라미터로 현재 파일의 content 부분만을 전달합니다.

 

1.5 실행 결과

브라우저에서 /sample/exTemplate URL을 확인하면 다음과 같이 처리됩니다:

 

2. 부트스트랩 템플릿 적용하기

이제 부트스트랩 템플릿을 적용하여 좀 더 고급스러운 레이아웃을 만들어보겠습니다.

2.1 부트스트랩 파일 추가

부트스트랩 템플릿 파일을 프로젝트의 static 폴더 내에 복사합니다.

src
└── main
    └── resources
        └── static
            ├── css
            │   └── simple-sidebar.css
            └── vendor
                └── bootstrap
                    ├── css
                    │   └── bootstrap.min.css
                    └── js
                        ├── bootstrap.bundle.min.js
                        └── jquery.min.js

 

2.2 기본 레이아웃 파일 작성

templates/layout 폴더에 basic.html 파일을 추가하고, 부트스트랩 템플릿의 내용을 추가합니다.

basic.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:fragment="setContent(content)">
<head>
    <meta charset="UTF-8">
    <title>Simple Sidebar - Start Bootstrap Template</title>
    <!-- Bootstrap core CSS -->
    <link th:href="@{/vendor/bootstrap/css/bootstrap.min.css}" rel="stylesheet">
    <!-- Custom styles for this template -->
    <link th:href="@{/css/simple-sidebar.css}" rel="stylesheet">
</head>
<body>
    <div class="container-fluid">
        <th:block th:replace="${content}"></th:block>
    </div>
    <!-- Bootstrap core JavaScript -->
    <script th:src="@{/vendor/jquery/jquery.min.js}"></script>
    <script th:src="@{/vendor/bootstrap/js/bootstrap.bundle.min.js}"></script>
</body>
</th:block>
</html>

 

2.3 컨트롤러 수정

SampleController/exSidebar 경로를 추가합니다.

@GetMapping({"/exLayout1", "/exLayout2", "/exTemplate", "/exSidebar"})
public void exLayout1() {
    log.info("exLayout...");
}

 

2.4 예제 페이지 작성

templates/sample 폴더에 exSidebar.html 파일을 추가합니다.

exSidebar.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}">
    <th:block th:fragment="content">
        <h1>exSidebar Page</h1>
    </th:block>
</th:block>

실행 결과

브라우저에서 /sample/exSidebar URL을 확인하면, 레이아웃이 유지된 상태에서 exSidebar.html의 내용이 출력됩니다.

이제 Thymeleaf와 부트스트랩을 이용하여 레이아웃을 효율적으로 관리하고, 공통의 템플릿을 쉽게 적용할 수 있게 되었습니다.  

반응형