[Spring] 나만의 게시판 만들기 3 - Board

최진민·2022년 2월 11일
1

게시판 만들기

목록 보기
3/9
post-thumbnail

Model


  • Board
    @Entity
    @Getter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class Board extends TimeEntity {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        @Column(name = "board_id")
        private Long board_id;
    
        @Column(name = "title")
        private String title;
    
        @Column(name = "board_content")
        private String content;
    
        @Column(name = "writer")
        private String writer;
    
        @Enumerated(EnumType.STRING)
        private BoardCategory category;
    
        @JsonBackReference
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "user_id")
        private User user;
    
    }
    • @Enumerated : enum 타입의 프로퍼티 또는 필드를 생성하는 애노테이션입니다.
      • EnumType에는 STRINGORDINAL이 존재하는데 ORDINAL순서, STRING글자 그대로이기 때문에 무조건 STRING을 사용하는 것이 좋습니다.
    • JPA의 연관 관계
      • User - boards
        @Entity
        @Getter
        @NoArgsConstructor(access = AccessLevel.PROTECTED)
        public class User extends TimeEntity {
        
        		//...
        		@JsonManagedReference
            @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
            private List<Board> boards = new ArrayList<>();
        		//...
        }
      • 연관관계가 성립되는 필드 값에 대해 순환 참조를 방어하기 위해서 @JsonManagedReference - @JsonBackReference를 사용했습니다.
      • User(일) - Board(다) ⇒ 일대다 연관관계를 성립하기 위해 @OneToMany - @ManyToOne / @JoinColumn을 사용했습니다. (💡 매우 중요하고 배울 것이 많은 개념이기 때문에 해당 글에서는 생략하겠습니다.)
  • BoardCategory
    public enum BoardCategory {
        NORMAL, SPORTS, STYLE, FUN
    }

Repository


  • BoardRepository
    public interface BoardRepository extends JpaRepository<Board, Long> {
    
        List<Board> findByCategory(BoardCategory category);
    
        List<Board> findByUser(User user);
    }
    • 스프링 데이터 JPA메소드 명, 리턴 타입, 파라미터 등을 사용하여 메소드를 생성할 수 있습니다.
      • findCategory() : 선택한 카테고리에 맞는 게시판의 리스트를 조회할 수 있습니다.
      • findByUser() : 사용자가 작성한 게시글을 리스트로 조회할 수 있습니다.

Service


게시판의 작성, 조회, 수정, 삭제를 Spring Data Jpa의 CRUD를 통해 구현합니다.


BoardFindService

@Service
@RequiredArgsConstructor
public class BoardFindService {

    private final BoardRepository boardRepository;
    private final UserFindService userFindService;

    @Transactional(readOnly = true)
    public Board findById(Long boardId) {
        Board board = boardRepository.findById(boardId)
                .orElseThrow(() -> new NotFoundBoardException(String.format("Board is not Found!")));
        return board;
    }

    @Transactional(readOnly = true)
    public List<Board> findAll() {
        return boardRepository.findAll();
    }

    @Transactional(readOnly = true)
    public List<Board> findByCategory(BoardCategory category) {
        return boardRepository.findByCategory(category);
    }

    @Transactional(readOnly = true)
    public List<Board> findByUser(Long userId) {
        User user = userFindService.findById(userId);
        return boardRepository.findByUser(user);
    }

}
  • BoardRepositoryUserFindService를 생성자(@RequiredArgsConstructor + private final)를 통해 참조하도록 합니다.
    • 앞서 설명했지만, 참조 관계순환되도록 하는 것은 지양합니다.
  • 사용자가 작성한 게시판 리스트를 조회하고자, 게시판에서 UserFindService를 참조하도록 했습니다.

BoardWriteService

@Service
@RequiredArgsConstructor
public class BoardWriteService {

    private final UserFindService userFindService;
    private final BoardRepository boardRepository;

    @Transactional
    public Long writeBoard(Long userId, BoardWriteRequest boardWriteRequest) {
        User user = userFindService.findById(userId);
        Board board = Board.builder()
                .title(boardWriteRequest.getTitle())
                .writer(user.getName())
                .content(boardWriteRequest.getContent())
                .category(boardWriteRequest.getCategory())
                .build();
        Board savedBoard = boardRepository.save(board);
        user.writeBoard(savedBoard);
        return savedBoard.getBoard_id();
    }
}
  • 게시판을 작성한다는 것 = 사용자가 게시판을 작성하는 것이기 때문에 작성시 필요한 정보userIdBoardWriteRequest 입니다.
    @Getter
    @NoArgsConstructor
    public class BoardWriteRequest {
    
        private String title;
        private String content;
        private BoardCategory category;
    }
  • 로그인한 사용자의 식별자 값(id) + builder()를 활용하여 새로운 게시글 생성하여 저장합니다.
  • writeBoard()
    @Entity
    @Getter
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    public class User extends TimeEntity {
    
    		//...
    		public void writeBoard(Board board) {
            this.boards.add(board);
            board.createdByUser(this);
        }
    		//...
    }
    • 연관관계를 고려하여 메서드를 작성했습니다.
      • 사용자의 게시글에 추가 : this.boards.add(board)
      • 게시글의 작성자를 해당 클래스로 설정 : board.createdByUser(this)
        @Entity
        @Getter
        @NoArgsConstructor(access = AccessLevel.PROTECTED)
        public class Board {
        
        		//...
        		public void createdByUser(User user) {
                this.user = user; //like setter
            }
        		//...
        }

BoardUpdateService

@Service
@RequiredArgsConstructor
public class BoardUpdateService {
    private final UserFindService userFindService;
    private final BoardFindService boardFindService;

    @Transactional
    public Long updateBoard(Long userId, Long boardId, BoardUpdateRequest boardUpdateRequest) {
        User user = userFindService.findById(userId);
        Board board = boardFindService.findById(boardId);
        checkBoardLoginUser(user, board);
        Long updatedBoardId = board.update(
                boardUpdateRequest.getTitle(),
                boardUpdateRequest.getContent(),
                boardUpdateRequest.getCategory()
        );

        return updatedBoardId;
    }

    private void checkBoardLoginUser(User user, Board board) {
        if (!Objects.equals(board.getUser().getUser_id(), user.getUser_id())) {
            throw new NotHavePermissionBoardException("해당 게시물을 수정할 권한이 없습니다.");
        }
    }
}
  • 게시글을 작성한 사용자가 맞는지 확인(checkBoardLoginUser)한 후 BoardUpdateRequest를 통해 게시글을 수정할 수 있습니다.
    @Getter
    @NoArgsConstructor
    public class BoardUpdateRequest {
    
        private String title;
        private String content;
        private BoardCategory category;
    
    }
    • 게시글의 제목, 내용, 카테고리를 수정할 수 있습니다.

BoardDeleteService

@Service
@RequiredArgsConstructor
public class BoardDeleteService {

    private final UserFindService userFindService;
    private final BoardFindService boardFindService;
    private final BoardRepository boardRepository;

    @Transactional
    public void deleteBoardById(Long userId, Long boardId) {
        User user = userFindService.findById(userId);
        Board board = boardFindService.findById(boardId);
        checkBoardLoginUser(user, board);
        boardRepository.deleteById(boardId);
    }

    private void checkBoardLoginUser(User user, Board board) {
        if (!Objects.equals(board.getUser().getUser_id(), user.getUser_id())) {
            throw new NotHavePermissionBoardException("해당 게시물을 삭제할 권한이 없습니다.");
        }
    }
}
  • 수정 서비스와 마찬가지로 게시글을 작성한 사용자인지 확인하여 삭제할 수 있습니다.

Controller


BoardFindApi

@Slf4j
@RestController
@RequestMapping("/boards")
@RequiredArgsConstructor
public class BoardFindApi {

    private final BoardFindService boardFindService;

    @GetMapping()
    public ApiResult<List<Board>> findAll() {
        try {
            return ApiResult.succeed(boardFindService.findAll());
        } catch (Exception e) {
            log.error(e.getMessage());
            return ApiResult.failed(e.getMessage());
        }
    }

    @GetMapping("/{boardId}")
    public ApiResult<Board> findById(@PathVariable Long boardId) {
        try {
            return ApiResult.succeed(boardFindService.findById(boardId));
        } catch (Exception e) {
            log.error(e.getMessage());
            return ApiResult.failed(e.getMessage());
        }
    }

    @GetMapping("/category/{category}")
    public ApiResult<List<Board>> findByCategory(@PathVariable BoardCategory category) {
        try {
            return ApiResult.succeed(boardFindService.findByCategory(category));
        } catch (Exception e) {
            log.error(e.getMessage());
            return ApiResult.failed(e.getMessage());
        }
    }

    @GetMapping("/user/{userId}")
    public ApiResult<List<Board>> findByUserId(@PathVariable Long userId) {
        try {
            return ApiResult.succeed(boardFindService.findByUser(userId));
        } catch (Exception e) {
            log.error(e.getMessage());
            return ApiResult.failed(e.getMessage());
        }
    }
}
  • @(애노테이션) 관련 설명은 User편에서 확인하시길 바랍니다.
  • ApiResult 설명 또한, 앞선 게시물에 있습니다!
  • 모든 게시물 조회 : findAll()
  • 단일 게시물 조회 : findById(Long boardId)
  • 카테고리에 해당하는 모든 게시물 조회 : findByCategory(BoardCategory boardCategory)
  • 사용자가 작성한 모든 게시물 조회 : findByUserId(Long userId)

BoardWriteApi

@Slf4j
@RestController
@RequestMapping("/boards")
@RequiredArgsConstructor
public class BoardWriteApi {

    private final BoardWriteService boardWriteService;

    @PostMapping("/{userId}/write")
    public ApiResult<Long> writeBoard(@PathVariable Long userId,
                                      @RequestBody BoardWriteRequest boardWriteRequest) {
        try {
            Long boardId = boardWriteService.writeBoard(userId, boardWriteRequest);
            return ApiResult.succeed(boardId);
        } catch (Exception e) {
            log.error(e.getMessage());
            return ApiResult.failed(e.getMessage());
        }
    }
}
  • BoardFindApi와 마찬가지로 해당하는 비즈니스 로직의 서비스 클래스를 사용하여 필요한 정보를 반환하도록 합니다. 특히 try-catch를 사용할 경우, 위처럼 에러를 확인만 하는 행위는 지양해야합니다.
  • BoardUpdateApiBoardDeleteApi 또한 필요한 매개변수서비스 클래스를 사용하여 비슷하게 구현했기 때문에 생략하겠습니다.
profile
열심히 해보자9999

0개의 댓글