인프런 커뮤니티 질문&답변

주우민님의 프로필 이미지
주우민

작성한 질문수

실전! 스프링 데이터 JPA

@EntityGraph

스프링 데이터 jpa delete 최적화

해결된 질문

작성

·

1.5K

·

수정됨

0

학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.

1. 강의 내용과 관련된 질문을 남겨주세요.
2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.
(자주 하는 질문 링크: https://bit.ly/3fX6ygx)
3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.
(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)

질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.
=========================================
[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예/아니오) 예
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)

[질문 내용]

강사님 강의 정말 잘 보고 있다가 의문점이 생겨 이렇게 질문 남깁니다.

 

일단 간단하게 예시를 만들면 양방향 관계로 묶인 Member와 Team이 있을때 Team을 삭제할때 해당 Team에 있던 Member도 전부 삭제를 원하는 상황입니다.

데이터는 Team 2개 Team 하나당 3개의 Member씩이 있는 상태입니다.

Member.java

package com.example.demo.entity;

import lombok.*;

import javax.persistence.*;

@Entity
@Table(name = "member")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member {

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

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

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team")
    private Team team;

    public Member(String name, Team team) {
        this.name = name;
        this.team = team;
    }
}

 

Team.java

package com.example.demo.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

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

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "team")
public class Team {

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

    @OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
    List<Member> members = new ArrayList<>();

}

 

controller.java

package com.example.demo.entity.controller;


import com.example.demo.entity.repository.TeamRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class Controller {
    
    private final TeamRepository teamRepository;

    @DeleteMapping("/")
    public void deleteTeam(@RequestParam Long id) {
        teamRepository.deleteById(id);
        //deleteById는 JpaRepository에서 제공하는 기본
    }
}

위의 코드를 실행하고 delete요청을 했을때

Hibernate:

select

team0_.team_id as team_id1_1_0_

from

team team0_

where

team0_.team_id=?

 

Hibernate:

select

members0_.team as team3_0_0_,

members0_.member_id as member_i1_0_0_,

members0_.member_id as member_i1_0_1_,

members0_.member_name as member_n2_0_1_,

members0_.team as team3_0_1_

from

member members0_

where

members0_.team=?

 

Hibernate:

delete

from

member

where

member_id=?

 

Hibernate:

delete

from

member

where

member_id=?

 

Hibernate:

delete

from

member

where

member_id=?

 

Hibernate:

delete

from

team

where

team_id=?

 

이렇게 나오고 Team의 members의 fetch type을 eager로 바꾸었을때는

 

select

team0_.team_id as team_id1_1_0_,

members1_.team as team3_0_1_,

members1_.member_id as member_i1_0_1_,

members1_.member_id as member_i1_0_2_,

members1_.member_name as member_n2_0_2_,

members1_.team as team3_0_2_

from

team team0_

left outer join

member members1_

on team0_.team_id=members1_.team

where

team0_.team_id=?

 

Hibernate:

delete

from

member

where

member_id=?

Hibernate:

delete

from

member

where

member_id=?

 

Hibernate:

delete

from

member

where

member_id=?

 

Hibernate:

delete

from

team

where

team_id=?

이러한 쿼리가 발생하는것을 볼 수 있습니다.

 

여기서 제가 든 궁금점은 (편의상 맨 위의 쿼리를 1번쿼리라 하겠습니다)

  1. 지연로딩을 할때 Team의 Member는 프록시이므로 member select 하긴 할텐데
    select

    members0_.team as team3_0_0_,

    members0_.member_id as member_i1_0_0_,

    members0_.member_id as member_i1_0_1_,

    members0_.member_name as member_n2_0_1_,

    members0_.team as team3_0_1_

    이 쿼리(2번 쿼리)가 어떻게 나오게 된건지 이해가 되질 않습니다.

     

  2. 최대한 쿼리를 최적화 해보려 했지만 지연로딩을 할때 fetch join, entity graph등을 사용할 수 없어 1번과 2번 쿼리를 즉시로딩을 사용할때 처럼 최적화 할 수가 없었고 delete문도 멤버를 한번에 delete, 그다음 team delete 이렇게 두번으로 최적화 하고 싶었는데 방법이 떠오르질 않습니다. 정확하게 알려주시기 번거로우시면 키워드나 따로 어떻게 공부하면 해결 할 수 있을지 알려주시면 정말 감사드리겠습니다.

 

 

답변 1

1

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. 주우민님

1. orphanRemoval = true이 부분 때문일 것 같아요. orphanRemoval은 부모를 삭제했을 때 자식까지 함께 삭제하는 코드입니다. 그런데 자식을 알아야 삭제할 수 있으니 이렇게 호출 된 것 같아요.

2. 한번에 삭제하고 싶으면 JPQL 벌크 연산으로 직접 삭제하면 됩니다. JPA 기본편의 마지막 부분에 있는 벌크 연산을 참고해주세요.

감사합니다.

주우민님의 프로필 이미지
주우민

작성한 질문수

질문하기