jpa나 spring에 관련된 글은 많이 썻는데 정작 간단한 게시판 만드는 글을 안써서 오늘은 jpa를 사용해서 간단한 게시판 crud api를 개발해보겠습니다.
우선 엔티티를 먼저 생성해주겠습니다. @NoArgsConstructor는 기본 생성자를 생성해주는 어노테이션입니다. 접근 레벨은 PROTECTED 입니다.
그리고 밑에 있는 생성자는 @Builder 패턴을 사용했습니다.
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Board {
@Id @GeneratedValue
@Column(name = "board_id")
private Long id;
@Column(length = 15, nullable = false)
private String writer;
@Column(length = 100, nullable = false)
private String title;
@Column(columnDefinition = "TEXT", nullable = false)
private String content;
@Builder
public Board(String writer, String title, String content) {
this.writer = writer;
this.title = title;
this.content = content;
}
}
엔티티를 그대로 반환해주면 안되기 때문에 DTO 클래스를 만들어주도록 하겠습니다.
@Setter
@NoArgsConstructor
@Getter
public class BoardRequestDto {
private Long id;
private String writer;
private String title;
private String content;
@Builder
public BoardRequestDto(String writer, String title, String content) {
this.writer = writer;
this.title = title;
this.content = content;
}
public Board ToEntity(){
return Board.builder()
.writer(this.writer)
.title(this.title)
.content(this.content)
.build();
}
}
@Data
@NoArgsConstructor
public class BoardResponseDto {
private Long id;
private String writer;
private String title;
private String content;
@Builder
public BoardResponseDto(String writer, String title, String content) {
this.writer = writer;
this.title = title;
this.content = content;
}
}
그 다음은 CRUD 기능을 제공해줄 Repository를 만들어주도록 하겠습니다.
BoardRepository - 인터페이스
public interface BoardRepository extends JpaRepository<Board, Long> {
}
레파지토리를 생성해주었으니 비즈니스 로직을 구현해주기 위해 서비스 계층을 만들어보겠습니다. 서비스는 추상화를 적용하기 위해 Service와 ServiceImpl 2개의 클래스로 나누는 방식을 사용하겠습니다.
public interface BoardService {
public void savePost(BoardRequestDto boardDto);
public List<BoardRequestDto> getBoardList(Integer pageNum);
public BoardRequestDto getPost(Long id);
public void deletePost(Long id);
public List<BoardRequestDto> searchPosts(String keyword);
public void update(Long id, BoardRequestDto dto);
}
@Service
@RequiredArgsConstructor
public class BoardServiceImpl implements BoardService{
private final BoardRepository boardRepository;
@Transactional
@Override
public void savePost(BoardRequestDto boardDto){
boardRepository.save(boardDto.ToEntity());
}
@Transactional
@Override
public List<BoardRequestDto> getBoardList(){
List<Board> all = boardRepository.findAll();
List<BoardRequestDto> boardDtoList = new ArrayList<>();
for(Board board : all){
BoardRequestDto boardDto = BoardRequestDto.builder()
.title(board.getTitle())
.content(board.getContent())
.writer(board.getWriter())
.build();
boardDtoList.add(boardDto);
}
return boardDtoList;
}
@Transactional
@Override
public BoardRequestDto getPost(Long id){
Optional<Board> boardWrapper = boardRepository.findById(id);
Board board = boardWrapper.get();
return BoardRequestDto.builder()
.title(board.getTitle())
.content(board.getContent())
.writer(board.getWriter())
.build();
}
@Transactional
@Override
public void deletePost(Long id){
boardRepository.deleteById(id);
}
@Transactional
@Override
public List<BoardRequestDto> searchPosts(String keyword){
List<Board> boards = boardRepository.findByTitleContaining(keyword);
List<BoardRequestDto> boardList = new ArrayList<>();
for(Board board : boards){
BoardRequestDto build = BoardRequestDto.builder()
.title(board.getTitle())
.content(board.getContent())
.writer(board.getWriter())
.build();
boardList.add(build);
}
return boardList;
}
@Transactional
@Override
public void update(Long id, BoardRequestDto dto) {
Optional<Board> byId = boardRepository.findById(id);
Board board = byId.get();
board.updateBoard(dto.getWriter(), dto.getTitle(), dto.getContent());
}
}
getBoardList, searchPosts 같이 컬렉션 조회는 반환할 때 for 문 안에 Builder 를 사용하면 코드가 상당히 길어집니다.
따라서 stream 의 map을 사용해서 바꾸어주는 것이 더 좋습니다.
하지만 stream 을 모르는 분들이 계실 수 있으니 for문으로 처리하였습니다.
대부분의 crud 기능 구현은 마쳤으니 이를 api로 만들어보겠습니다.
@RestController
@RequiredArgsConstructor
public class BoardApiController {
private final BoardServiceImpl boardService;
private final BoardRepository boardRepository;
@PostMapping("/api/post")
public BoardResponseDto savePost(@RequestBody @Valid BoardRequestDto request) {
boardService.savePost(request);
return new BoardResponseDto(
request.ToEntity().getTitle(),
request.ToEntity().getWriter(),
request.ToEntity().getContent()
}
@PutMapping("/api/post/{id}")
public BoardResponseDto updatePost(@PathVariable("id") Long id,
@RequestBody @Valid BoardRequestDto request) {
boardService.update(id, request);
Optional<Board> findPost = boardRepository.findById(id);
Board board = findPost.get();
return new BoardResponseDto(
board.getTitle(),
board.getWriter(),
board.getContent());
}
@GetMapping("/api/board/posts")
public List<BoardRequestDto> findPosts(){
List<Board> findAll = boardRepository.findAll();
List<BoardRequestDto> allPost = new ArrayList<>();
for(Board board : findAll){
BoardRequestDto build = BoardRequestDto.builder()
.content(board.getContent())
.writer(board.getWriter())
.title(board.getTitle())
.build();
allPost.add(build);
}
return allPost;
}
@GetMapping("/api/board/post/{id}")
public BoardResponseDto findPost(@PathVariable("id") Long id){
BoardRequestDto post = boardService.getPost(id);
return new BoardResponseDto(
post.getWriter(),
post.getTitle(),
post.getContent()
);
}
@DeleteMapping("/api/post/delete/{id}")
public void delete(@PathVariable("id") Long id){
boardService.deletePost(id);
}
}
이렇게 간단한 게시판 기능들을 api로 만들어보았습니다.
Postman과 같은 툴로 api를 검증해보면 결과가 잘 나오는 것을 확인하실 수 있습니다.
여기에 LocalDateTime과 페이지네이션(페이징) 등등 기능을 조금만 더 추가해주면 더 좋은 게시판이 될 것 같습니다.