일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 스프링부트 계층구조
- 스프링
- MSA
- 비즈니스 계층
- 로그인/로그아웃
- http
- 작업명중복
- @temproal
- 토큰기반 인증
- RESTfull API
- 스프링부트 구조
- 로그인 인증 흐름
- java I/O
- spring
- IPC
- formmatted
- JWT
- 세션기반 인증
- 비동기
- 어노테이션
- ORM
- ./gradlew docker
- 동기
- ./gr
- 프레젠테이션 계층
- 스프링부트
- 퍼시스턴스 계층
- JPA
- Java
- 스프링 부트 테스트
- Today
- Total
[DEV] J-Jay
스프링 부트 Blog 만들기 (3) 본문
기능1. 블로그 글 목록 조회
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();
}
}
BlogViewController.java
@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"키에 블로그 글 들(List)을 저장하며
반환값인 "articleList" 는 resource/templates/articleList.html의 view 이름이다.
기능2. 블로그 글 상세 조회
Article.java
@EntityListeners(AuditingEntityListener.class)
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Article {
...
@CreatedDate
@Column(name = "created_at")
private LocalDateTime createdAt;
@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;
}
엔티티에 생성 시간과 수정 시간을 추가해 글이 언제 생성되었는지 뷰에서 확인하기위해 위의 두 컬럼을 추가한다.
여기서 @CreatedDate 어노테이션을 사용하면 엔티티가 생성될 때 생성 시간을 저장하며, @LastModifedDate 어노테이션을 사용하면 엔티티가 수정될 때 마지막으로 수정된 시간을 저장한다.
data.sql
INSERT INTO article (title, content, created_at, updated_at) VALUES ('제목 1', '내용 1', NOW(), NOW())
INSERT INTO article (title, content, created_at, updated_at) VALUES ('제목 2', '내용 2', NOW(), NOW())
INSERT INTO article (title, content, created_at, updated_at) VALUES ('제목 3', '내용 3', NOW(), NOW())
엔티티를 생성하면 생성 시간과 수정 시간이 자동으로 저장되지만 스프링 부트 서버를 실행할 때마다 SQL문으로 데이터를 넣는 data.sql 파일은 created_at과 updated_at을 바꾸지 않는다. 최초 파일 생성에도 이 값을 수정하도록 위와 같이 수정한다.
SpringBootDeveloperApplication.java
@EnableJpaAuditing
@SpringBootApplication
public class SpringBootDeveloperApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDeveloperApplication.class, args);
}
}
@EnableJpaAuditing 어노테이션을 통해 created_at과 updated_at을 자동으로 업데이트를 한다.
추가적으로 Entity 클래스에도 @EntityListeners(AuditingEntityListener.class) 어노테이션 사용해야 되는데,
이 어노테이션은 Spring Data JPA에서 엔티티 객체의 생명주기 이벤트를 처리하고, 이를 활용하여 엔티티 객체의 생성일과 수정일을 추적하고 업데이트하는 역할을 수행한다.
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();
}
}
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() 메서드를 통해 글을 조회하고, 화면에서 사용할 모델에 데이터를 저장한 후, 보여줄 화면의 템플릿 이름을 반환한다.
기능3. 블로그 글 추가, 수정, 삭제를 위한 HTML, javascript
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">
<button type="button" id="create-btn"
th:onclick="|location.href='@{/new-article}'|"
class="btn btn-secondary btn-sm mb-3">글 등록</button>
<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>
<script src="/js/article.js"></script>
</body>
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>
<script src="/js/article.js"></script>
</body>
newArticle.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">
<input type="text" class="form-control" placeholder="제목" id="title" th:value="${article.title}">
</header>
<section class="mb-5">
<textarea class="form-control h-25" rows="10" placeholder="내용" id="content" th:text="${article.content}"></textarea>
</section>
<button th:if="${article.id} != null" type="button" id="modify-btn" class="btn btn-primary btn-sm">수정</button>
<button th:if="${article.id} == null" type="button" id="create-btn" class="btn btn-primary btn-sm">등록</button>
</article>
</div>
</div>
</div>
<script src="/js/article.js"></script>
</body>
article.js
// 추가 기능
const createButton = document.getElementById('create-btn');
if (createButton) {
createButton.addEventListener('click', event => {
fetch('/api/articles', {
method: 'POST',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: document.getElementById('title').value,
content: document.getElementById('content').value
})
})
.then(() => {
alert('등록 완료되었습니다.');
location.replace('/articles');
});
});
}
// 수정 기능
const modifyButton = document.getElementById('modify-btn');
if (modifyButton) {
modifyButton.addEventListener('click', event => {
let params = new URLSearchParams(location.search);
let id = params.get('id');
fetch(`/api/articles/${id}`, {
method: 'PUT',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: document.getElementById('title').value,
content: document.getElementById('content').value
})
})
.then(() => {
alert('수정이 완료되었습니다.');
location.replace(`/articles/${id}`);
});
});
}
// 삭제 기능
const deleteButton = document.getElementById('delete-btn');
if (deleteButton) {
deleteButton.addEventListener('click', event => {
let id = document.getElementById('article-id').value;
fetch(`/api/articles/${id}`, {
method: 'DELETE'
})
.then(() => {
alert('삭제가 완료되었습니다.');
location.replace('/articles');
});
});
}
'Back-end > Spring' 카테고리의 다른 글
스프링 부트 - 로그인/로그아웃 구현 (0) | 2023.09.10 |
---|---|
스프링 시큐리티(Security) (0) | 2023.09.09 |
스프링 부트 Blog 만들기 (2) - Thymeleaf (0) | 2023.09.05 |
스프링 부트 Blog 만들기 (1) (0) | 2023.09.04 |
스프링 부트 ORM (2) (0) | 2023.09.03 |