JPA를 사용하기전 Mybatis를 사용할 때는 xml로 객체를 SQL에 매핑하는데 시간을 썼지만 JPA는 자동으로 쿼리를 만들어줘서 편하게 사용하고 있었다. 하지만 편한 만큼이나 나도 모르게 1 + n 쿼리가 실행되고 있었다. 이것만 잘 컨트롤 해줘도 성능이 좋아지고 JPA이해도가 높아지는 것 같다. 이번 글에는 Fetch Join과 Fetch 전략을 사용해서 n+1을 개선할 것이다.


n+1 이란?

Car 라는 클래스와 그 차의 주인인 Master라는 클래스가 있습니다. 아시다시피 차 주인은 여러 대의 차를 가질 수 있으니 차와 주인의 관계는 N : 1 관계입니다.

@Entity
public class Master {

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

    @Column
    private String name;
}
@Entity
public class Car {

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

    @Column
    private String name;

    @ManyToOne
    private Master master;
}

 

아래 코드는 마스터한명과 차 2대를 생성하는 로직입니다.

@GetMapping("/test1")
public void createCarAndMaster(){
    // Car 와 Master를 생성한다.
    Master master = Master.builder().name("빈디젤").build();
    Car car = Car.builder().name("스포츠카").build();
    
    //Master를 저장
    Master saveMaster= masterRepository.save(master);

    //저장된 Master를 Car에 관계를 맺여준다.
    car.settingMaster(saveMaster);
    
    //Car 저장
    carRepository.save(car);
}

 

H2 데이터베이스에 저장된 걸 확인 할 수 있습니다.

 

그러면 이제 우리는 저장된 Car를 보고싶기에 Car를 조회합니다. 하지만 아래와 같이 조회를 하면 쿼리가 2번 발생하고  car를 조회했지만 master도 같이 조회가 되신 걸 확인 할 수 있습니다. 이것을 쿼리가 n번 더 일어났다고 해서 n+1 이라고 합니다.

@GetMapping("/test2")
public List<Car> getCar() {
    return carRepository.findAll();
}
Hibernate: 
    select
        car0_.id as id1_1_,
        car0_.master_id as master_i3_1_,
        car0_.name as name2_1_ 
    from
        car car0_
Hibernate: 
    select
        master0_.id as id1_3_0_,
        master0_.name as name2_3_0_ 
    from
        master master0_ 
    where
        master0_.id=?
Response

[
    {
        "id": 1,
        "name": "스포츠카",
        "master": {
            "id": 1,
            "name": "빈디젤"
        }
    }
]

 

 


Fetch 전략이란? 

fetch 전략에는 FetchType.EAGER(즉시 로딩), FetchType.LAZY(지연 로딩)  2가지 설정이 있습니다. 

 

즉시 로딩(FetchType.EAGER)

연관 관계에 있는 Entity 들 모두 가져온다  @ManyToOne 과 @OneToOne는
기본값으로 즉시 로딩(FetchType.EAGER)로 설정되어 있다.

 

지연 로딩(FetchType.LAZY)

연관 관계에 있는 Entity 가져오지 않고, 필요할 때 가져온다. @OneToMany와 @ManyToMany는
기본값으로  지연 로딩(FetchType.LAZY)로 설정되어 있습니다.

 

사용법 (예제)

Car 클래스에 연관관계를 맺여준 변수에(fetch = FetchType.LAZY) 를 설정해주면 끝이다.

@Entity
public class Car {

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

    @Column
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private Master master;
}

 

조회를 해준다. 리턴값을 null을 주었는데 자세히는 나중에 설명을 하겠습니다. 아래 코드를 실행하면 로직이 한번만 진행합니다.

@GetMapping("/test2")
public List<Car> getCar() {
    List<Car> carList= carRepository.findAll();
    return null;
}
Hibernate: 
    select
        car0_.id as id1_1_,
        car0_.master_id as master_i3_1_,
        car0_.name as name2_1_ 
    from
        car car0_

 

이번에는 Master를 사용하는 로직을 만들어 어떻게되는지 확인해보면 Master를 조회하는 쿼리가 발생합니다.

@GetMapping("/test2")
public List<Car> getCar() {
    List<Car> carList= carRepository.findAll(); // Car를 조회하는 쿼리 발생
    System.out.println(carList.get(0).getMaster().getName());  // Master를 조회하는 쿼리 발생
    return null;
}
Hibernate: 
    select
        car0_.id as id1_1_,
        car0_.master_id as master_i3_1_,
        car0_.name as name2_1_ 
    from
        car car0_
Hibernate: 
    select
        master0_.id as id1_3_0_,
        master0_.name as name2_3_0_ 
    from
        master master0_ 
    where
        master0_.id=?

 

Master의 데이터가 필요없다면 Car 객체만 리턴해주면 되겠지?! 라고 생각 하실 수도 있지만 Car을 return 해주면 Master가 조회됩니다. 그러므로 반드시 Car만 필요하다면 리턴해줄 때 Dto객체로 생성해 리턴해주세요
@GetMapping("/test2")
public List<Car> getCar() {
    List<Car> carList= carRepository.findAll();  //Car 조회 쿼리 발생
    return carList;  // Master 조회 쿼리 발생
}

 

Hibernate: 
    select
        car0_.id as id1_1_,
        car0_.master_id as master_i3_1_,
        car0_.name as name2_1_ 
    from
        car car0_
Hibernate: 
    select
        master0_.id as id1_3_0_,
        master0_.name as name2_3_0_ 
    from
        master master0_ 
    where
        master0_.id=?

 


@EntityGraph에 대해해서는 2부에서 설명이 이어집니다. 

2022.08.04 - [데이터베이스/JPA] - [JPA] Fetch 전략과 @EntityGraph로 1+n 해결하기(2)

 

[JPA] Fetch 전략과 @EntityGraph로 1+n 해결하기(2)

지난 번에는 Fetch전략인 LAZY와 EAGER에 대해 알아봤다. 하지만 조회된 테이블의 연관관계 된 테이블까지 써야한다면 @EntityGraph를 사용하는게 좋다. 이 글에서는 @EntityGraph에 대해서 다뤄볼 예정입

whitewise95.tistory.com

 

 


참고했던 자료 및 참고하면 좋은 자료

 

JPA 페치(Fetch) 전략 - 즉시 로딩(EAGER)과 지연 로딩(LAZY)

JPA 페치(Fetch) 전략 - 즉시 로딩(EAGER)과 지연 로딩(LAZY) 예제코드 세팅 import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.Id; import javax.persistence.Joi..

rutgo-letsgo.tistory.com

 

 

[Spring JPA] fetch전략과 fetch join에 관한 오해

I. 개요 [Spring JPA] 프로젝트 중 게시판 쿼리 이슈를 해결하던 도중 굉장히 잘못된 지식을 갖고 있다는 것을 알게 되었다. 내용을 정리하는 편이 기억에 남을 것 같아서 글로 남기려 한다. II. 오해

mighty96.github.io

 

 

[JPA] Fetch 전략 공부하기 - @OneToOne 양방향 매핑에서 Lazy가 동작하지 않는 이유

Fetch 전략 기존 Hibernate의 Fetch 전략은 모두 Lazy였는데, Hibernate 5.x 버전부터 JPA와 똑같이 전략이 변경되었다. Hibernate User Guide의 일부를 보면 다음과 같다. The Hibernate recommendation is to st..

loosie.tistory.com

 

 

JPA 의 Fetch Type 과 친해지기 | Jayne.who();

< Back JPA 의 Fetch Type 과 친해지기 web | 06 August 2019 Tags | java spring jpa JPA 를 이용한 개발을 하다보면 자주 접하는 프로그래밍적인 이슈가 있습니다. 바로 Fetch Type (Fetch 전략) 입니다. Fetch Type 속성은

jaynewho.com

 

 

JPA Fetch 전략과 N+1 문제

Fetch Type 이란 Fetch Type 은 JPA 가 하나의 Entity 를 조회할 때, 연관관계에 있는 객체들을 어떻게 가져올 것이냐를 나타내는 설정값입니다. Fetch Type 은 크게 Eager 와 Lazy 두가지 전략이 있습니다. Fetch.

baek-kim-dev.site

 

 

[JPA] 즉시 로딩과 지연 로딩(FetchType.LAZY or EAGER)

즉시 로딩과 지연 로딩 프록시 학습 처음에 했던 질문. Member를 조회할 때 Team도 함께 조회 해야 할까? 비즈니스 로직에서 단순히 멤버 로직만 사용하는데 함께 조회하면, 아무리 연관관계가 걸려

ict-nroo.tistory.com

 

복사했습니다!