[한화시스템 BEYOND SW캠프 7기] #29. Spring | JPA - Entity 관련 주요 설정 정보

아로롱·2024년 7월 20일

BEYOND SW CAMP 7

목록 보기
30/30

⚙️ JPA의 Entity 관련 주요 설정 정보

Entity데이터베이스 테이블과 매핑되는 클래스.
객체를 데이터베이스의 테이블에 매핑하여 영속성을 관리할 수 있게 해준다.
@Entity 어노테이션 사용.

@OneToMany

OneToMany 관계는 하나의 엔티티가 여러 개의 다른 엔티티와 관계를 맺는 경우를 의미.
⇒ 일대다 관계를 의미 !
예를 들어, 하나의 Author(작성자)가 여러 개의 Post(게시글)를 가질 수 있는 경우.

MappedBy

양방향 관계에서, 연관 관계의 주인을 지정.

연관 관계의 주인이란 JPA에서 관계를 맺고 있는 두 엔티티 중(양방향 관계) 관계를 관리 (데이터베이스의 외래 키를 업데이트)하는 책임을 가지는 쪽을 의미.

관계의 주인이 아닌 엔티티에서 mappedBy 속성을 통해 관계의 주인이 되는 필드를 지정한다.

@OneToMany(mappedBy = "author")
    private List<Post> posts;
// Author 클래스에 Post 를 mappedBy 로 선언해줌.
  • Many 쪽 엔티티의 필드 이름을 값으로 설정.
  • 연관 관계의 주인이 Many쪽임에 유의.

Cascade

엔티티 상태 변화(예: 저장, 삭제)를 연관된 엔티티에 전파하는 방식을 지정.
예를 들어, 부모 엔티티를 저장하거나 삭제할 때 자식 엔티티도 함께 저장되거나 삭제되도록 할 수 있다.

  • cascade를 사용하면, 부모 엔티티를 저장할 때 자동으로 연관된 자식 엔티티들도 함께 저장함으로서 코드를 간결하게 만들고, 엔티티 간의 관계를 명확하게 표현할 수 있다.

  • 더티체킹과 비교하면 더티체킹은 변경사항에 대해서만 반영하지만, cascade옵션을 통해서는 연관 엔티티의 신규 사항 추가 가능

CascadeType.PERSIST: 부모 엔티티를 저장할 때 자식 엔티티함께 저장.
CascadeType.MERGE: 부모 엔티티를 병합할 때 자식 엔티티함께 병합합니다.
CascadeType.REMOVE: 부모 엔티티를 삭제할 때 자식 엔티티함께 삭제합니다.
CascadeType.REFRESH: 부모 엔티티를 새로 고침할 때 자식 엔티티함께 새로고침합니다.
CascadeType.DETACH: 부모 엔티티를 분리할 때 자식 엔티티함께 분리합니다.
CascadeType.ALL: 모든 작업을 자식 엔티티에도 전파합니다.

// cascade 단일 지정
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL)
    private List<Post> posts;

// cascade 복수 지정
@OneToMany(mappedBy = "author", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
    private List<Post> posts;

cascade = CascadeType.ALL

CascadeType.ALL 붙여준 뒤 → Post 가 있어도 Author 삭제가 가능하며, 자동으로 함께 삭제된다.

cascade = CascadeType.PERSIST

.getPosts() 를 위해 Post 선언 시 List<Post> posts 로 변경 후 진행.
가입과 동시에 가입 환영 인사가 작성되도록 설정하였다.

// List<Post> posts 로 선언해준 뒤, dto 파일에서 가입 시 초기화 진행
public Author toEntity(){
        Author author = Author.builder().password(this.password).name(this.name)
                .email(this.email).role(this.role)
                .posts(new ArrayList<>())
                .build();
        return author;
    }

JPA → 영속성 컨텍스트 ⇒ 중간(임시 저장소 개념)에서 Author 보내고 받고(생성 후), Post 에 author id 보내고 받고 → 임시 저장해서 순차적으로 처리함.
⇒ 이게 안되면 author 저장하고 post 저장하는 걸로 하나 하나 작동하게 해야 함 !


Fetch

  • fetch는 가져오다라는 의미를 가진 단어로서, 엔티티 또는 엔티티의 연관된 컬렉션 및 엔티티를 데이터베이스로부터 어떻게 로드할지 결정하는 전략.
  • FetchType.LAZY는 지연 로딩, FetchType.EAGER는 즉시 로딩
    • lazy 설정시 엔티티를 참조하지 않는한 자식 테이블로 쿼리 발생 X
      • 참조할 경우 N+1 이슈 발생, 하지 않으면 발생 안 함.
    • eager 설정시 즉시, 상대 엔티티를 참조하지 않더라도 자식 테이블로 쿼리 발생 O
      • 즉 N+1 이슈 즉시 발생.
    • @OneToManyOneToOne은 default설정이 FetchType.LAZY.
    • @ManyToOne은 default가 FetchType.EAGER.

💻 lazy(지연로딩), eager(즉시로딩) 실습

Post 에서 멤버를 가져올 때 @ManyToOne 으로 설정되었기 때문에 default 값인 EAGER 로 설정이 되어있다.

@OneToMany 로 설정된 Member 에서의 조회는 lazy 가 적용되어 쿼리가 적게 실행되었고,
@ManyToOne 로 설정된 Post 에서의 조회는 실행되는 쿼리와 출력이 많은 것을 알 수 있다 !
OneToMany 는 로딩(fetch) 전략이 lazy(지연로딩)
→ Member 도 관련있는 post 가 있음에도 불구하고 쿼리를 내보내지 않는다.
OneToOne 또는 ManyToOne 은 로딩 전략이 eager(즉시 로딩)

post 를 통해 조회했을 때 N+1 문제가 발생하게 된다.
해결책은 lazy 로 직접 설정해주는 것 !

lazy 로 FetchType 을 지정해준 뒤, 이전에 post 만큼 나왔던 쿼리문이 나오지 않는 것을 확인할 수 있었다.


@ManyToOne

ManyToOne 관계는 여러 개의 엔티티가 하나의 다른 엔티티와 관계를 맺는 경우를 의미.
하나의 Author 가 여러 개의 Post 를 가질 때, 각 Post 는 하나의 Author 에 속한다 !

@JoinColumn

외래 키(FK)를 매핑할 때 사용함.
JPA 에서 엔티티 간의 관계를 정의할 때 사용되는 어노테이션.

// 연관 관계의 주인은 FK 가 있는 Post.
@ManyToOne(fetch = FetchType.LAZY) //참조 안 하면 안 나가게.
@JoinColumn(name = "author_id", nullable = false)
private Author author;

name 속성으로 외래 키 컬럼의 이름을 지정.
nullable 속성으로 nullable 또는 not null지정, unique 설정도 가능 !

Cascade

@OneToMany와 동일하게 상태 변화를 전파하는 방식을 지정하는 것.
일반적으로는 부모엔티티에 설정한다.

💻 Fetch 로 인한 N + 1 문제 해결 실습

@Repository
public interface PostRepository extends JpaRepository<Post, Long>{
    // JPQL 문법.
    // 네이밍룰을 통한 방식이 아닌 메서드 생성.
    // select p.*, a.* from post p left join author a on p.author_id=a.id;
    @Query("select p from Post p left join fetch p.author")
    List<Post> findAllFetch();

findAllFetch() 메서드는 모든 Post 객체를 가져오면서, 각 Post와 연관된 Author 객체도 함께 가져온다.
JPQL 쿼리 : select p from Post p left join fetch p.author
left join fetch p.author : Post 객체와 연관된 Author 객체를 함께 가져옴.

// fetch 가 아닌 그냥 left join 이라면 ?
// select p.* from post p left join author a on p.author_id=a.id;
// => a 를 안 가져 옴 ! -> left join 을 뭐하러 하나요..?
// author 객체를 통한 조건문으로 post 를 filtering 할 때 사용함. -> 이름이 hong 인 사람의 post 를 가져오겠다!
// -> N+1 문제가 똑 같 이 발 생.
@Query("select p from Post p left join p.author")
List<Post> findAllNOFetch();
}

findAllNOFetch() 메서드는 모든 Post 객체를 가져온다.
Post와 연관된 Author 객체와 조인은 하지만, Author 객체의 데이터는 가져오지 않는다 !
JPQL 쿼리 : select p from Post p left join p.author
left join p.author : Post 객체와 연관된 Author 객체와 조인하지만, Author 객체의 데이터는 가져오지 않음

findAllFetch(): Post와 Author를 함께 가져오므로, 데이터베이스에서 한 번에 필요한 모든 데이터를 가져옴 → 이를 통해 성능을 향상시킬 수 있다!
하지만 필요한 데이터가 많아질 수 있다. ( 다 가져왔으니까 ! )

findAllNOFetch(): Post만 가져오고 Author는 가져오지 않는다.
데이터베이스에서 필요한 데이터만 가져옴 → 쿼리가 더 가볍다.
하지만 나중에 Author 데이터를 가져와야 한다면 추가 쿼리가 발생할 수 있다.

select post0_.id as id1_1_0_, author1_.id as id1_0_1_, post0_.author_id as author_i6_1_0_, post0_.contents as contents2_1_0_, post0_.created_time as created_3_1_0_, post0_.title as title4_1_0_, post0_.update_time as update_t5_1_0_, author1_.created_time as created_2_0_1_, 
author1_.updated_time as updated_3_0_1_, 
author1_.email as email4_0_1_, author1_.name as name5_0_1_, 
author1_.password as password6_0_1_, author1_.role as role7_0_1_ from post
post0_ left outer join author author1_ on post0_.author_id=author1_.id

마지막 줄에서 left outer join 확인 됨 !
fetch join 쿼리 작성 ⇒ 그냥 join 은 안된다!(left join fetch 로 작성함) → 그냥 join 은 할 때마다 join 해주고 필요하면 또 해주고 . . → fetch join 은 임시 저장해두듯이 한번에 묶기. ⇒ 이것도 JPA 에서만 가능 ⇒ 영속성 컨텍스트

profile
Dilige, et fac quod vis

0개의 댓글