registerDate
타입을 LocalDateTime
으로 하면 게시글 조회 화면에서
년-월-일T시:분:초
라고 나타나기 때문에 DateTimeFormatter
를 사용해서 registerDate
를 String 타입으로 저장한다.
package hello.board.entity;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "board_id")
private Long id;
private String title;
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
private String registerDate;
@Builder
public Board(String title, String content, User user, LocalDateTime registerDate) {
this.title = title;
this.content = content;
this.user = user;
this.registerDate = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(registerDate);
}
//==생성 메서드==//
public static Board createBoard(String title, String content, User user) {
return Board.builder()
.title(title).content(content).user(user)
.registerDate(LocalDateTime.now())
.build();
}
//==비즈니스 메서드==//
public void update(String title, String content) {
this.title = title;
this.content = content;
}
}
LocalDateTime
을 보내면, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(registerDate);
를 통해 포맷한 문자열을 registerDate
에 저장한다.ddl도 수정한다.
create table board (
board_id bigint not null auto_increment,
title varchar(255),
content text,
user_id bigint,
register_date varchar(20),
primary key (board_id),
constraint fk_user_board
foreign key (user_id)
references users (user_id) on update cascade
) engine=InnoDB default charset=utf8mb4;
register_date varchar(20)
package hello.board.controller.board;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Getter @Setter
public class PostForm {
@NotBlank(message = "제목을 입력해주세요.")
private String title;
private String content;
}
package hello.board.controller.board;
import hello.board.controller.SessionConst;
import hello.board.entity.Board;
import hello.board.entity.User;
import hello.board.service.BoardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@Controller
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/board")
public class BoardController {
private final BoardService boardService;
@GetMapping
public String postList(Model model) {
model.addAttribute("list", boardService.findAll());
return "board/postList";
}
@GetMapping("/{postId}")
public String postView(@PathVariable Long postId, Model model) {
log.info("postView");
Board post = boardService.findOne(postId).orElseThrow();
model.addAttribute("post", post);
return "board/post";
}
@GetMapping("/register")
public String registerForm(@ModelAttribute PostForm postForm) {
return "board/registerForm";
}
@PostMapping("/register")
public String register(@Valid @ModelAttribute PostForm postForm, BindingResult bindingResult, @SessionAttribute(name = SessionConst.LOGIN_USER, required = false) User loginUser) {
if (bindingResult.hasErrors()) {
log.info("errors = {}", bindingResult);
return "board/registerForm";
}
boardService.register(postForm.getTitle(), postForm.getContent(), loginUser.getId());
return "redirect:/board";
}
}
postList()
: 게시글 전체 조회boardService.findAll()
의 반환 값인 List<Board>
를 model
에 담아서 뷰 템플릿으로 전달한다.postView()
: 게시글 상세 조회postId
로 게시글을 찾아 model
에 담아서 뷰 템플릿에 전달한다.registerForm()
: 게시글 등록 폼 화면register()
: 게시글 등록@Valid
어노테이션으로 PostForm
객체를 검증한다.@SessionAttribute
로 현재 로그인한 회원(loginUser
)의 정보를 얻어 id 값을 찾고, boardService.register()
를 호출한다.게시글의 내용(content
)은 아무것도 입력하지 않아도 된다. 하지만, 내용에 아무것도 입력하지 않으면
BoardController
의 register()
메서드의
boardService.register(postForm.getTitle(), postForm.getContent(), loginUser.getId());
에서 NPE
가 발생한다.
PostForm
에 @NotNull
어노테이션을 추가한다.
package hello.board.controller.board;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Getter @Setter
public class PostForm {
@NotBlank(message = "제목을 입력해주세요.")
private String title;
@NotNull
private String content;
}
board/postList.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div id="wrapper">
<div class="container">
<div class="col-md-12">
<div class="py-5 text-center">
<h2>게시판</h2>
</div>
<button class="btn btn-dark"
th:onclick="|location.href='@{/}'|" type="button">
홈 화면
</button>
<button class="btn btn-primary"
th:onclick="|location.href='@{/board/register}'|" type="button">
게시글 작성
</button>
<hr class="my-4">
<table class="table">
<thead>
<tr>
<th width="10%">게시글번호</th>
<th width="">제목</th>
<th width="20%">작성자</th>
<th width="20%">작성일</th>
</tr>
</thead>
<tbody>
<tr th:each="post : ${list}">
<td>
<a th:href="@{/board/{postId}(postId=${post.id})}"
th:text="${post.id}">게시글 번호</a>
</td>
<td>
<a th:href="@{/board/{postId}(postId=${post.id})}"
th:text="${post.title}">제목</a>
</td>
<td th:text="${post.user.loginId}">작성자</td>
<td th:text="${post.registerDate}">작성일</td>
</tr>
</tbody>
</table>
<hr class="my-4">
</div>
</div>
</div>
</body>
board/registerForm.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
<style>
.field-error {
border-color: #dc3545;
color: #dc3545;
}
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2>게시글 등록</h2>
</div>
<form th:action th:object="${postForm}" method="post">
<div>
<label for="title">제목</label>
<input type="text" id="title" th:field="*{title}" class="form-control"
th:errorclass="field-error">
<div class="field-error" th:errors="*{title}" />
</div>
<div class="mb-3">
<label for="content">내용</label>
<textarea class="form-control" rows="5"
id="content" name="content" th:value="*{content}"></textarea>
</div>
<hr class="my-4">
<button class="btn btn-primary btn-lg" type="submit">
작성
</button>
<button class="btn btn-secondary btn-lg"
th:onclick="|location.href='@{/board}'|"
type="button">
취소
</button>
</form>
</div>
</body>
content
)을 적는 칸에는 input
태그 대신 textarea
태그를 사용하여, 여러 줄의 텍스트를 입력할 수 있는 입력 칸을 만든다.board/post.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<link th:href="@{/css/bootstrap.min.css}"
href="../css/bootstrap.min.css" rel="stylesheet">
<style>
.container{
max-width: 700px;
}
.field-error {
border-color: #dc3545;
color: #dc3545;
}
</style>
</head>
<body>
<div class="container">
<div class="py-5 text-center">
<h2>게시글</h2>
</div>
<button class="btn btn-dark pull-right"
onclick="location.href='board/postList.html'"
th:onclick="|location.href='@{/board}'|"
type="button">목록으로
</button>
<div>
<label for="postId">게시판번호</label>
<input type="text" id="postId" name="postId" class="form-control"
value="1" th:value="${post.id}" readonly>
</div>
<div>
<label for="postTitle">제목</label>
<input type="text" id="postTitle" name="postTitle" class="form-control"
value="제목" th:value="${post.title}" readonly>
</div>
<div>
<label for="postContent">내용</label>
<textarea class="form-control" rows="5"
id="postContent" name="postContent"
th:value="${post.content}" th:text="${post.content}" readonly></textarea>
</div>
<div>
<label for="writer">작성자</label>
<input type="text" id="writer" name="writer" class="form-control"
value="작성자" th:value="${post.user.loginId}" readonly>
</div>
<div>
<label for="registerDate">작성일</label>
<input type="text" id="registerDate" name="registerDate" class="form-control"
value="작성자" th:value="${post.registerDate}" readonly>
</div>
<hr class="my-4">
<button class="btn btn-primary"
th:onclick="|location.href='@{/board/{postId}/edit(postId=${post.id})}'|"
type="button">수정</button>
<button class="btn btn-danger"
th:onclick="|location.href='@{/board/{postId}/delete(postId=${post.id})}'|"
type="button">삭제</button>
</div> <!-- /container -->
</body>
content
)을 적는 칸에는 input
태그 대신 textarea
태그를 사용하여, 여러 줄의 텍스트를 입력할 수 있는 입력 칸을 만든다.textarea
태그는 th:text
를 사용해야지 넘겨받은 변수내용을 표기할 수 있다. th:value
만 사용한다면 값이 표기되지 않는다.input
태그와 textarea
태그의 속성으로 readonly
를 사용하여 수정이 불가능하도록 한다.