-
[3주차] CRUD(by JPA) 설계 후 Postman으로 실행하기SpringBoot 2023. 11. 28. 14:28
꽤나... 오래걸림
개념이 제대로 잡혀있지 않음을 깨달았다
이 그림이 전체적 파일 구성과 이해에 도움을 준 거 같아서 첨부
presentation layer(controller) / business layer(service) / persistence layer(repository)
세가지 간의 흐름에 대한 이해가 필요하다
Board.java
package gdsctuk.sbbasic.sptingbootstudybasic.entity; import lombok.*; import jakarta.persistence.*; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; @Getter @Setter @NoArgsConstructor @Entity @EntityListeners(AuditingEntityListener.class) @Table(name = "Board") public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(nullable = false, updatable = false) //@OneToMany(mappedBy = "bId", cascade = CascadeType.ALL) private Long boardId; @Column(length = 40, nullable = false) private String boardTitle; @Column(length = 10000, nullable = false) private String boardContent; @Column(length = 20, nullable = false) private String boardWriter; @CreatedDate private LocalDateTime boardWriteTime; @LastModifiedDate private LocalDateTime boardEditTime; private Boolean isDeleted; // Null default - soft delete 판별용 // 값 주입을 위해 사용; builder @Builder public Board(Long boardId, String boardTitle, String boardContent, String boardWriter, LocalDateTime boardWriteTime, LocalDateTime boardEditTime) { this.boardId = boardId; this.boardTitle = boardTitle; this.boardContent = boardContent; this.boardWriter = boardWriter; this.boardWriteTime = boardWriteTime; this.boardEditTime = boardEditTime; } public void updateBoard(String boardTitle, String boardContent) { this.boardId = boardId; this.boardTitle = boardTitle; this.boardContent = boardContent; } public void delete() { this.isDeleted = true; } }
Response Dto는 여러 개를 만들어서 용도에 따라 사용하였다
BoardId가 기본키이기 때문에 해당 값을 반환해 주는 DTO / Board 목록을 조회할 때 list 형으로 반환하는 DTO로 추가
RequestDto
필요로 하는 요소가 달라서 기본 RequestDto와 업데이트 시에 이용하는 dto로 분리해서 활용하였다.
BoardRepository.java(interface) - 메서드를 사용하기 위해 구성, 이때 JPA가 기본 제공하는 메서드 사용이 가능하다
package gdsctuk.sbbasic.sptingbootstudybasic.repository; import gdsctuk.sbbasic.sptingbootstudybasic.entity.Board; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; import java.util.Optional; //반환 형태, method 명을 미리 선언 - interface public interface BoardRepository extends JpaRepository<Board, Long>{ Board save(Board board); Optional<Board> findById(Long id); List<Board> findAll(); // method - 반환값 List(Board) }
BoardMapper.java - builder 패턴을 활용하여 mapping 구성
package gdsctuk.sbbasic.sptingbootstudybasic.mapper; import gdsctuk.sbbasic.sptingbootstudybasic.dto.BoardRequestDto; import gdsctuk.sbbasic.sptingbootstudybasic.dto.BoardResponseDto; import gdsctuk.sbbasic.sptingbootstudybasic.dto.BoardResponseIdDto; import gdsctuk.sbbasic.sptingbootstudybasic.dto.BoardResponseListDto; import gdsctuk.sbbasic.sptingbootstudybasic.entity.Board; import org.springframework.stereotype.Component; import java.util.List; import java.util.stream.Collectors; @Component public class BoardMapper { public Board toEntity(BoardRequestDto request) { // entity에 알맞게 mapping해주기 위해 선언 - 보드라는 엔티티에 매핑해 주기 위해 return Board.builder() .boardTitle(request.getBoardTitle()) .boardContent(request.getBoardContent()) .boardWriter(request.getBoardWriter()) // nullable = false 값들은 모두 포함 .build(); } public BoardResponseIdDto toResponseId(Board board){ return BoardResponseIdDto.builder() .id(board.getBoardId()) .build(); } public BoardResponseDto toResponse(Board board) { return BoardResponseDto.builder() .boardId(board.getBoardId()) .boardTitle(board.getBoardTitle()) .boardContent(board.getBoardContent()) .boardEditTime(board.getBoardEditTime()) .build(); } public BoardResponseListDto toListResponse(List<Board> boardList) { List<BoardResponseDto> boardResponseList = boardList.stream().map(this::toResponse).collect(Collectors.toList()); // 순차 method/클래스 내의 toResponse 사용 return BoardResponseListDto.builder() .boardList(boardResponseList) .build(); } }
BoardService.java - 비즈니스 로직을 수행하고, 알맞은 정보로 가공하는 부분
package gdsctuk.sbbasic.sptingbootstudybasic.service; import gdsctuk.sbbasic.sptingbootstudybasic.dto.*; import gdsctuk.sbbasic.sptingbootstudybasic.entity.Board; import gdsctuk.sbbasic.sptingbootstudybasic.mapper.BoardMapper; import gdsctuk.sbbasic.sptingbootstudybasic.repository.BoardRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service @RequiredArgsConstructor public class BoardService { // request가 보드에 들어갈 수 있는 mapper 짜서 board rep save 하도록 private final BoardRepository boardRepository; //private final 선언해야 17번 의존성 주입 가능 private final BoardMapper boardMapper; // 게시글 생성 public BoardResponseIdDto createBoard(BoardRequestDto request) { Board board = boardRepository.save(boardMapper.toEntity(request)); // 값 저장 BoardResponseIdDto boardResponseIdDto = boardMapper.toResponseId(board); //return boardMapper.toResponseId(board); return boardResponseIdDto; } // 게시글 List 조회 public BoardResponseListDto findAllBoard() { List<Board> boards = boardRepository.findAll(); // List는 s 붙여서 처리 많음 return boardMapper.toListResponse(boards); } // 게시글 하나 조회 public BoardResponseDto findOneBoard(Long id) { Board board = boardRepository.findById(id) .orElseThrow(IllegalStateException::new); return boardMapper.toResponse(board); } // 게시글 수정 @Transactional public BoardResponseDto updateBoard(BoardUpdateDto request) { Board board = boardRepository.findById(request.getBoardId()) .orElseThrow(IllegalStateException::new); board.updateBoard(request.getBoardTitle(), request.getBoardContent()); return boardMapper.toResponse(board); } // 게시글 삭제 @Transactional public BoardResponseDto deleteBoard(Long id) { Board board = boardRepository.findById(id) .orElseThrow(IllegalStateException::new); board.delete(); return boardMapper.toResponse(board); } }
이때 detele의 경우 soft delete를 활용하였다.
BoardController.java - 사용자의 요청을 처리 후 지정된 View에 모델 객체를 넘겨주는 역할(코딩하는 어린 콩)
@RequiredArgsConstructor @RestController @RequestMapping("/boards") public class BoardController { private final BoardService boardService; private final BoardMapper boardMapper; @PostMapping(value = "/create") public ResponseEntity<BoardResponseIdDto> createBoard(@RequestBody BoardRequestDto request){ return ResponseEntity.ok(boardService.createBoard(request)); } @GetMapping("/list") public ResponseEntity<BoardResponseListDto> findALlBoard() { return ResponseEntity.ok(boardService.findAllBoard()); } @GetMapping("/find/{id}") public ResponseEntity<BoardResponseDto> findById(@PathVariable Long id) { return ResponseEntity.ok(boardService.findOneBoard(id)); } @PutMapping("/update/{id}") public ResponseEntity<BoardResponseDto> updateBoard(@RequestBody BoardUpdateDto request) { return ResponseEntity.ok(boardService.updateBoard(request)); } @DeleteMapping("/delete/{id}") public ResponseEntity<BoardResponseDto> deleteBoard(@PathVariable Long id) { return ResponseEntity.ok(boardService.deleteBoard(id)); } }
Postman 테스트
Create
List(FindAll)
* null 값으로 보이는 부분은 오류가 아니고 dto/mapper에서 반환을 안 해줘서 존재하지 않는 것이다
FindOne
Update
아이디, 제목, 내용 값을 받아서 수정해 주는 걸로 설계하였는데 아이디 값은 일단은 참조 값으로 url에 활용해야 해서 집어넣은 거긴 한데 업데이트하는 부분에서는 제외해야 할 것 같다(무결성 문제 등)
Delete
Postman에서 삭제 요청 후 MySQL에 접속해서 Board를 조회해 보니
id 값이 2인 행의 is_deleted 값이 1(true)로 soft delete가 이루어짐을 확인해 볼 수 있었다
이처럼 soft delete는 실제로 행을 삭제하는 것이 아닌, is_delete라는 boolean 속성의 값을 true로 반환하여
delete됨을 labeling 하는 방법이라고 이해하면 될 것 같다
'SpringBoot' 카테고리의 다른 글
ch2 - Spring Data JPA (0) 2023.12.21 [5주차] 댓글 CRUD 구현 (0) 2023.12.04 [4주차] 지난 피드백 반영, Git에 코드 올리기 (0) 2023.11.30 [2주차]DB 설계(by JPA) (1) 2023.11.12 [1주차] 개발 환경 세팅(+ DB 연동) (0) 2023.11.06