# Spring Boot CRUD API ๊ตฌํ˜„ (1) - ์š”๊ตฌ์‚ฌํ•ญ ๋ถ„์„, ๋ฐ์ด๋ฒ„ํ…Œ์ด์Šค ์„ค๊ณ„, ๋„๋ฉ”์ธ ์„ค๊ณ„
Study Repository

Spring Boot CRUD API ๊ตฌํ˜„ (1) - ์š”๊ตฌ์‚ฌํ•ญ ๋ถ„์„, ๋ฐ์ด๋ฒ„ํ…Œ์ด์Šค ์„ค๊ณ„, ๋„๋ฉ”์ธ ์„ค๊ณ„

by rlaehddnd0422

๊ธฐ๋ณธ์ ์ธ ๊ฒŒ์‹œํŒ API๋ฅผ ์„ค๊ณ„ํ•จ์— ์•ž์„œ ์š”๊ตฌ์‚ฌํ•ญ์„ ๋ถ„์„ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ๋„๋ฉ”์ธ์„ ์„ค๊ณ„ํ•ด๋ด…์‹œ๋‹ค.


์š”๊ตฌ์‚ฌํ•ญ

  • ๊ธฐ๋ณธ๊ธฐ๋Šฅ
  • ํšŒ์› ํ…Œ์ด๋ธ”์— ๋“ฑ๋ก๋œ ์‚ฌ๋žŒ๋งŒ ๊ฒŒ์‹œ๊ธ€๊ณผ ๊ฒŒ์‹œ๊ธ€์˜ ๋Œ“๊ธ€์„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์Œ.
  • ํšŒ์› ๊ฐ€์ž…
    • ์•„์ด๋””๋Š” ํŠน์ˆ˜๋ฌธ์ž๋ฅผ ์ œ์™ธํ•œ 4~20์ž๋ฆฌ
    • ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 8~16์ž ์˜๋ฌธ ๋Œ€์†Œ๋ฌธ์ž, ์ˆซ์ž ํŠน์ˆ˜๋ฌธ์ž๋ฅผ ๋ฐ˜๋“œ์‹œ ํฌํ•จ
    • ๋‹‰๋„ค์ž„์€ ํŠน์ˆ˜๋ฌธ์ž ์ œ์™ธ 2~10์ž๋ฆฌ
    • ์ด๋ฉ”์ผ์€ ์ด๋ฉ”์ผ ํ˜•์‹์„ ๊ฐ–์ถ”์–ด์•ผ ํ•จ (~~@naver.com) 
  • ๊ฒŒ์‹œํŒ
    • ๊ธฐ๋ณธ์ ์ธ CRUD ( Create(์ƒ์„ฑ), Read(์กฐํšŒ), Update(์ˆ˜์ •), Delete(์‚ญ์ œ) )
  • ๋Œ“๊ธ€
    • ๊ฒŒ์‹œํŒ์— ๋Œ€ํ•œ ๊ธฐ๋ณธ์ ์ธ CRUD ( Create(์ƒ์„ฑ), Read(์กฐํšŒ), Update(์ˆ˜์ •), Delete(์‚ญ์ œ) 
  • ์„œ๋ธŒ๊ธฐ๋Šฅ
    • Spring Security JWT ๊ธฐ์ˆ ์„ ๋„์ž…ํ•œ ๋กœ๊ทธ์ธ ๋ฐ ํšŒ์›๊ฐ€์ž… ( ์ถ”ํ›„ ์ถ”๊ฐ€์˜ˆ์ • )
      • Oauth 2.0 ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ํ™•์žฅ : ๋ณ„๋„ ํšŒ์›๊ฐ€์ž… ์—†์ด ๊ตฌ๊ธ€, ๋„ค์ด๋ฒ„, ์นด์นด์˜ค ์•„์ด๋””๋กœ ๋กœ๊ทธ์ธ
      • ๋Œ“๊ธ€ ๋ฐ ๊ฒŒ์‹œ๊ธ€ ์—…๋กœ๋“œ๋Š” ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋งŒ ์ž‘์„ฑ ๊ฐ€๋Šฅ
    • View ์ถ”๊ฐ€ ( ๋จธ์Šคํ…Œ์น˜ )
    • ๊ตญ์ œํ™” ๋ฐ ๋ฉ”์‹œ์ง€ ๊ธฐ๋Šฅ 
    • ๊ฒŒ์‹œํŒ ์กฐํšŒ์ˆ˜ ๊ธฐ๋Šฅ ์ถ”๊ฐ€

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„

Member

  • member_id : Long (PK)
  • username : String
  • password : String
  • nickname : String
  • email : String 
  • role : Enum Type (USER, ADMIN)

Board

  • board_id : Long (PK)
  • member_id : Long
  • content : String
  • title : String
  • comments : List<String>

Comment

  • comment_id : Long (PK)
  • board_id : Long
  • member_id : Long
  • comment : String

BaseEntity

: Member, Board, Comment ํ…Œ์ด๋ธ”์—์„œ ๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ๋กœ ๋“ฑ๋ก์ผ, ์ˆ˜์ •์ผ์„ ํฌํ•จ. ์‹ค์ œ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ํ…Œ์ด๋ธ”๋กœ ๋“ฑ๋กํ•˜์ง„ ์•Š์Œ.


์—ฐ๊ด€๊ด€๊ณ„ ๋งคํ•‘

1. ํšŒ์›์€ ๊ธ€์„ ์—ฌ๋Ÿฌ ๊ฐœ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

:  Member (1) ↔๏ธ  Board(ๅคš) : ์ผ๋Œ€๋‹ค ๊ด€๊ณ„ ( OneToMany )

:  Board(ๅคš) ↔๏ธ Member : ๋‹ค๋Œ€์ผ ๊ด€๊ณ„ ( ManyToOne)

 

2. ๊ธ€์—๋Š” ๋Œ“๊ธ€์ด ์—ฌ๋Ÿฌ๊ฐœ ๋‹ฌ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

:  Board (1) ↔๏ธ  Comment (ๅคš) : ์ผ๋Œ€๋‹ค ๊ด€๊ณ„ ( OneToMany )

:  Comment(ๅคš) ↔๏ธ Board : ๋‹ค๋Œ€์ผ ๊ด€๊ณ„ ( ManyToOne)

 

3. ํšŒ์›์€ ๋Œ“๊ธ€์„ ์—ฌ๋Ÿฌ๊ฐœ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

:  Member (1) ↔๏ธ Comment(ๅคš) : ์ผ๋Œ€๋‹ค ๊ด€๊ณ„ ( OneToMany )

:  Comment(ๅคš) ↔๏ธ Member : ๋‹ค๋Œ€์ผ ๊ด€๊ณ„ ( ManyToOne)

 

๋„๋ฉ”์ธ ์„ค๊ณ„ ๋ฐ ํ…Œ์ด๋ธ” ๋งคํ•‘

Member

package dongwoongkim.crud.domain;

import dongwoongkim.crud.domain.base.BaseEntity;
import dongwoongkim.crud.dto.member.MemberEditRequestDto;
import dongwoongkim.crud.dto.member.MemberRequestDto;
import dongwoongkim.crud.repository.MemberRepository;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(nullable = false)
    private String username;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false)
    private String nickname;

    @Column(nullable = false)
    private String email;

    @Column(nullable = false)
    @Enumerated(EnumType.STRING)
    private MemberRole role;

    @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE)
    private List<Comment> comments = new ArrayList<>();

    @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE)
    private List<Board> boards = new ArrayList<>();

    public Member(MemberRequestDto memberRequestDto) {
        this.username = memberRequestDto.getUsername();
        this.password = memberRequestDto.getPassword();
        this.nickname = memberRequestDto.getNickname();
        this.email = memberRequestDto.getEmail();
        this.role = memberRequestDto.getRole();
    }

    public Member(String username, String password, String nickname, String email) {
        this.username = username;
        this.password = password;
        this.nickname = nickname;
        this.email = email;
    }

    public void updateMember(MemberEditRequestDto memberEditRequestDto) {
        this.password = memberEditRequestDto.getPassword();
        this.email = memberEditRequestDto.getEmail();
        this.nickname = memberEditRequestDto.getNickname();
    }

}
  • @Entity : ์‹ค์ œ ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘ ( @Table(name="member") ์ƒ๋žต ์‹œ ์ž๋™์œผ๋กœ ํด๋ž˜์Šค ์ด๋ฆ„์˜ ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘ )
  • @OneToMany : ์ผ๋Œ€๋‹ค ๋งคํ•‘ ( comment, board )
    • cascade = CascadeType.REMOVE ์˜ต์…˜ : ํšŒ์›์ด ์‚ญ์ œ๋  ๋•Œ ํ•ด๋‹น ๋ฉค๋ฒ„์˜ PK๋ฅผ FK๋กœ ๊ฐ€์ง„ ์—ฐ๊ด€๋œ ํ…Œ์ด๋ธ”์˜ ์ปฌ๋Ÿผ๋“ค๋„ ํ•จ๊ป˜ ์‚ญ์ œ ํ•˜๋„๋ก ์„ค์ • 
  • @Column(nullable = false) : ๋„ ๊ฐ’ ํ—ˆ์šฉ X

MemberRole

package dongwoongkim.crud.domain;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum MemberRole {
    USER("USER"),
    ADMIN("ADMIN");
    private final String value;
}
  • EnumType : ADMIN, USER

Board

package dongwoongkim.crud.domain;

import dongwoongkim.crud.domain.base.BaseEntity;
import dongwoongkim.crud.dto.board.BoardEditRequestDto;
import dongwoongkim.crud.dto.board.BoardRequestDto;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Board extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "BOARD_ID")
    private Long id;

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

    @Column(nullable = false)
    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    @OneToMany(mappedBy = "board")
    @OrderBy("id asc")
    private List<Comment> comments = new ArrayList<>();

    public Board(String title, String content, Member member) {
        this.title = title;
        this.content = content;
        this.member = member;
    }

    public static Board toEntity(BoardRequestDto boardRequestDto) {
        return new Board(boardRequestDto.getTitle(), boardRequestDto.getContent(), boardRequestDto.getMember());
    }

    public void updateBoard(BoardEditRequestDto boardEditRequestDto) {
        this.title = boardEditRequestDto.getTitle();
        this.content = boardEditRequestDto.getContent();
    }
}
  • @ManyToOne : ๋‹ค๋Œ€์ผ ๋งคํ•‘ ( board -> member ) 
    • @JoinColumn(name="member_id") : ์™ธ๋ž˜ ํ‚ค๋กœ Member ํ…Œ์ด๋ธ”์˜ PK์ธ member_id ์‚ฌ์šฉ 
    • fetch = FetchType.LAZY : ์ง€์—ฐ๋กœ๋”ฉ์œผ๋กœ ์„ค์ •ํ•˜์—ฌ Member ์—”ํ‹ฐํ‹ฐ ์ง์ ‘ ์‚ฌ์šฉ ์‹œ ํ”„๋ก์‹œ๋กœ ์‹ค์ œ ๊ฐ์ฒด ํ˜ธ์ถœ
  • @OneToMany : ์ผ๋Œ€ ๋‹ค ๋งคํ•‘ ( board -> comment )

Comment

package dongwoongkim.crud.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import dongwoongkim.crud.domain.base.BaseEntity;
import dongwoongkim.crud.dto.comment.CommentRequestDto;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Comment extends BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String comment;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "BOARD_ID")
    private Board board;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "MEMBER_ID")
    private Member member;

    public Comment(String comment, Board board, Member member) {
        this.comment = comment;
        this.board = board;
        this.member = member;
    }

    public static Comment toEntity(CommentRequestDto commentRequestDto) {
        return new Comment(commentRequestDto.getComment(),commentRequestDto.getBoard(),commentRequestDto.getMember());
    }

    public void update(String comment) {
        this.comment = comment;
    }

}
  • @ManyToOne : ๋‹ค๋Œ€์ผ ๋งคํ•‘ ( Commnet -> Board )  
  • @ManyToOne : ๋‹ค๋Œ€์ผ ๋งคํ•‘ ( Comment -> Member)
~ToMany ๋Š” ์ง€์—ฐ๋กœ๋”ฉ(LAZY)์ด default๋กœ ์„ค์ •๋˜์žˆ์ง€๋งŒ, ~ToOne์€ ์ฆ‰์‹œ๋กœ๋”ฉ์ด default๋กœ ์„ค์ •๋˜์–ด ์žˆ๊ธฐ ๋–„๋ฌธ์—
๋ชจ๋‘ fetch ํƒ€์ž…์„ LAZY๋กœ ์„ค์ •ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

BaseEntity

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModified;

    @PreUpdate
    public void onPreUpdate() {
        this.lastModified = LocalDateTime.now();
    }

}
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”๋กœ ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์˜ค๋กœ์ง€ ์—”ํ‹ฐํ‹ฐ๋“ค์—๊ฒŒ ์ƒ์†ํ•˜๊ธฐ ์œ„ํ•œ ๋ชฉ์ ์œผ๋กœ ๋งŒ๋“  ์—”ํ‹ฐํ‹ฐ.
    • @Entity๋ฅผ ๋ถ™์—ฌ์ฃผ์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • @CreatedDate : ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ ์ž๋™์œผ๋กœ ํ˜„์žฌ ๋‚ ์งœ ๋ฐ ์‹œ๊ฐ„์œผ๋กœ ์ƒ์„ฑ๋˜๋Š” ์ปฌ๋Ÿผ
  • @LastModifiedDate : ๋ฐ์ดํ„ฐ๊ฐ€ ์ˆ˜์ •๋ ๋•Œ ์ž๋™์œผ๋กœ ํ˜„์žฌ ๋‚ ์งœ ๋ฐ ์‹œ๊ฐ„์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜๋Š” ์ปฌ๋Ÿผ 

๋ธ”๋กœ๊ทธ์˜ ์ •๋ณด

Study Repository

rlaehddnd0422

ํ™œ๋™ํ•˜๊ธฐ