hayu's 개발 일지

[TIL]240322 스프링 부트와 AWS로 혼자 구현하는 웹서비스: -4장 머스테치 & CRUD 본문

스터디/spring

[TIL]240322 스프링 부트와 AWS로 혼자 구현하는 웹서비스: -4장 머스테치 & CRUD

hayu00 2024. 3. 22. 21:08

템플릿 엔진이란?

  • 지정된 템플릿 양식과 데이터가 합쳐져 HTML 문서를 출력하는 소프트웨어를 말한다.

(ex. 서버 템플릿 엔진: JSP, Freemarker 클리아언트 템플릿 엔진: 리엑트, 뷰 등)

머스테치란?

  • 수많은 언어를 지원하는 가장 심플한 템플릿이다.
  • 루비, 자바스크립트, 파이썬, PHP, 자바 등 현존하는 대부분의 언어를 지원한다.
  • 자바에서 사용될 때는 서버 템플릿 엔진으로, 자바 스크립트를 사용할 때는 클라이언트 템플릿 엔진으로 모두 사용한다.

머스테치의 장점

  • 문법이 다른 템플릿 엔진보다 심플하다.
  • 로직 코드를 사용할 수 없어 View 의 역할과 서버의 역할이 명확하게 분리된다.
  • 하나의 문법으로 클라이언트/서버 템플릿을 모두 사용 가능하다.

CRUD 코드 리펙토링(lv1 과제 코드)

  • Controller 코드
@RestController
@RequestMapping("/api")
public class BoardController {

    // JdbcTemplate 사용을 JPA 로  바꿈
    // service 생성자 주입(기존 코드에는 없었음)
    private final BoardService boardService;

    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }

    // 게시글 작성
    @PostMapping("/board")
    public ResponseEntity<CreateBoardResponseDto> createBoard(@RequestBody CreateBoardRequestDto boardRequestDto) {
        // BoardService boardService = new BoardService(jdbcTemplate); 서비스 생성자를 주입하지 않아서 인스턴스를 만들었음. -> IoD / DI 를 위반.
        CreateBoardResponseDto createBoardResponseDto = boardService.createBoard(boardRequestDto);
        return ResponseEntity.ok().body(createBoardResponseDto);
    }

    /*
     * ResponseEntity.ok()와 ResponseEntity.ok().body() 차이
     * ResponseEntity.ok()
     * 단순히 HTTP 상태 코드 200 OK를 갖는 ResponseEntity 객체 생성한다. 별도의 데이터가 포함되지 않는다.
     * OK() 안에 값을 넣으면 데이터가 반환된다.
     * 보통 추가 헤더나 상태 코드를 지정할 필요가 없는 경우 사용하는 간결하고 간단한 방법
     * ResponseEntity.ok().body()
     * HTTP 상태 코드 200 OK를 갖는 ResponseEntity 객체 생성하고 응답 본문에 헤당하는 데이터를 지정한다.
     * 상태 코드 200과 함께 특정 데이터가 반환된다.
     * 추가 헤더나 상태 코드를 설정해야하는 경우에 사용한다. (더 유연함)
     * */

    // 목록 조회
    @GetMapping("/boards")
    public ResponseEntity<List<UpdateBoardResponseDto>> getBoardsList() {
        List<UpdateBoardResponseDto> boardResponseDto = boardService.getBoardsList();
        return ResponseEntity.ok().body(boardResponseDto);
    }

    // 수정
    @PutMapping("/board/{id}")
    public ResponseEntity<UpdateBoardResponseDto> updateBoard(@PathVariable Long id, @RequestBody UpdateBoardRequestDto requestDto) {
        UpdateBoardResponseDto updateBoardResponseDto = boardService.updateBoard(id, requestDto);
        return ResponseEntity.ok().body(updateBoardResponseDto);
    }

    // 삭제
    @DeleteMapping("/board/{id}")
    public void deleteBoard(@PathVariable Long id, @RequestParam String password) { // return 값이 있었음. 삭제 기능에서는 필요하지 않음.
        //  @RequestBody BoardRequestDto requestDto 를 사용. -> @RequestBody 는 POST, PUT 만 사용해야함.
        boardService.deleteBoard(id, password);
    }
}

  • Service 코드
@Service // @Service 추가
public class BoardService {

    // BoardRepository 생성자 주입
    private final BoardRepository boardRepository;

    public BoardService(BoardRepository boardRepository) {
        this.boardRepository = boardRepository;
    }

    // 목록 작성
    @Transactional // @Transactional 추가 각각의 기능에 대한 틀내잭션 관리를 뒤해 추가.
    public CreateBoardResponseDto createBoard(CreateBoardRequestDto boardRequestDto) {

        // DB 저장
        // toEntity 를 사용하여 boardRequestDto 를 엔티티로 변환하는 코드 없이 DB에 저장.
        Board board = boardRepository.save(boardRequestDto.toEntity());

        // Entity -> ResponseDto
        return new CreateBoardResponseDto(board);
    }

    // 목록 조회
    @Transactional
    public List<UpdateBoardResponseDto> getBoardsList() {
        List<Board> boards = boardRepository.findAll();

        // 목록을 반환하는 코드
        return boards.stream()
                .map(UpdateBoardResponseDto::new)
                .toList();
    }

    // 게시글 수정
    @Transactional
    public UpdateBoardResponseDto updateBoard(Long id, UpdateBoardRequestDto requestDto) {
        // 예외 처리 코드 추가
        Board board = boardRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("찾을 수 없는 게시글입니다."));
        // NotFoundException 사용이 안됨. -> 사용하기 위해서는 @ExceptionHandler 를 사용하거나 사용자 정의 예외 클래스를 정의해야 한다.

        if (!board.getPassword().equals(requestDto.getPassword())) {
            throw new IllegalArgumentException("잘못된 비밀번호 입니다.");
        }

        board.updateBoard(requestDto.getTitle(), requestDto.getUsername(), requestDto.getPassword(), requestDto.getContents()); // 엔티티에서 매개변수로 자정한 각각의 값을 넣어줌
        /*
         * 엔티티에 매개 변수로 넣은 값에 따라 코드가 달라진다.
         * board.updateBoard(requestDto)
         * 장점: 엔티티 내에서 논리를 캡슐화하고 코드 중복을 줄인다. 그리고 단일 매개변수를 허용하여 가독성을 향상시킨다.
         * 단점: 유연성이 떨어진다. dto 와 엔티티의 결합은 바람직하지 않다.
         * board.updateBoard(requestDto.getTitle(), requestDto.getUsername(), requestDto.getPassword(), requestDto.getContents());
         * 장점: 각각 매개변수를 명시적으로 전달하여 어떤 필드인지 알 수 있다. 특정 필드를 선택적으로 업데이트하고 다른 필드는 변경하지 않아서 더 많은 유연성을 허용한다.
         * 단점: 엔티티에 새로운 필드가 추가되면 서비스에도 수정이 필요하다.
         * */

        return new UpdateBoardResponseDto(board);
    }

    // 게시글 삭제
    @Transactional
    public void deleteBoard(Long id, String password) { // return 삭제
        Board board = boardRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("찾을 수 없는 게시글입니다."));

        if (!board.getPassword().equals(password)) {
            throw new IllegalArgumentException("잘못된 비밀번호 입니다.");
        }

        boardRepository.delete(board);
    }

}