[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

1개의 댓글

comment-user-thumbnail
2025년 1월 23일

Our Delhi Escort Service is dedicated to providing a truly sexual experience for our clients. Our hot models are carefully selected for their beauty, intelligence, and charm, ensuring that your encounter is not only visually stunning but also intellectually stimulating. Book now and let us show you why we're the best in the business.

답글 달기