// domain - Article.java
@Entity // 엔티티로 지정
@Getter
@NoArgsConstructor
public class Article{
~ 생략 ~
@CreatedDate // 엔티티가 생성될 때 생성 시간 저장
@Column(name = "created_at")
private LocalDateTime createdAt;
@LastModifiedDate // 엔티티가 수정될 때 수정 시간 저장
@Column(name = "updated_at")
private LocalDateTime updatedAt;
~ 생략 ~
}
// springbootdeveloper - BlogApplication.java
@EnableJpaAuditing // created_at, updated_at 자동 업데이트
@SpringBootApplication
public class BlogApplication {
public static void main(String[] args) {
SpringApplication.run(BlogApplication.class, args);
}
}
- Audit 기능
- Spring Data JPA에서 제공하는 엔티티의 변경 이력을 추적하고 관리하는 기능이다.
- 데이터베이스 테이블의 각 레코드에 대해 생성일시, 수정일시, 생성자, 수정자 등의 정보를 저장하고자 할 때 사용된다.
- spring-boot-starter-data-jpa 의존성만 추가해도 Audit을 사용할 수 있다.
// dto - ArticleViewResponse.java
@RequiredArgsConstructor
@Controller
public class BlogViewController {
private final BlogService blogService;
~ 생략 ~
@GetMapping("/articles/{id}")
public String getArticle(@PathVariable Long id, Model model) {
Article article = blogService.findById(id);
model.addAttribute("article", new ArticleViewResponse(article));
return "article";
}
}
// controller - BlogViewController.java
@RequiredArgsConstructor
@Controller
public class BlogViewController {
private final BlogService blogService;
~ 생략 ~
@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() 메서드로 넘겨 글을 조회하고, 화면에서 사용할 모델에 데이터를 저장한 다음, 보여줄 화면의 템플릿 이름을 반환.
// resources - templates - 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>
<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"
class="btn btn-primary btn-sm">수정</button>
<button type="button"
class="btn btn-secondary btn-sm">삭제</button>
</article>
</div>
</div>
</div>
<script src="/js/article.js"></script>
</body>
${#temporals.format(article.createdAt, 'yyyy-MM-dd HH:mm')}
article.createdAt의 값을 가져와서 'yyyy-MM-dd HH:mm' 형식의 문자열로 포맷팅한다.
location.href
JavaScript에서 현재 창의 URL을 나타내는 속성이다. 해당 속성을 사용하면 현재 페이지의 URL을 읽거나 변경할 수 있다.
// resources - templates - 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}">
<div class="card">
<div class="card-header" th:text="${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:href="@{/articles/{id}(id=${item.id})}"
/articles/{id}와 매핑되는 컨트롤러가 실행되어, 결국에는 해당 컨트롤러가 반환하는 뷰가 보여진다. id 값에는 모델의 item 객체의 id 속성 값이 들어간다.
해당 글은 다음 도서의 내용을 정리하고 참고한 글임을 밝힙니다.
신선영, ⌜스프링 부트 3 벡엔드 개발자 되기 - 자바 편⌟, 골든래빗(주), 2023, 384쪽