Controller 패키지를 생성한다.
BoardApiController를 생성하여 두 annotation을 달아준다.
@RestController
@RequestMapping("/api/board")
public class BoardApiController {
}
model 패키지에 BoardRequest를 생성한다.
Camel Case를 사용할 것이니 @JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)를 달아준다. 이 annotation을 달지 않아도 할 수 있는 방법이 있지만 일단 이 방법도 써본다.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class BoardRequest {
@NotBlank
private String boardName;
}
Service 패키지를 생성해준다.
여기에 BoardService 클래스를 생성한다.
service에서는 repository랑 연결해줄 것이다.
Save해보자.
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
public BoardEntity create(
BoardRequest boardRequest
){
var entity = BoardEntity.builder()
.boardName(boardRequest.getBoardName())
.status("REGISTERED")
.build();
return boardRepository.save(entity);
}
}
private final BoardRepository boardRepository에서 final을 넣는 이유는 기본 생성자(Argument)에 값이 들어가서 생성되도록 하기 위해서 넣는 것이다.
즉 생성자에서 초기화되어야 하기 때문이다.
id는 필요없으니 넣지 않고 BoardName이랑 status넣을 것이다.
BoardName에는 Request가 가져온 getBoardName을 해줄 것이고 status는 REGISTERED변수로 받을 것이다.
다시 Controller로 가서 Service랑 연결시키자.
@RequiredArgsConstructor를 추가로 달아주고 @Vaild를 넣어서 BookRequest를 검증시킨다.
Post는 반드시 RequestBody로 들어올 수 있도록 @RequestBody를 달아준다
그래서 BoardService의 Create를 통해서 요청이 온 Request에 대한 Name, 즉 사용자가 요청한 이름을 BoardName에 지정하고 상태는 REGISTERED로 Save한 다음 다시 값을 Return시키는 것이다.
@RestController
@RequestMapping("/api/board")
@RequiredArgsConstructor
public class BoardApiController {
private final BoardService boardService;
@PostMapping("")
public BoardEntity create(
@Valid
@RequestBody BoardRequest boardRequest
){
return boardService.create(boardRequest);
}
}
board에서 했던 방식과 비슷하게 구현한다.
PostRequest클래스
@Size(min = , max = )를 사용해 password를 최소, 최대 4자리로 설정한다.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class PostRequest {
@NotBlank
private String userName;
@NotBlank
@Size(min = 4, max = 4)
private String password;
@NotBlank
@Email
private String email;
@NotBlank
private String title;
@NotBlank
private String content;
}
Controller클래스
@RestController
@RequestMapping("/api/post")
@RequiredArgsConstructor
public class PostApiController {
private final PostService postService;
@PostMapping("")
public PostEntity create(
@Valid
@RequestBody PostRequest postRequest
){
return postService.create(postRequest);
}
}
Service클래스
PostRequest를 받으면 PostEntity.builder패턴을 통해서 만들 것들을 작성한다.
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
public PostEntity create(
PostRequest postRequest
){
var entity = PostEntity.builder()
.boardId(1L) //임시 고정
.userName(postRequest.getUserName())
.password(postRequest.getPassword())
.email(postRequest.getEmail())
.status("REGISTERED")
.title(postRequest.getTitle())
.content(postRequest.getContent())
.postedAt(LocalDateTime.now())
.build();
return postRepository.save(entity);
}
}
잘 구현됐는지 API TESTER을 작동해보자
Board 테스트
POST로 http://localhost:8080/api/board 를 넣고
BODY에
{
"board_name": "Q&A 게시판"
}
을 넣고 SEND한다.

body에 있는 내용들이 성공적으로 들어온 모습
Post 테스트
POST로 http://localhost:8080/api/post 를 넣고
BODY에
{
"user_name" : "홍길동",
"password" : 1111,
"email": "hong@gmail.com",
"status": "REGISTERED",
"title" : "문의",
"content" : "네고해요"
}
을 넣고 SEND한다.

이것도 잘 나오는 모습
게시글을 Post한다면 Post 된 글을 봐야한다.
그래서 PostEntityView라는 것을 만들 것이다.
그 전에 model 패키지에 PostViewRequest 클래스를 만들자
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class PostViewRequest {
@NotNull
private Long postId;
@NotBlank
private String password;
}
다시 Controller로 넘어오자.
원래는 글을 보기 위해 @GetMapping을 넣어야겠지만,
나는 비밀글을 볼 수 있는 게시판을 할 것이다.
글을 누르면 비밀번호를 입력해야하기 때문에 @PostMapping을 하도록 한다.
@PostMapping("/view")
public PostEntity View(
@Valid
@RequestBody PostViewRequest postViewRequest
){
return postService.view(postViewRequest)
}
코드를 이렇게 입력하면 view에 에러가 뜰 것이다.
Alt+Enter하고 'Create method 'view' in 'PostService'를 클릭해서
public PostEntity view(PostViewRequest postViewRequest) {
}
를 만든다.
Service에서 만들 것은
1. 게시글이 있는가?
2. 비밀번호가 맞는가? 를 만들면 된다.
password가 서로 맞지 않으면 (equals가 아니면) throw를 던지고, password가 맞으면 해당 entity를 리턴할 것이다.
만약 DATA가 없다면 .orElseThrow를 통해 해당 게시글이 없다는 exception을 터뜨릴 것이다.
public PostEntity view(PostViewRequest postViewRequest) {
return postRepository.findById(postViewRequest.getPostId())
.map(it -> {
if (!it.getPassword().equals(postViewRequest.getPassword())) {
throw new RuntimeException(String.format("패스워드가 맞지 않습니다. %s vs %s", it.getPassword(), postViewRequest.getPassword()));
}
return it;
}).orElseThrow(
() -> new RuntimeException("해당 게시글이 존재하지 않습니다." + postViewRequest.getPostId())
);
}
다시 Controller의 view로 돌아와서
게시글 전체를 보여주는 List를 만들자.
전체니까 @GetMapping("/all")로 받을 것이다.
@GetMapping("/all")
public List<PostEntity> list(
){
return postService.all();
}
로 하고 all에 아까처럼 오류가 뜰텐데 또 Serivce에 method를 만들자
아래처럼 입력해준다.
public List<PostEntity> all() {
return postRepository.findAll();
}
마지막 delete를 위해 Controller로 간다.
반드시 비밀번호를 넣어야하기 때문에 삭제가 불가능하다. 그러면 어떻게 해야할까?
viewRequest를 같이 써야할 것 같다. 그래서 여기다가는 deleteMapping body를 쓸 수 없기 때문에 @PostMapping("/delete")을 작성해주자.
이 때는 예외가 발생한다.
그래서 deleteMapping이 아니기 대문에 /delete 라고 만들어줄 수 있는 것이고, 그래서 반드시 view라고 해서 꼭 getMapping이 되는 것은 아니다.
상황에 맞춰서 annotation을 사용하면 된다.
@PostMapping("/delete")
public void delete(
@Valid
@RequestBody PostViewRequest postViewRequest
){
postService.delete(postViewRequest);
}
여기서도 delete에 오류가 뜰텐데 Service에 method를 만들어주자.
delete도 비밀번호를 확인해야하니 Service클래스에 작성한 view 메소드의 로직을 가져와서 사용할 것이다.
postRepository.findById에 return을 빼고 패스워드가 맞을 경우의 코드를 추가해준다.
패스워드가 맞을 경우 status를 UNREGISTERED에서 해지시켜 주도록 하자. 그러고 나서 save를 하자.
public void delete(PostViewRequest postViewRequest) {
postRepository.findById(postViewRequest.getPostId())
.map(it -> {
var format = "패스워드가 맞지 않습니다 %s vs %s";
if (it.getPassword().equals(postViewRequest.getPassword())) {
throw new RuntimeException("패스워드가 맞지 않습니다.");
}
it.setStatus("UNREGISTERED");
postRepository.save(it);
return it;
}).orElseThrow(
() -> {
return new RuntimeException("해당 게시글이 존재 하지 않습니다." + postViewRequest.getPostId());
}
);
}
GET 으로 http://localhost:8080/api/post/all 에 SEND 하면
이렇게 전체 목록이 뜬다.

POST에
{
"post_id":2,
"password" :1111
}
로 http://localhost:8080/api/post/view 에 SEND하면 지정한 글 내용이 뜬다.

만약
{
"post_id":2,
"password" :11112
}
로 틀린 PASSWORD를 준다면
java.lang.RuntimeException: 패스워드가 맞지 않습니다. 1111 vs 11112] with root cause
java.lang.RuntimeException: 패스워드가 맞지 않습니다. 1111 vs 11112
오류가 뜬다.

이번엔 없는 POST ID를 불러와보자
{
"post_id":4,
"password" :11112
}
로 하면
java.lang.RuntimeException: 해당 게시글이 존재하지 않습니다. ID: 4] with root cause
java.lang.RuntimeException: 해당 게시글이 존재하지 않습니다. ID: 4
오류가 뜬다.

POST 로 http://localhost:8080/api/post/delete
{
"post_id":2,
"password" :1111
}
에 SEND 하면 잘 동작되는 것 같지만

전체 목록을 출력하면 지운게 아직도 나온다.

이를 해결하기 위해 Service의 view와 Repository를 수정 및 추가하자.
PostRepository
public Optional<PostEntity> findFirstByIdAndStatusOrderByIdDesc(Long id, String status);
PostService
public PostEntity view(PostViewRequest postViewRequest) {
return postRepository.findFirstByIdAndStatusOrderByIdDesc(postViewRequest.getPostId(),"REGISTERED")
.map(it -> {
if (!it.getPassword().equals(postViewRequest.getPassword())) {
throw new RuntimeException(String.format("패스워드가 맞지 않습니다. %s vs %s", it.getPassword(), postViewRequest.getPassword()));
}
return it;
}).orElseThrow(
() -> new RuntimeException("해당 게시글이 존재하지 않습니다. ID: " + postViewRequest.getPostId())
);
}
이렇게 수정하고 다시
http://localhost:8080/api/post/view 에
{
"post_id":2,
"password" :1111
}
을 send하면 게시글이 존재하지 않는다고 성공적으로 delete된 모습을 볼 수 있다.

BoardApiController
package com.example.simpleboard.board.controller;
import com.example.simpleboard.board.db.BoardEntity;
import com.example.simpleboard.board.model.BoardRequest;
import com.example.simpleboard.board.service.BoardService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/board")
@RequiredArgsConstructor
public class BoardApiController {
private final BoardService boardService;
@PostMapping("")
public BoardEntity create(
@Valid
@RequestBody BoardRequest boardRequest
){
return boardService.create(boardRequest);
}
}
BoardEntity
package com.example.simpleboard.board.db;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
@Entity(name = "board")
public class BoardEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String boardName;
private String status;
}
BoardRepository
package com.example.simpleboard.board.db;
import org.springframework.data.jpa.repository.JpaRepository;
public interface BoardRepository extends JpaRepository<BoardEntity, Long> {
}
BoardRequest
package com.example.simpleboard.board.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import jakarta.validation.constraints.NotBlank;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class BoardRequest {
@NotBlank
private String boardName;
}
BoardService
package com.example.simpleboard.board.service;
import com.example.simpleboard.board.db.BoardEntity;
import com.example.simpleboard.board.db.BoardRepository;
import com.example.simpleboard.board.model.BoardRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class BoardService {
private final BoardRepository boardRepository;
public BoardEntity create(
BoardRequest boardRequest
){
var entity = BoardEntity.builder()
.boardName(boardRequest.getBoardName())
.status("REGISTERED")
.build();
return boardRepository.save(entity);
}
}
PostApiController
package com.example.simpleboard.post.controller;
import com.example.simpleboard.post.db.PostEntity;
import com.example.simpleboard.post.model.PostRequest;
import com.example.simpleboard.post.model.PostViewRequest;
import com.example.simpleboard.post.service.PostService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/post")
@RequiredArgsConstructor
public class PostApiController {
private final PostService postService;
@PostMapping("")
public PostEntity create(
@Valid
@RequestBody PostRequest postRequest
){
return postService.create(postRequest);
}
@PostMapping("/view")
public PostEntity View(
@Valid
@RequestBody PostViewRequest postViewRequest
){
return postService.view(postViewRequest);
}
@GetMapping("/all")
public List<PostEntity> list(
){
return postService.all();
}
@PostMapping("/delete")
public void delete(
@Valid
@RequestBody PostViewRequest postViewRequest
){
postService.delete(postViewRequest);
}
}
PostEntity
package com.example.simpleboard.post.db;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
@Entity(name = "post")
public class PostEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long boardId;
private String userName;
private String password;
private String email;
private String status;
private String title;
@Column(columnDefinition = "TEXT")
private String content;
private LocalDateTime postedAt;
}
PostRepository
package com.example.simpleboard.post.db;
import com.example.simpleboard.board.db.BoardEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface PostRepository extends JpaRepository<PostEntity, Long> {
public Optional<PostEntity> findFirstByIdAndStatusOrderByIdDesc(Long id, String status);
}
PostRequest
package com.example.simpleboard.post.model;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import jakarta.persistence.Column;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class PostRequest {
@NotBlank
private String userName;
@NotBlank
@Size(min = 4, max = 4)
private String password;
@NotBlank
@Email
private String email;
@NotBlank
private String title;
@NotBlank
private String content;
}
PostViewRequest
package com.example.simpleboard.post.model;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
@JsonNaming(value = PropertyNamingStrategy.SnakeCaseStrategy.class)
public class PostViewRequest {
@NotNull
private Long postId;
@NotBlank
private String password;
}
PostService
package com.example.simpleboard.post.service;
import com.example.simpleboard.post.db.PostEntity;
import com.example.simpleboard.post.db.PostRepository;
import com.example.simpleboard.post.model.PostRequest;
import com.example.simpleboard.post.model.PostViewRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.List;
@Service
@RequiredArgsConstructor
public class PostService {
private final PostRepository postRepository;
public PostEntity create(PostRequest postRequest) {
var entity = PostEntity.builder()
.boardId(1L) // 임시 고정
.userName(postRequest.getUserName())
.password(postRequest.getPassword())
.email(postRequest.getEmail())
.status("REGISTERED")
.title(postRequest.getTitle())
.content(postRequest.getContent())
.postedAt(LocalDateTime.now())
.build();
return postRepository.save(entity);
}
public PostEntity view(PostViewRequest postViewRequest) {
return postRepository.findFirstByIdAndStatusOrderByIdDesc(postViewRequest.getPostId(),"REGISTERED")
.map(it -> {
if (!it.getPassword().equals(postViewRequest.getPassword())) {
throw new RuntimeException(String.format("패스워드가 맞지 않습니다. %s vs %s", it.getPassword(), postViewRequest.getPassword()));
}
return it;
}).orElseThrow(
() -> new RuntimeException("해당 게시글이 존재하지 않습니다. ID: " + postViewRequest.getPostId())
);
}
public List<PostEntity> all() {
return postRepository.findAll();
}
public void delete(PostViewRequest postViewRequest) {
postRepository.findById(postViewRequest.getPostId())
.map(it -> {
if (!it.getPassword().equals(postViewRequest.getPassword())) {
throw new RuntimeException(String.format("패스워드가 맞지 않습니다. %s vs %s", it.getPassword(), postViewRequest.getPassword()));
}
it.setStatus("UNREGISTERED");
postRepository.save(it);
return it;
}).orElseThrow(
() -> new RuntimeException("해당 게시글이 존재하지 않습니다." + postViewRequest.getPostId())
);
}
}