๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

JPA

JPA ์–‘๋ฐฉํ–ฅ ๋ฌดํ•œ ์ฐธ์กฐ

์–‘๋ฐฉํ–ฅ ๋ฌดํ•œ ์ฐธ์กฐ

JPA๋ฅผ ์ด์šฉํ•  ๋•Œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌดํ•œ ์ฐธ์กฐ๋ฅผ ์•Œ์•„๋ณด๊ณ  ํ•ด๊ฒฐํ•ด๋ณด์ž.

Post, Reply

๊ฒŒ์‹œ๋ฌผ๊ณผ ๋Œ“๊ธ€์„ ์œ„ํ•œ Entity๊ฐ€ ์žˆ๋‹ค๊ณ  ํ•ด๋ณด์ž.

package com.vividswan.blog.model;

import java.sql.Timestamp;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;

import org.hibernate.annotations.CreationTimestamp;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Board {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // auto_increment
    private int id;

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

    @Lob
    private String content;

    private int count; // ์กฐํšŒ์ˆ˜

    @OneToMany(mappedBy = "board", fetch = FetchType.EAGER, cascade =  CascadeType.REMOVE)
    @OrderBy("id desc")
    private List<Reply> replys;

    @ManyToOne // Many = Board, One = User
    @JoinColumn(name = "userId")
    private User user;

    @CreationTimestamp
    private Timestamp createDate;
}
package com.vividswan.blog.model;

import java.sql.Timestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import org.hibernate.annotations.CreationTimestamp;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Reply {

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

    @Column(nullable = false, length = 200)
    private String content;

    @ManyToOne
    @JoinColumn(name = "boardId")
    private Board board;

    @ManyToOne
    @JoinColumn(name = "userId")
    private User user;

    @CreationTimestamp
    private Timestamp createDate;

    public Reply(User user, Board board, String content) {
        this.user = user;
        this.board = board;
        this.content = content;
    }
}

Board Model์—์„œ Reply๋ฅผ ๋ถ€๋ฅด๋Š” ๋ถ€๋ถ„์„ ์‚ดํŽด๋ณด์ž.

    @OneToMany(mappedBy = "board", fetch = FetchType.EAGER, cascade =  CascadeType.REMOVE)
    @OrderBy("id desc")
    private List<Reply> replys;

๋Œ“๊ธ€์„ EAGER ๋ฐฉ๋ฒ•์œผ๋กœ ๊ฐ€์ ธ์˜ค๊ณ  OneToMany์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋”ฐ๋กœ FK๋กœ ์ €์žฅ๋˜์ง„ ์•Š๋Š”๋‹ค.

ํ•˜์ง€๋งŒ JPA๋Š” ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๊ฒŒ์‹œํŒ ์ •๋ณด๋ฅผ ๋ณด๋‚ด์ค„ ๋•Œ ๋Œ“๊ธ€ ์ •๋ณด๋„ ๊ฐ™์ด ๋ณด๋‚ด์ฃผ๊ฒŒ ๋œ๋‹ค.

Reply Model์—์„œ Reply๋ฅผ ๋ถ€๋ฅด๋Š” ๋ถ€๋ถ„์„ ์‚ดํŽด๋ณด์ž.

    @ManyToOne
    @JoinColumn(name = "boardId")
    private Board board;

ManyToOne ์ž„์œผ๋กœ Reply์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ Board์— ๋Œ€ํ•œ FK๋ฅผ "boardId"๋กœ ๊ฐ–๊ณ  ์žˆ๋‹ค.

์ด ๋˜ํ•œ JPA๊ฐ€ ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๋Œ“๊ธ€ ์ •๋ณด๋ฅผ ๋ณด๋‚ด์ค„ ๋•Œ ๊ฒŒ์‹œํŒ ์ •๋ณด๋„ ๊ฐ™์ด ๋ณด๋‚ด์ฃผ๊ฒŒ ๋œ๋‹ค.

๋ฌดํ•œ ์ฐธ์ดˆ ๋ฐœ์ƒ

package com.vividswan.blog.test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.vividswan.blog.model.Board;
import com.vividswan.blog.repository.BoardRepository;

@RestController
public class InfiniteReferenceTest {

    @Autowired BoardRepository boardRepository;

    @GetMapping("/test/inReference")
    public Board getPost() {
        return boardRepository.findById(1).get();
    }
}

์œ„์˜ ๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŠธ๋กœ ๊ฒŒ์‹œํŒ ์ •๋ณด๋ฅผ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ณด๋‚ด์ค˜๋ณด์ž.

์ด๋•Œ, id=1์ธ ๊ฒŒ์‹œํŒ์„ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•˜๊ณ , ๊ทธ ๊ฒŒ์‹œํŒ์— ๋Œ“๊ธ€๋„ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•œ ๋’ค ๋ณด๋‚ด์ฃผ๋ฉด ๋ฌดํ•œ ์ฐธ์กฐ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋Ÿฐ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•œ ์ด์œ ๋Š” ์œ„์˜ Board์™€ Reply์˜ ์ฝ”๋“œ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

JPA์—๊ฒŒ Board ์ •๋ณด๋ฅผ ๋“ค๊ณ  ์˜ฌ ๋•Œ Reply ์ •๋ณด๋„ ๋“ค๊ณ  ์™€๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ–ˆ๋‹ค.

๊ทผ๋ฐ Reply ์ •๋ณด๋ฅผ ๋“ค๊ณ  ์˜ฌ ๋•Œ ์—ญ์‹œ Board ์ •๋ณด๋ฅผ ๋“ค๊ณ  ์™€๋‹ฌ๋ผ๊ณ  ์š”์ฒญํ–ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด Board์— ๋“ฑ๋ก๋œ Reply๋ฅผ ๋“ค๊ณ  ์˜ค๋ฉด์„œ ๋˜๋‹ค์‹œ Board๋ฅผ ๋“ค๊ณ  ์˜ค๊ณ  ์ด๋ ‡๊ฒŒ ์„œ๋กœ๋ฅผ ๊ณ„์† ์ฐธ์กฐํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์ด๋Ÿฌํ•œ ์ƒํ™ฉ์„ ๋ฌดํ•œ ์ฐธ์กฐ๋ผ๊ณ  ๋งํ•œ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

์šฐ์„  ์ด๋Ÿฌํ•œ ๋ฌดํ•œ ์ฐธ์กฐ๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ Entitiy ์ž์ฒด๊ฐ€ ์•„๋‹Œ DTO๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋ฆฌํ„ดํ•ด์ฃผ๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์ด๋‹ค.

 

๋‹ค๋ฅธ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•๋“ค๋„ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์ง€๋งŒ ์—ฌ๊ธฐ์„  JsonIgnoreProperties๋ฅผ ์ด์šฉํ•ด๋ณด์ž.

Board Model ๊ฐ์ฒด๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•œ๋‹ค.

    @OneToMany(mappedBy = "board", fetch = FetchType.EAGER, cascade =  CascadeType.REMOVE)
    @JsonIgnoreProperties({"board"})
    @OrderBy("id desc")
    private List<Reply> replys;

@JsonIgnoreProperties({"board"})๋ผ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด ์ถ”๊ฐ€๋œ ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด ์–ด๋…ธํ…Œ์ด์…˜์˜ ์˜๋ฏธ๋กœ๋Š” board์— ์˜ํ•ด ํ˜ธ์ถœ๋œ reply์—์„œ๋Š” board ์ž๋ฃŒ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ง๋ผ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์ด๋‹ค.

๋” ์ƒ๊ฐํ•ด ๋ณผ ๊ฑด, board์—์„œ reply์˜ ์ž๋ฃŒ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒฝ์šฐ์—๋งŒ ํ•ด๋‹นํ•˜๋ฏ€๋กœ, ๋งŒ์•ฝ reply๋งŒ ๋”ฐ๋กœ ๋ถˆ๋Ÿฌ์˜จ๋‹ค๋ฉด reply์˜ FK๋กœ Board์˜ ์ž๋ฃŒ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๊ฒƒ์ด๋‹ค.

์œ„์™€ ๊ฐ™์ด reply์—์„œ ๋‹ค์‹œ board์˜ ์ž๋ฃŒ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ฌดํ•œ ์ฐธ์กฐ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.