사이먼's 코딩노트

[SpringBoot] Thymeleaf / 데이터 전달 및 랜더링 / Redirect 본문

Java/SpringBoot

[SpringBoot] Thymeleaf / 데이터 전달 및 랜더링 / Redirect

simonpark817 2024. 5. 8. 23:22

[Thymeleaf 설치]

  • 스프링 부트에 타임리프를 설치하고 템플릿을 사용하여 페이지를 하나 만들어봅시다.
  • Thymeleaf(타임리프)는 웹 애플리케이션 개발을 위한 서버 측 템플릿 엔진으로서, 여기서 말하는 템플릿은 HTML, CSS, JavaScript, XML 및 일반 텍스트를 의미한다.
  • 이번에는 HTML 템플릿을 생성하여 DB에 담겨진 전체 question 데이터를 가져와 페이지에 보여지도록 합시다.
  • 먼저 타임리프를 설치하기 위해선 build.gradle에 해당 라이브러리를 implementation을 해야한다.
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
  • question 데이터 전달을 위해서 QuestionController.java 클래스를 생성하고, 주소를 매핑하여 올바르게 작동하는 지 테스트를 해 볼 필요가 있다.
  • 아래 코드는 QuestionController.java 클래스에 작성된 코드이다.
package com.sbs.sbb.Question;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class QuestionController {
    @GetMapping("/question/list")
    public String list() {
        return "question_list";
    }
}
  • @Controller 어노테이션은 해당 클래스가 컨트롤러의 역할을 한다는 것을 알려주는 용도이다.
  • @GetMapping을 통해 localhost:8090/question/list의 URL 경로를 매핑한다.
  • 실제로 해당 주소로 접속하게 되면 return 값인 question_list라는 문자열이 출력되는 것을 볼 수 있다.
  • 하지만 보통 브러우저에 응답하는 문자열은 위의 예시처럼 자바 코드에서 직접 만들지 않는다.
  • 아래는 templates라는 패키지를 생성하여 question_list.html 템플릿을 생성해 question 목록 페이지를 만든 모습이다.
<h1 style="color: red;">안녕하세요</h1>
  • 템플릿 명이 question_list.html인 이유는 QuestionController.java 에서 return 값을 question_list로 반환했기 때문에 템플릿 명을 동일하게 맞춰야한다.
  • 다시 로컬 서버를 실행하여 localhost:8090/question/list에 접속하면 아래와 같이 빨간 글의 '안녕하세요'가 출력된다.

타임리프 설치 후 템플릿을 적용한 모습

 

[데이터 전달 및 랜더링]

  • 템플릿이 적용된 모습을 확인했다면, 이번에는 question 목록이 담긴 데이터를 조회하여 이를 템플릿을 통해 화면에 전달해봅시다.
  • question 목록과 관련된 데이터를 조회하기 위해서는 questionRepository를 사용해야 한다.
  • QuestionRepository로 조회한 question 목록 데이터는 Model 클래스를 사용하여 템플릿에 전달할 수 있다.
  • 아래는 수정된 QuestionController.java 클래스이다.
package com.sbs.sbb.Question;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

@Controller
@RequiredArgsConstructor
public class QuestionController {
    private final QuestionRepository questionRepository;

    @GetMapping("/question/list")
    public String list(Model model) {
        List<Question> questionList = this.questionRepository.findAll();
        model.addAttribute("questionList", questionList);

        return "question_list";
    }
}
  • 자바의 MVC를 생각해보면 Controller -> Service -> Repository의 순서가 맞지만, 현재 코드는 Service가 도입되어 있지 않아 임시적으로 Controller에서 바로 Repository로 전달되었다. 반드시 후에 QuestionService 클래스를 생성하여 해당 실행 경로를 수정해야한다.
  • 데이터 조회를 위한 questionRepository 객체를 생성하였고, @Autowired를 대신 @RequiredArgsConstructor 어노테이션을 사용하였다.
  • @RequiredArgsConstructor 어노테이션은 QuestionController 생성자를 통해 QuestionRepository 객체를 생성하는 역할이라고 생각하면 좋다. 이 때 final 속성을 반드시 붙혀줘야 한다.
  • list() 메서드의 매개변수로 Model을 지정하면 Model 객체가 자동으로 생성된다.
  • findAll() 메서드를 통해 question 목록을 조회하고 List 형식의 questionList을 생성하여 해당 데이터를 가져오고 Model 객체에 questionList 라는 이름으로 저장했다.
  • 여기서 Model 객체는 자바 클래스와 템플릿 간의 연결 고리 역할을 한다. Model 객체에 값을 담아두면 템플릿에서 그 값을 바로 사용할 수 있다.

 

[Question 데이터 가져오기]

  • 아래는 이제 question_list.html 템플릿에 해당 데이터들을 가져오기 위해 테이블을 사용하여 코드를 작성한 모습이다.
<table border="1">
    <thead>
        <tr>
            <th>번호</th>
            <th>제목</th>
            <th>작성일시</th>
        </tr>
    </thead>
    <tbody>
        <tr th:each="question : ${questionList}">
            <td>
                <a th:text="${question.id}" th:href="@{|/question/detail/${question.id}|}"></a>
            </td>
            <td>
                <a th:text="${question.subject}" th:href="@{|/question/detail/${question.id}|}"></a>
            </td>
            <td th:text="${question.createDate}"></td>
        </tr>
    </tbody>
</table>
  • 각 속성에 작성된 th: 는 타임리프에서 사용되는 속성임을 나타내는데, 이 부분이 자바 코드와 연결된다.  
  • QuestionController.java 클래스에서 Model 객체에 저장된 이름 questionList를 그대로 가져와 <tr th:each="question : ${questionList}"> 에 적용하였다. 해당 코드는 questionList에 저장된 데이터를 하나씩 꺼내 question 변수에 대입한 후 questionList의 개수만큼 반복하며 문장을 출력하라는 뜻이다.
  • 다시 말해, 몇 개의 question 데이터인지 상관없이 반복문을 통해 question 변수에 담아서 가져올 수 있다는 뜻이다.
  • th:text="${question.id}"은 question 객체의 id를 출력한다.
  • 나머지 question.subject, question.createDate도 모두 같은 의미의 같은 역할을 한다.
  • 이렇게 데이터를 전달하여 템플릿에서 성공적으로 나열하는 것을 랜더링한다고 표현하기도 한다.
  • 다시 로컬 서버를 실행하여 localhost:8090/question/list에 접속하면 아래와 같이 테이블 형식에 맞춰 모든 question 데이터가 출력된다.

question 데이터 랜더링

 

[Redirect]

  • 서버의 URL을 요청할 때 도메인 명 뒤에 아무런 주소도 덧붙이지 않는 URL을 Root URL이라고 하고, Root URL을 요청했을 때 보여지는 페이지를 메인 페이지라고 한다.
  • 현재 진행중인 Sbb 서비스도 question 목록을 메인 페이지로 정하고, Root URL을 요청했을 때 question 목록 화면으로 이동하도록 만들어봅시다.
  • 그리고 위와 같이 사용자가 처음 요청한 URL이 아닌, 다른 URL로 보내는 것을 Redirect(리다이렉트) 라고 한다.
  • 현재 localhost:8090 인 Root URL로 접속하게 되면 스프링 시큐리티를 적용하였기 때문에 로그인 과정은 생략하게 되고 404 에러 페이지가 나타나게 된다.
  • MainController.java 클래스를 아래와 같이 수정하여 404 에러 페이지를 피하고 바로 localhost:8090/question/list 페이지가 보이도록 합시다.
package com.sbs.sbb;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MainController {
    @GetMapping("/sbb")
    @ResponseBody      
    public String index() {
        return "안녕하세요!!!";
    }

    @GetMapping("/")
    public String root() {
        return "redirect:/question/list";
    }
}
  • @GetMapping 어노테이션을 통해 Root URL을 매핑하고 root() 메서드에 return 값을 redirect:/question/list 로 반환하면 우리가 원하는 대로 리다이렉트하여 localhost:8090/question/list 페이지로 바로 이동하게 된다.
  • 위에서 잠깐 언급했지만, HTTP 응답 상태 코드에는 404 외에도 몇 가지가 존재한다.
  • 200번 대는 성공을 의미, 300번 대는 리다이렉트를 의미, 400번 대는 고객의 잘못으로 인한 실패를 의미, 500번 대는 서버의 잘못으로 인한 실패를 의미한다.
  • 응답 코드를 보고 HTTP 동작의 어떤 부분이 문제인지 캐치하는 것도 중요한 디버깅의 시작이 될 수 있다.

 

반응형