상세 컨텐츠

본문 제목

[스프링 실습] 게시판 구현

스프링/[스터디] 스프링 실습

by 안애옹 2023. 12. 1. 22:59

본문

게시판을 구현하기 위해서 필요한 클래스들은 다음과 같다

 

BoardController 사용자의 입력을 처리하고 Service에 구현되어 있는 메서드들(비즈니스 로직)을 호출

BoardDto Controller를 통해서 받아온 사용자의 입력 값을 실제 DB에 넣기 위해, 사용자의 입력 값을 잠시 넣어두는 상자 역할

BoardEntity DB table 역할

BoardRepository Spring Data JPA 확장. DB와 통신하여 CRUD 작업 수행

BoardService 비즈니스 로직 구현


📄 BoardEntity

 

package com.example.demo.Entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;    // import persistence 인지 확인해주기

// entity 라는걸 명시해주기 위해서
@Entity
@Getter
@Setter
public class Board {

    @Id // 기본키인거 알려주기 위해
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 아이디가 1씩 증가하도록 auto increment
    private Long id;
    // <Long 타입 사용한 이유>
    // Long 타입은 일반적으로 DB 에서 사용되는 기본 키 타입 중 하나
    // 많은 DB 에서 기본 키가 숫자형이고, 자동으로 증가하는 식별자가 필요한데
    // Long 은 큰 범위의 정수를 다룰 수 있고, 많은 레코드를 처리하는 데 적합한 타입

    private String title;
    private String content;
}

 

 


📄 BoardDto

 

package com.example.demo.Dto;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter

// Dto = 상자
// controller 에서, DB 에 넣을 새로운 데이터를 받아올 때, Dto 라는 상자에 담아오는 것
// ######## 실제 데이터베이스에 넣기 위해서 잠깐 담아두는 (거쳐가는) 상자의 느낌
public class BoardDto {
    private String title;
    private String content;
}

 


📄 BoardRepository

 

package com.example.demo.Repository;

import com.example.demo.Entity.Board;
import org.springframework.data.jpa.repository.JpaRepository;

/*
<JpaRepository>
Spring Data JPA 에서 제공하는 인터페이스
데이터베이스와 상호 작용하기 위한 다양한 메서드들을 기본적으로 제공해 줌
이 인터페이스를 상속받으면 'Board' 엔티티와 관련된 CRUD 기능을 사용할 수 있게 됨

node 는 crud 해올 때, 직접 구현해야 하는데
Spring 은 JPA 쓰면 (extends JpaRepository 를 해주면), 쉽고 빠르게 데이터를 가져올 수 있음

<JPA 쓸 때 지켜야 하는 점>
Spring JPA 에서 제공하는 메서드 이름 규칙 잘 지켜야 함

<Board, Long>
Board 는 엔티티 타입
Long 은 엔티티의 기본 키의 타입
 */


public interface BoardRepository extends JpaRepository<Board, Long> {
    /*
    <플젝할 때 많이 쓰이는 것 예시>

    List<Board> findByTitle(String title);
    List<Board> findByTitleAndContent(String title, String Content);

    @Query("SELECT b From Board b WHERE b.title = :title")
    List<Board> findByTitle(@Param("title") String title);
    -> Board 엔티티에서 title 필드를 기준으로 검색하는 쿼리

    +)
    여기에서 @Query 어노테이션은 JPQL 쿼리를 정의하고 있음
    ': title' 은 JPQL 에서 파라미터를 나타내고, 이 파라미터는 findByTitle 메서드의 매개변수인 'title' 에 의해 전달 됨

    @Param("title") 어노테이션은 메서드 파라미터와 JPQL 쿼리의 파라미터를 매핑해 줌
    즉, ':title' 은 'title' 매개변수의 값으로 대체 됨
    이렇게 함으로써 Spring Data JPA 는 메서드 호출 시 전달된 값으로 쿼리를 실행 함

    +)
    <@Query 어노테이션>
    Spring Data JPA 에서 사용되며, JPQL (Query DSL)이나 네이티브 쿼리를 사용하여 데이터베이스에서 데이터를 검색하고 조작하기 위해 사용 됨
    @Query 어노테이션을 사용하면 복잡한 검색 조건이나 특정 비즈니스 로직에 맞게 쿼리를 작성할 수 있음
    주로 Repository 인터페이스에 선언하며, 해당 레포지토리에서 특정한 검색이나 조작을 위한 쿼리를 정의할 때 사용

    다시 말해서,
    Spring Data JPA 에서는 레포지토리 인터페이스에 정의된 메서드 이름 규칙에 따라 자동으로 쿼리를 생성해주는 기능이 있음
    그러나 이런 자동 생성된 쿼리로는 원하는 로직을 충분히 표현하기 어려운 경우가 있고, 그럴 때 이런 @Query 어노테이션을 사용하여 직접 JPQL 이나 네이티브 쿼리를 정의할 수 있음

    */
}

 


📄 BoardService

 

package com.example.demo.Service;

import com.example.demo.Dto.BoardDto;
import com.example.demo.Entity.Board;
import com.example.demo.Repository.BoardRepository;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

// Service 인거 알려주기 위해서
@Service
public class BoardService {
    private final BoardRepository boardRepository;
    public BoardService (BoardRepository boardRepository) {
        this.boardRepository = boardRepository;
    }

    public List<Board> getAllBoards() {
        return boardRepository.findAll();
    }

    /*
    <optional>
    값이 있을 수도 있고 없을 수도 있는 컨테이너 객체
    메서드가 특정 조건에 따라서 null 을 반환하는 경우, 이로 인해 NullPointerException 이 발생할 수 있는데,
    Optional 은 이러한 상황에서 명시적으로 값이 없는 상태를 나타내기 위해 사용됨

    Optional<String> optional = Optional.of("안녕");
    이런 식으로 사용

    if (optional.isPresent()) {
    }
    있는지 없는지 확인하기 위해 사용

    String get = optional.orElse("안녕!");
    이  값이 존재하면 안녕을, 존재하지 않으면 안녕! 을 return 가능
    즉, 값이 없을 때의 return 값 설정도 가능 -> 확장성이 많음
    */
    public Optional<Board> getBoardById(Long id) {
        return boardRepository.findById(id);
    }

    // [Create] 새로운 게시글 작성하는 함수
    public Board createBoard(BoardDto boardDto) {
        // 빈 보드 객체 만들기
        Board newBoard = new Board();

        newBoard.setTitle(boardDto.getTitle());
        newBoard.setContent(boardDto.getContent());

        // 이제 실제로 데이터를 넣어줘야 하니까
        return boardRepository.save(newBoard);
    }

    // [Update]
    // create 와의 차이점 : update 하려면 id를 알아야 함 -> id 를 받아와야 함 + Optional 사용
    // boardDto 여기에 수정해야 할 내용이 담겨있음
    public Board updateBoard(Long id, BoardDto boardDto) {

        // Optional<Board> 은 Board 타입의 객체를 감싸고 있거나, 값이 없을 때는 비어있는 'empty' Optional 객체를 나타냄
        // findByID 메서드는 주어진 ID에 해당하는 엔티티를 찾아서 Optional 로 감싼 상태로 반환해 줌
        // 이를 통해 해당 Id에 해당하는 엔티티가 존재하지 않을 경우에도 명시적으로 처리할 수 있음
        Optional<Board> optionalBoard = boardRepository.findById(id);

        // 명시적으로 null 체크를 하기 위해서
        if (optionalBoard.isPresent()) {
            Board existingBoard = optionalBoard.get();

            existingBoard.setTitle(boardDto.getTitle());
            existingBoard.setContent(boardDto.getContent());

            return boardRepository.save(existingBoard);
        }
        return null;
    }

    // [Delete]
    public void deleteBoard(Long id) {
        boardRepository.deleteById(id);
    }

}

 


📄 BoardController

 

package com.example.demo.Controller;

import com.example.demo.Dto.BoardDto;
import com.example.demo.Entity.Board;
import com.example.demo.Service.BoardService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Optional;

@RestController

@RequestMapping("/api/boards")
public class BoardController {
    private final BoardService boardService;

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

    // get 은 *특정* 값을 가져오는 게 아닌, 그냥 전체를 가져오기만 하면 될 때 사용
    @GetMapping
    public List<Board> getAllBoards() {
        return boardService.getAllBoards();
    }

    /*
    service 에서, 레포지토리에 있는 거 사용
    controller 에서, service 에 있는 거 사용
    (controller 최대한 간결하게 적는게 좋으니까, controller 너무 길어지지 않게하기 위해서)
     */

    /*
    "{/id}" 는 pathVariable 랑 관련이 있는데, 일종의 변수라고 생각하면 됨
    Long id 여기에 들어있는 게, "{/id}" 여기 url 에 들어가게 됨
     */
    // [Read] 특정 게시글 조회
    @GetMapping("{/id}")
    public ResponseEntity<Board> getBoardById(@PathVariable Long id) {
        Optional<Board> board = boardService.getBoardById(id);

        // optional 이었으므로 map, 삼항연산자 써 줌
        // id 값 있는 경우에만 -> map 호출돼서 ResponseEntity::ok 를 넣어주고 map 호출
        // id 값 없는 경우 -> orElseGet(() -> ResponseEntity.notFound().build() 호출
        // 즉, 비어있는 경우 404 notFound 페이지 호출
        return board.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
    }

    /*
    <Get vs Post>
    DB 에 넣을 때 post
    DB 에서 가져올 때 get
     */
    // [Create] 새로운 게시글 작성
    @PostMapping
    public ResponseEntity<Board> createBoard(@RequestBody BoardDto boardDto) {
        Board newBoard = boardService.createBoard(boardDto);
        return ResponseEntity.ok(newBoard);
    }

    // 1. 찾아서 2. 넣어줘야 하니까
    // 위에 두개가 합쳐진 거라고 생각
    // [Update]
    @PutMapping("/{id}")
    public ResponseEntity<Board> updateBoard(@PathVariable Long id, @RequestBody BoardDto boardDto) {
        Board updateBoard = boardService.updateBoard(id, boardDto);

        // null 이 아니어야 수정이 됐다는거니까 != 써줘야 함
        return updateBoard != null ? ResponseEntity.ok(updateBoard) : ResponseEntity.notFound().build();
    }

    // [Delete]
    @DeleteMapping("{/id}")
    public ResponseEntity<Board> deleteBoard(@PathVariable Long id) {
        boardService.deleteBoard(id);
        return ResponseEntity.noContent().build();
        // noContent() -> 응답 데이터가 없다는 걸 알려줌
    }
}

 


 

'스프링 > [스터디] 스프링 실습' 카테고리의 다른 글

[스프링 실습] 좋아요 구현  (0) 2023.12.01
[스프링 실습] 로그인 구현  (0) 2023.11.25

관련글 더보기