์ˆœํ™˜ ์ฐธ์กฐ์˜ ์›์ธ

Spring Boot๋Š” @ResponseBody ์–ด๋…ธํ…Œ์ด์…˜์„ ์„ ์–ธํ•  ๋•Œ ๊ฐ์ฒด๋ฅผ JSON ์ƒํƒœ๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด Jackson ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Jackson์€ ๊ฐ์ฒด๋ฅผ ์ง๋ ฌํ™”ํ•˜์—ฌ JSON ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•˜๋Š”๋ฐ, ๊ธฐ๋ณธ์ ์œผ๋กœ public ํ•„๋“œ ๋˜๋Š” getter๊ฐ€ ์žˆ๋Š” ํ•„๋“œ๋งŒ ์ง๋ ฌํ™” ๋Œ€์ƒ์œผ๋กœ ์ทจ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ๊ณผ ๊ฐ™์€ Member ํด๋ž˜์Šค๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค:

public class Member { 
    private String stringValue; 
    int intValue; 
    protected float floatValue; 
    public boolean booleanValue; 
// No Getter Setter 
}

์œ„ ํด๋ž˜์Šค์—์„œ๋Š” booleanValue๋งŒ public์ด๋ฏ€๋กœ JSON์œผ๋กœ ์ง๋ ฌํ™”๋ฉ๋‹ˆ๋‹ค.

์‹ค์ œ ์ˆœํ™˜ ์ฐธ์กฐ ๋ฌธ์ œ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์˜ˆ์‹œ

Board์™€ Post ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ†ตํ•ด ์ˆœํ™˜ ์ฐธ์กฐ ๋ฌธ์ œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

Board.java


@Entity  
@Getter  
@Setter  
@AllArgsConstructor  
@NoArgsConstructor  
public class Board {  
    @Id  
    @GeneratedValue(strategy = GenerationType.AUTO)  
    @Column(name = "board_id")  
    private Long id;  

    @Column(name = "board_name")  
    private String name;  

    @OneToMany(mappedBy = "board", cascade = CascadeType.ALL)  
    private List<Post> postList = new ArrayList<>();  
}

Post.java


@Entity  
@Data  
public class Post extends TimeEntity {  
    @Id  
    @GeneratedValue    @Column(name = "post_id")  
    private Long id;  
    @Column(nullable = false, name = "post_title")  
    private String title;   //์ œ๋ชฉ  

    @Column(nullable = false, length = 3000, name = "post_content")  
    private String content; //๋‚ด์šฉ  


    @ManyToOne(targetEntity = Board.class, fetch = FetchType.EAGER, optional = false)  
    @JoinColumn(name = "board_id", nullable = false)  
    private Board board;  
}

์ด ๊ตฌ์„ฑ์—์„œ Board์˜ postList๋ฅผ ์ง๋ ฌํ™”ํ•˜๋ฉด ๊ฐ Post์˜ board ๊ฐ์ฒด๋ฅผ ๋‹ค์‹œ ์ง๋ ฌํ™”ํ•˜๋ ค๊ณ  ํ•˜๊ณ , ์ด ๊ณผ์ •์ด ๋ฐ˜๋ณต๋˜์–ด ์ˆœํ™˜ ์ฐธ์กฐ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ˆœํ™˜ ์ฐธ์กฐ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์œผ๋กœ๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ํšจ๊ณผ์ ์ธ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. @JsonManagedReference์™€ @JsonBackReference ์‚ฌ์šฉ

@JsonManagedReference๋Š” ์ง๋ ฌํ™”์— ํฌํ•จ๋˜๋ฉฐ, @JsonBackReference๋Š” ์ง๋ ฌํ™”์—์„œ ์ œ์™ธ๋ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์ ์šฉํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

Board.java


@Entity  
@Getter  
@Setter  
@AllArgsConstructor  
@NoArgsConstructor  
public class Board {  
    @Id  
    @GeneratedValue(strategy = GenerationType.AUTO)  
    @Column(name = "board_id")  
    private Long id;  

    @Column(name = "board_name")  
    private String name;  

    @JsonManagedReference  
    @OneToMany(mappedBy = "board", cascade = CascadeType.ALL)  
    private List<Post> postList = new ArrayList<>();  
}

Post.java

@Entity  
@Data  
public class Post extends TimeEntity {  
    @Id  
    @GeneratedValue    @Column(name = "post_id")  
    private Long id;  
    @Column(nullable = false, name = "post_title")  
    private String title;   //์ œ๋ชฉ  

    @Column(nullable = false, length = 3000, name = "post_content")  
    private String content; //๋‚ด์šฉ  

    @JsonBackReference  
    @ManyToOne(targetEntity = Board.class, fetch = FetchType.EAGER, optional = false)  
    @JoinColumn(name = "board_id", nullable = false)  
    private Board board;  
}

์ˆœํ™˜ ์ฐธ์กฐ ํ•ด๊ฒฐ์„ ์œ„ํ•œ ์‹ค์ œ ์ ์šฉ ์‚ฌ๋ก€

ํ”„๋กœ์ ํŠธ์—์„œ ์‹ค์ œ๋กœ ์ˆœํ™˜ ์ฐธ์กฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์—ฌ๋Ÿฌ ๋ฐฉ๋ฒ•์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„์—์„œ ์„ค๋ช…ํ•œ @JsonManagedReference์™€ @JsonBackReference ์™ธ์—๋„, DTO๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋” ํšจ์œจ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  API ์‘๋‹ต์„ ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค.

DTO(Data Transfer Object)๋ฅผ ์‚ฌ์šฉํ•œ ์ˆœํ™˜ ์ฐธ์กฐ ํ•ด๊ฒฐ

DTO ์‚ฌ์šฉ ์˜ˆ์‹œ๋กœ Author์™€ Book์˜ ๊ด€๊ณ„๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ์˜ˆ์‹œ์—์„œ๋Š” ๊ฐ„๋‹จํ•œ ๋„์„œ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ๋•Œ Author๊ฐ€ ์—ฌ๋Ÿฌ Book์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š” ๊ด€๊ณ„๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

Author.java

@Entity
public class Author {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
    private List<Book> books = new ArrayList<>();
}

Book.java

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String genre;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id")
    private Author author;
}

AuthorDTO.java

public class AuthorDTO {
    private Long id;
    private String name;
    private List<BookDTO> books;

    public AuthorDTO(Author author) {
        this.id = author.getId();
        this.name = author.getName();
        this.books = author.getBooks().stream().map(BookDTO::new).collect(Collectors.toList());
    }
}

public class BookDTO {
    private Long id;
    private String title;
    private String genre;

    public BookDTO(Book book) {
        this.id = book.getId();
        this.title = book.getTitle();
        this.genre = book.getGenre();
    }
}

์œ„ ์˜ˆ์‹œ์—์„œ๋Š” Author์™€ Book ์‚ฌ์ด์˜ ์ˆœํ™˜ ์ฐธ์กฐ ๋ฌธ์ œ๋ฅผ DTO๋ฅผ ํ†ตํ•ด ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค. AuthorDTO์™€ BookDTO๋Š” ๊ฐ๊ฐ ํ•„์š”ํ•œ ์ •๋ณด๋งŒ์„ ํฌํ•จํ•˜๋ฏ€๋กœ, ์ง๋ ฌํ™” ๊ณผ์ •์—์„œ ์ˆœํ™˜ ์ฐธ์กฐ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋งคํ•‘ ์žฌ์„ค์ •์„ ํ†ตํ•œ ์ˆœํ™˜ ์ฐธ์กฐ ํ•ด๊ฒฐ

๋•Œ๋กœ๋Š” ์–‘๋ฐฉํ–ฅ ๋งคํ•‘์ด ๊ผญ ํ•„์š”ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์–‘๋ฐฉํ–ฅ ๋งคํ•‘์„ ์„ค์ •ํ•˜๊ณ , ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋Š” ๋‹จ๋ฐฉํ–ฅ ๋งคํ•‘์„ ๊ณ ๋ คํ•ด๋ณด๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ๊ตฌ์กฐ๋ฅผ ๋‹จ์ˆœํ™”ํ•˜๊ณ  ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ˆœํ™˜ ์ฐธ์กฐ ๋ฌธ์ œ๋Š” ๋งŽ์€ ํ”„๋กœ์ ํŠธ์—์„œ ์ง๋ฉดํ•  ์ˆ˜ ์žˆ๋Š” ๊ณผ์ œ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์–‘ํ•œ ์ ‘๊ทผ ๋ฐฉ๋ฒ•์„ ์ดํ•ดํ•˜๊ณ  ์ ์ ˆํžˆ ์ ์šฉํ•˜๋Š” ๊ฒƒ์€ ํ”„๋กœ์ ํŠธ์˜ ์„ฑ๊ณต์— ํฌ๊ฒŒ ๊ธฐ์—ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ˆœํ™˜ ์ฐธ์กฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ž˜ ์ดํ•ดํ•˜๊ณ , ์ƒํ™ฉ์— ๋งž๊ฒŒ ์ ์šฉํ•˜์—ฌ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ์ฃผ์˜ํ•ด๋ณด์ž