김쥬르에 개발일지
Spring Boot 화면구성 본문
오늘은 템플릿엔진을 이용하여 실제 보여질 화면을 구성해보겠습니다.
템플릿엔진?
스프링 서버에서 데이터를 받아 우리가 보는 웹 페이지, 즉 HTML 상에 그 데이터를 넣어 보여주는 도구입니다.
다만 템플릿 엔진을 HTML과 함께 템플릿 엔진을 위한 문법을 섞어 사용해야합니다.
템플릿 엔진도 종류가 여러가지이지만 보편적으로 많이 사용하고 Spring Boot에서
권장하는 Thymeleaf를 사용해보도록 하겠습니다.
간단하게 템플릿 문법을 알아보겠습니다.
(Thymeleaf)
<h1 text = ${이름}>
<p text = ${나이}>
(서버)
{이름 : 홍길동 나이 : 11}
서버에서 이러한 값이 템플릿엔진으로 전달됐다면
<h1 text = 홍길동>
<p text = 11>
이렇게 값이 들어가는것입니다.
서버에서 이름,나이 라는 키로 데이터를
템플릿 엔진에 넘겨주면 템플릿 엔진은 이를 바탕으로 동적으로 값을 적용하여
동적인 웹 페이즈를 만들수 있게 되는것이죠.

템플릿 엔진은 각각 문법이 미묘하게 달라서 템플릿 엔진마다 문법을 새로 배워야합니다.
대부분의 구조는 비슷해서 한 번 배워두면 다른 템플릿 엔진은 금방 익숙하게 다룰수있습니다.
저의 경우 JSP를 주로 다뤄왔었기 때문에 이해하기 쉬웠습니다.
주로 사용하는 타임리프에 표현식과 문법을 알아보겠습니다.
타임리프 표현식
| 표현식 | 설명 |
| ${...} | 변수의값 표현식 |
| #{...} | 속성 파일 값 표현식 |
| @{...} | URL 표현식 |
| *{...} | 선택한 변수의 표현식. th:object에서 선택한 객체에 접근 |
타임리프 문법
| 표현식 | 설명 | 예제 |
| th:text | 텍스트를 표현할때 사용 | th:text=${person.name} |
| th:each | 컬렉션을 반복할 때 사용 | th:each="person:${persons}" |
| th:if | 조건이 true인 때만 표시 | th:if="${person.age}>=20" |
| th:unless | 조건이 false인 때만 표시 | th:unless="${person.age}>=20" |
| th:href | 이동 경로 | th:href="@{/person(id=${person.id})}" |
| th:with | 변숫값으로 지정 | th:with"name=${person.name}" |
| th:object | 선택한 객체로 지정 | th:object="${person}" |
타임리프를 사용하기 위해선 의존성을 추가해야합니다.
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
추가 한후 새로고침하여 그레이들 변경 사항을 적용해줍니다.
연습용 컨트롤러를 임시로 만들어서 문법을 익혀봅시다.
controller패키지에 ExampleController.java파일을 만들고 코드를 입력해주세요.
Model
모델 객체는 뷰 , 즉 HTML 쪽으로 값을 넘겨주는 객체입니다.
모델 객체는 따로 생성할 필요 없이 코드처럼 인자로 선언하기만 하면
스프링이 알아서 만들어주므로 편리하게 사용할 수 있습니다.
addAttribute() 메서드로 모델에 값을 저장합니다.
person 이라는 키에 사람정보를 담고
today 라는 키에 날짜 정보를 저장합니다.
해당 메서드에 리턴값은 example이죠?
이 값은 클래스에 붙은 어노테이션이 @Controller 이므로 뷰의 이름을
반환하는 겁니다. 즉 스프링 부트는 컨트롤의
@Controller 어노테이션을 보고 반환하는 값의 이름을 가진 뷰의 파일을 찾으라는 것으로 이해하여
resource/templates 디렉터리에서 example.html 을 찾은 다음
웹 브라우저에서 해당 파일을 보여줍니다.
모델의 역할을 더 자세히 살펴보겠습니다.

모델에는 person, today 두 키를 가진 데이터가 들어있습니다.
컨트롤러는 이렇게 모델을 통해 데이터를 설정하고 모델을 뷰로 이 데이터를 전달해
키에 맞는 데이터를 뷰에서 조회할 수 있게 하죠
모델은 컨트롤러와 뷰의 중간다리 역할을 해준다고 생각하면 됩니다.
이번에 뷰를 작성해서 눈으로 확인해보겠습니다.
src/main/resources/templates 디렉터리에 example.html 파일을 생성하고
아래에 코드를 입력해봅시다.
파일을 만들때 주의하실점은 HTML5 파일로 만드셔야합니다.
#temporals.format() 함수로 LocalDate 타입인 오늘 날짜를 yyyy-MM--dd 형식의
String 타입으로 포매팅 합니다.
th:object를 사용해 모델에서 받은 객체중 "person" 이라는 키를
가진 객체의 데이터를 하위 태그에 지정합니다.
그러면 하위 태그에서는 *{...}를 사용해 부모태그에 적용한 객체 값에 접근할 수 있습니다.
th:text는 텍스트를 표현합니다. 여기서는 이름 : 이라는 문자열과
person 객체의 name값인 홍길동을 이어 붙이겠죠
th:each는 객체의 hobbies 개수 만큼 반복하는 반복자입니다.
th:if는 말 그대로 if문입니다.
이제 결과물이 어떻게 나오는지 스프링 부트 서버를 실행 후
http://localhost:8080/thymeleaf/example에 접속해보겠습니다.

저희가 컨트롤러에 입력했던 값들과 일치하게 나오는걸 확인할수 있습니다.
이제 기본적인 지식을 익혔으니 본격적으로 구현을 해보겠습니다.
순서는 컨트롤러의 메서드를 만들고 HTML 뷰를 만든 다음 뷰를 테스트합니다.
지금까지는 API를 만들기 위해 컨트롤러 메서드가 데이터를 직렬화한 JSON 문자열을
반환했지만 뷰 컨트롤러 메서드는 뷰의 이름을 반환하고 모델 객체에 값을 담습니다.
반환하는 값이 다를뿐 전체적인 구조는 비슷하니 어렵지 않을겁니다.
뷰에게 데이터를 전달하기 위한 객체를 생성하기 위해
dto 패키지에 ArticleListViewResponse.java 파일을 만든 다음 코드를 작성합니다.
@Getter
public class ArticleListViewResponse {
private final Long id;
private final String title;
private final String content;
public ArticleListViewResponse(Article article) {
this.id = article.getId();
this.title = article.getTitle();
this.content = article.getContent();
}
}
다음 controller 패키지에 BlogViewController.java 파일을 만들어
/articles GET요청을 처리할 코드를 작성합니다.
@RequiredArgsConstructor
@Controller
public class BlogViewController {
private final BlogService blogService;
@GetMapping("/articles")
public String getArticles(Model model) {
List<ArticleListViewResponse> articles = blogService.findAll().stream()
.map(ArticleListViewResponse::new)
.toList();
model.addAttribute("articles", articles);
return "articleList";
}
}
addAttribute() 메서드를 사용해 모델에 값을 저장했습니다.
여기서는 articles 키에 글 리스트를 저장합니다.
반환값인 articleList 는 articleList.html 을 찾도록 뷰의 이름을 적었습니다.
articleList.html 파일을 만들고 모델에 전달한 블로그 글 리스트 개수만큼 반복해
글 정보를 보여주도록 코드를 작성해보겠습니다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>블로그 글 목록</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
</head>
<body>
<div class="p-5 mb-5 text-center</> bg-light">
<h1 class="mb-3">My Blog</h1>
<h4 class="mb-3">블로그에 오신 것을 환영합니다.</h4>
</div>
<div class="container">
<div class="row-6" th:each="item : ${articles}"> <!-- article 개수 만큼 반복 -->
<div class="card">
<div class="card-header" th:text="${item.id}"> <!-- item의 id 출력 -->
</div>
<div class="card-body">
<h5 class="card-title" th:text="${item.title}"></h5>
<p class="card-text" th:text="${item.content}"></p>
<a th:href="@{/articles/{id}(id=${item.id})}" class="btn btn-primary">보러가기</a>
</div>
</div>
<br>
</div>
</div>
</body>
th:each로 articles 키에 담긴 데이터 개수만큼 반복합니다.
th:text는 반복 대상 객체의 id, text를 출력합니다.
화면의 가독성을 위해 부트스트랩을 사용하였습니다.
스프링 부트 서버를 재시작 한 후
http://localhost:8080/articles에 접속해서 잘 나오는지 확인해봅시다.

잘나오는것도 확인했으니 보러가기 버튼을 누르면
블로그 글이 보이도록 블로그 뷰를 구현하겠습니다.
엔티티에 생성 시간 , 수정 시간을 추가하고 컨트롤러 메서드를 만든 다음
HTML 뷰를 만들고 확인해보도록하겠습니다.
Aticle.java에 해당 필드들을 추가해주세요.
@CreatedDate
@Column(name = "created_at")
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "update_at")
private LocalDateTime updatedAt;
@CreateDate
엔티티가 생성될 때 생성 시간을 created_at 컬럼에 저장합니다.
@LastModifiedDate
엔티티가 수정될 때 마지막으로 수정된 시간을 updated_at 컬럼에 저장합니다.
SpringbootApplication.java 파일을 열어 엔티티의
created_at, updated_at 을 자동으로 업데이트 하기 위한 어노테이션을 추가합니다.
@EnableJpaAuditing // created_at , updated_at 자동 업데이트
@SpringBootApplication //메인 클래스로 사용하기 위한 어노테이션
public class SpringbootApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootApplication.class, args);
}
}
dto디렉터리에 ArticleViewResponse.java를 생성한뒤
뷰에서 사용할 DTO를 만들겠습니다.
@NoArgsConstructor
@Getter
public class ArticleViewResponse {
private Long id;
private String title;
private String content;
private LocalDateTime createdAt;
public ArticleViewResponse(Article article) {
this.id = article.getId();
this.title = article.getTitle();
this.content = article.getContent();
this.createdAt = article.getCreatedAt();
}
}
DTO를 만들었으니 블로그 글을 반환할 컨트롤러의 메서드를
BlogViewController.java 파일을 열어 추가하겠습니다.
@GetMapping("/articles/{id}")
public String getArticle(@PathVariable Long id, Model model) {
Article article = blogService.findById(id);
model.addAttribute("article", new ArticleViewResponse(article));
return "article";
}
getArticle() 메서드는 인자 id에 URL로 넘어온 값을 받아
findByid() 메서드로 넘겨 글을 조회하고,
화면에서 사용할 모델에 데이터를 저장한 다음 보여줄 화면의 템플릿 이름을 반환합니다.
article.html 을 만들어 화면을 작성합니다.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>블로그 글</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
</head>
<body>
<div class="p-5 mb-5 text-center</> bg-light">
<h1 class="mb-3">My Blog</h1>
<h4 class="mb-3">블로그에 오신 것을 환영합니다.</h4>
</div>
<div class="container mt-5">
<div class="row">
<div class="col-lg-8">
<article>
<input type="hidden" id="article-id" th:value="${article.id}">
<header class="mb-4">
<h1 class="fw-bolder mb-1" th:text="${article.title}"></h1>
<div class="text-muted fst-italic mb-2" th:text="|Posted on ${#temporals.format(article.createdAt, 'yyyy-MM-dd HH:mm')}|"></div>
</header>
<section class="mb-5">
<p class="fs-5 mb-4" th:text="${article.content}"></p>
</section>
<button type="button" id="modify-btn"
th:onclick="|location.href='@{/new-article?id={articleId}(articleId=${article.id})}'|"
class="btn btn-primary btn-sm">수정</button>
<button type="button" id="delete-btn"
class="btn btn-secondary btn-sm">삭제</button>
</article>
</div>
</div>
</div>
</body>
템플릿 함수 중 ${#temporals.format()} 은 날짜 형식을 yyyy-MM-dd HH:mm 으로 포매팅합니다.
포매팅한 날짜 형식을 || 기호와 함께 Posted on 이라는 텍스트를 붙힙니다.
결과물에 2099-99-31 23:11 Posted on
같이 글을 게시한 시간 알림을 표시할수 있습니다.
href 속성을 th:href 속성으로 변경하고 URL 표현식 @{...}을 사용해
보러가기를 눌렀을때 주소창의 값을 /articles/{item.id}으로 변경해 글 상세 화면으로 이동시킵니다.
화면을 확인하기 전
created_at 와 update_at는
생성과 수정했을때 값이 변화하기 때문에
drop table article;
쿼리문을 실행하여 article 테이블을 삭제한뒤
서버를 실행하여 테이블을 다시 만들어줍니다.
insert into article (title, content, created_at, update_at) values ('제목 1', '내용 1', NOW(), NOW());
insert into article (title, content, created_at, update_at) values ('제목 2', '내용 2', NOW(), NOW());
insert into article (title, content, created_at, update_at) values ('제목 3', '내용 3' , NOW(), NOW());
후에 인설트문을 실행하여 데이터를 임의로 넣어줍시다.
이제 다시 서버를 실행해준뒤 주소창에
http://localhost:8080/articles를 입력하고
보러가기 버튼을 눌러보겠습니다.

다음 포스팅엔 수정/삭제/생성 뷰 만들기를 해보겠습니다.
'Spring boot' 카테고리의 다른 글
| Spring Boot 스프링 시큐리티 (0) | 2024.01.01 |
|---|---|
| Spring Boot 각종 뷰 (1) | 2023.12.31 |
| Spring Boot 수정 삭제 (0) | 2023.12.28 |
| Spring Boot 블로그 글 조회 (0) | 2023.12.28 |
| Spring Boot 블로그 글 추가하기 (1) | 2023.12.27 |