어제 오늘 내일

[Spring Boot 입문 - 9] 게시판(CRUD) 기능 만들기 - 백엔드 API 구현 본문

IT/SpringBoot

[Spring Boot 입문 - 9] 게시판(CRUD) 기능 만들기 - 백엔드 API 구현

hi.anna 2026. 2. 14. 00:59

지난 시간까지 프로젝트 환경 설정을 모두 마쳤습니다. 이제 드디어 게시판의 핵심 기능을 구현할 차례입니다.

개발 용어로는 CRUD라고 하는데요, 각각 Create(생성), Read(조회), Update(수정), Delete(삭제)를 의미합니다. Spring Boot와 JPA를 사용하면 복잡한 SQL 쿼리 없이도 이 기능들을 아주 쉽고 빠르게 구현할 수 있습니다.

이번 포스팅에서는 화면(HTML)을 만들기 전에, 데이터를 처리하는 API 서버 역할을 먼저 완성해 보겠습니다.


📂 패키지 구조 잡기

코드를 작성하기 전, com.example.board 패키지 아래에 다음과 같이 패키지를 미리 나누어 두면 관리가 편합니다.

  • domain.entity : DB 테이블과 매핑되는 클래스
  • domain.repository : DB 접근 도구
  • dto : 데이터 전달 객체
  • service : 비즈니스 로직
  • controller : 웹 요청 처리

Step 1. 메인 실행 파일 작성 (BoardApplication.java)

프로젝트의 시작점입니다. 보통 자동 생성되지만, 혹시 없다면 최상위 패키지에 만들어 주세요.

  • 위치: src/main/java/com/example/board/BoardApplication.java
package com.example.board;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing // JPA Auditing 활성화 (생성일/수정일 자동 기록을 위해 미리 추가)
@SpringBootApplication
public class BoardApplication {

    public static void main(String[] args) {
        SpringApplication.run(BoardApplication.class, args);
    }
}

Step 2. Entity 클래스 (Board.java)

DB 테이블과 1:1로 매핑되는 클래스입니다. JPA에서는 이 클래스가 곧 테이블 정의서입니다.

  • 위치: domain/entity/Board.java
package com.example.board.domain.entity;

import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor // 기본 생성자 자동 추가
@Entity // DB 테이블과 연결
public class Board {

    @Id // PK (Primary Key)
    @GeneratedValue(strategy = GenerationType.IDENTITY) // Auto Increment
    private Long id;

    @Column(length = 500, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    private String author;

    @Builder // 빌더 패턴 클래스 생성
    public Board(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }

    // 게시글 수정 (Setter 대신 명확한 메소드 사용)
    public void update(String title, String content) {
        this.title = title;
        this.content = content;
    }
}

Tip: Entity 클래스에서는 절대 Setter를 만들지 않습니다. 대신 update 같이 명확한 목적을 가진 메소드를 추가하여 데이터를 변경합니다.


Step 3. Repository 인터페이스 (BoardRepository.java)

DB에 접근하여 데이터를 저장하고 조회하는 역할을 합니다. MyBatis의 DAO와 비슷합니다.

  • 위치: domain/repository/BoardRepository.java
package com.example.board.domain.repository;

import com.example.board.domain.entity.Board;
import org.springframework.data.jpa.repository.JpaRepository;

// <Entity 클래스, PK 타입> 상속 시 기본 CRUD 메소드 자동 생성
public interface BoardRepository extends JpaRepository<Board, Long> {
}

Step 4. DTO 클래스 만들기

"Entity를 컨트롤러에서 바로 쓰면 안 되나요?"
네, 안 됩니다. Entity는 DB와 밀접한 핵심 클래스이므로, 화면이나 API 요청 스펙에 맞춰 자주 변경되는 뷰 로직과 분리해야 합니다.

1. 등록 요청 DTO (dto/BoardSaveRequestDto.java)

package com.example.board.dto;

import com.example.board.domain.entity.Board;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class BoardSaveRequestDto {
    private String title;
    private String content;
    private String author;

    public Board toEntity() {
        return Board.builder()
                .title(title)
                .content(content)
                .author(author)
                .build();
    }
}

2. 수정 요청 DTO (dto/BoardUpdateRequestDto.java)

package com.example.board.dto;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class BoardUpdateRequestDto {
    private String title;
    private String content;
}

3. 응답 조회 DTO (dto/BoardResponseDto.java)

package com.example.board.dto;

import com.example.board.domain.entity.Board;
import lombok.Getter;

@Getter
public class BoardResponseDto {
    private Long id;
    private String title;
    private String content;
    private String author;

    public BoardResponseDto(Board entity) {
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.content = entity.getContent();
        this.author = entity.getAuthor();
    }
}

Step 5. Service (BoardService.java)

핵심 비즈니스 로직과 트랜잭션을 관리합니다.

  • 위치: service/BoardService.java
package com.example.board.service;

import com.example.board.domain.entity.Board;
import com.example.board.domain.repository.BoardRepository;
import com.example.board.dto.BoardResponseDto;
import com.example.board.dto.BoardSaveRequestDto;
import com.example.board.dto.BoardUpdateRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
public class BoardService {
    private final BoardRepository boardRepository;

    // 1. 글 등록
    @Transactional
    public Long save(BoardSaveRequestDto requestDto) {
        return boardRepository.save(requestDto.toEntity()).getId();
    }

    // 2. 글 수정
    @Transactional
    public Long update(Long id, BoardUpdateRequestDto requestDto) {
        Board board = boardRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + id));

        // Dirty Checking: 트랜잭션 안에서 값만 변경하면 자동으로 update 쿼리가 실행됨
        board.update(requestDto.getTitle(), requestDto.getContent());

        return id;
    }

    // 3. 글 조회
    public BoardResponseDto findById(Long id) {
        Board entity = boardRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id=" + id));

        return new BoardResponseDto(entity);
    }
}

Step 6. Controller (BoardApiController.java)

외부의 요청을 받는 API 컨트롤러입니다.

  • 위치: controller/BoardApiController.java
package com.example.board.controller;

import com.example.board.dto.BoardResponseDto;
import com.example.board.dto.BoardSaveRequestDto;
import com.example.board.dto.BoardUpdateRequestDto;
import com.example.board.service.BoardService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RequiredArgsConstructor
@RestController
public class BoardApiController {

    private final BoardService boardService;

    // 등록 API
    @PostMapping("/api/v1/posts")
    public Long save(@RequestBody BoardSaveRequestDto requestDto) {
        return boardService.save(requestDto);
    }

    // 수정 API
    @PutMapping("/api/v1/posts/{id}")
    public Long update(@PathVariable Long id, @RequestBody BoardUpdateRequestDto requestDto) {
        return boardService.update(id, requestDto);
    }

    // 조회 API
    @GetMapping("/api/v1/posts/{id}")
    public BoardResponseDto findById(@PathVariable Long id) {
        return boardService.findById(id);
    }
}

✅ 테스트 해보기

이제 BoardApplication.java를 실행(Run)한 뒤, Postman 등을 이용해 API를 테스트해 봅시다.

  1. 게시글 등록 (POST)
  • URL: http://localhost:8080/api/v1/posts
  • Body (JSON):
    {
      "title": "테스트 게시글",
      "content": "CRUD 테스트 중입니다.",
      "author": "홍길동"
    }
    

```

  1. 게시글 조회 (GET)
  • URL: http://localhost:8080/api/v1/posts/1
  • 결과: 방금 등록한 JSON 데이터가 리턴되면 성공!

여기까지 따라오셨다면 백엔드 CRUD 기능 구현은 완료되었습니다!

 

 

 

반응형
Comments