[재능교환소] JPA N+1 문제 해결

10000JI·2024년 6월 3일
0

프로젝트

목록 보기
5/14

🐶 Eager loading vs Lazy loading

JPA(Java Persistence API)를 사용하여 애플리케이션을 개발할 때, 엔티티 간의 관계를 효과적으로 로딩하는 것은 매우 중요하다.

엔티티 간의 관계를 로딩하는 방식에 따라 애플리케이션의 성능과 효율성이 크게 달라질 수 있기 때문이다.

필자는 [재능교환소] 라는 이름의 프로젝트를 진행하고 있다.

Lazy loading과 Eager loading에 대한 이야기는 예전부터 많았다.

Eager loading: 즉시로딩 방식으로 엔티티를 조회할 때 자신과 관련있는 엔티티를 Join을 통해 모두 불러와 조회한다.

매번 연관된 엔티티를 모두 가져와 편하게 사용할 수 있다는 장점이 있지만, 불필요한 조인까지 포함하여 처리하는 경우가 많기에 권장하지 않는다.

Lazy loading: 지연로딩 방식으로 자신과 연관된 엔티티를 사용할 때 연관된 엔티티를 조회한다.

해당 객체를 실제 사용할 때만(ex. getter 메서드를 사용할 때) 영속성 컨텍스트를 통해 실체 객체를 호출한다.

실제 프로젝트에도 User와 Authority만 Eager loading으로 설정하고, 나머지는 모두 Lazy loading으로 설정하였다.

리팩토링 하기 전 코드를 살펴보자

🦝 리팩토리 전 - Lazy loading

제일 연관관계가 많은 Talent 엔티티를 중심으로 설명하겠다.

Talent

@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class Talent extends BaseEntity {

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

    /**
     * 단방향 매핑 (단뱡향일 때는 Cascade 작동 X, User 삭제 시 Talent 삭제 후 User 삭제 해야 함)
     */
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="writer")
    private User writer;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "place_id")
    private Place place;

    /**
     * 단방향 매핑
     */
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "teached_subject_id", nullable = false)
    private SubjectCategory teachedSubject;

    /**
     * 단방향 매핑
     */
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "teaching_subject_id", nullable = false)
    private SubjectCategory teachingSubject;

    @Column(name = "talent_title", length = 50, nullable = false)
    private String title;

    @Column(name = "talent_content", length = 4000, nullable = false)
    private String content;

    @Column(name = "talent_hit")
    @ColumnDefault("0")
    private Long hit;

    @Column(name = "max_age", nullable = false)
    private Long maxAge;

    @Column(name = "min_age", nullable = false)
    private Long minAge;

    @Enumerated(EnumType.STRING)
    @Column(length = 6)
    private GenderForTalent gender;

    @ElementCollection
    @Enumerated(EnumType.STRING)
    @Column(name = "day_of_week")
    private Set<DayOfWeek> dayOfWeek = new HashSet<>();
    /**
     * 이미지와 양방향 매핑
     */
    @OneToMany(mappedBy = "talent", cascade = CascadeType.PERSIST)
    private List<File> files = new ArrayList<>();

    /**
     * Talent와 TalentScrap 양방향 매핑
     */
    //Talent 엔티티를 삭제하기 전에 해당 Talent 엔티티와 관련된 모든 TalentScrap 엔티티를 삭제
    @OneToMany(mappedBy = "talent", cascade = CascadeType.REMOVE)
    private Set<TalentScrap> talentScraps = new HashSet<>();

    /**
     * 양방향
     */
    @OneToMany(mappedBy = "talent", cascade = CascadeType.PERSIST)
    private List<Comment> comments = new ArrayList<>();

	...(생략)
}

User, Place 그리고 SubjectCategory와 DayOfWeek는 단방향 매핑이고, File과 Comment, TalentScrap는 양방향 매핑으로 설계되어 있다.

약 8개의 엔티티와 연관관계 설정이 되어있어 이를 모두 Eager 로딩으로 했다면 매 순간 모든 엔티티의 정보를 가져왔을 것으로 보인다.

그럼 Spring Data JPA에서 제공하는 findById() 내장 메서드를 가지고 재능교환 게시물 조회를 해보자.

포스트맨으로 조회 엔드포인트에 게시물 번호를 쿼리 스트링으로 함께 보내면, 응답이 JSON 형태로 반환된다.

    /**
     * 재능교환 게시물 조회
     */
    @Override
    @Transactional
    public TalentDto.TalentReadResponse read(Long talentId) {
        Talent talent = talentRepository.findById(talentId)
                .orElseThrow(() -> BoardNotFoundException.EXCEPTION);
        talent.updateHit();
        return new TalentDto.TalentReadResponse(talent);
    }

Service 코드를 확인해보면 앞서 말했듯이 findById로 Talent를 조회하고, updateHit() 메서드를 만들어 조회수를 늘려주는 작업을 할 수 있게 구현하였다.

실제로 어떤 SQL을 보내 응답을 얻었는지 로그를 확인해보자.

2024-06-03T11:06:47.855+09:00 DEBUG 21228 --- [nio-8080-exec-8] org.hibernate.SQL                        : 
    select
        t1_0.talent_id,
        t1_0.talent_content,
        t1_0.gender,
        t1_0.talent_hit,
        t1_0.max_age,
        t1_0.min_age,
        t1_0.moddate,
        t1_0.place_id,
        t1_0.regdate,
        t1_0.teached_subject_id,
        t1_0.teaching_subject_id,
        t1_0.talent_title,
        t1_0.writer 
    from
        talent t1_0 
    where
        t1_0.talent_id=?
Hibernate: 
    select
        t1_0.talent_id,
        t1_0.talent_content,
        t1_0.gender,
        t1_0.talent_hit,
        t1_0.max_age,
        t1_0.min_age,
        t1_0.moddate,
        t1_0.place_id,
        t1_0.regdate,
        t1_0.teached_subject_id,
        t1_0.teaching_subject_id,
        t1_0.talent_title,
        t1_0.writer 
    from
        talent t1_0 
    where
        t1_0.talent_id=?
2024-06-03T11:06:47.860+09:00 DEBUG 21228 --- [nio-8080-exec-8] org.hibernate.SQL                        : 
    select
        u1_0.user_id,
        u1_0.active,
        a1_0.user_id,
        a1_1.authority_name,
        u1_0.career_skills,
        u1_0.email,
        f1_0.file_id,
        f1_0.file_url,
        f1_0.board_id,
        f1_0.ori_name,
        f1_0.talent_id,
        u1_0.gender,
        u1_0.job,
        u1_0.my_subject,
        u1_0.password,
        u1_0.preferred_subject,
        rt1_0.token_id,
        rt1_0.expiration_time,
        rt1_0.refresh_token 
    from
        users u1_0 
    left join
        user_authority a1_0 
            on u1_0.user_id=a1_0.user_id 
    left join
        authority a1_1 
            on a1_1.authority_name=a1_0.authority_name 
    left join
        file f1_0 
            on u1_0.user_id=f1_0.user_id 
    left join
        refresh_token rt1_0 
            on u1_0.user_id=rt1_0.user_id 
    where
        u1_0.user_id=?
Hibernate: 
    select
        u1_0.user_id,
        u1_0.active,
        a1_0.user_id,
        a1_1.authority_name,
        u1_0.career_skills,
        u1_0.email,
        f1_0.file_id,
        f1_0.file_url,
        f1_0.board_id,
        f1_0.ori_name,
        f1_0.talent_id,
        u1_0.gender,
        u1_0.job,
        u1_0.my_subject,
        u1_0.password,
        u1_0.preferred_subject,
        rt1_0.token_id,
        rt1_0.expiration_time,
        rt1_0.refresh_token 
    from
        users u1_0 
    left join
        user_authority a1_0 
            on u1_0.user_id=a1_0.user_id 
    left join
        authority a1_1 
            on a1_1.authority_name=a1_0.authority_name 
    left join
        file f1_0 
            on u1_0.user_id=f1_0.user_id 
    left join
        refresh_token rt1_0 
            on u1_0.user_id=rt1_0.user_id 
    where
        u1_0.user_id=?
2024-06-03T11:06:47.862+09:00 DEBUG 21228 --- [nio-8080-exec-8] org.hibernate.SQL                        : 
    select
        p1_0.place_id,
        p1_0.place_name 
    from
        place p1_0 
    where
        p1_0.place_id=?
Hibernate: 
    select
        p1_0.place_id,
        p1_0.place_name 
    from
        place p1_0 
    where
        p1_0.place_id=?
2024-06-03T11:06:47.862+09:00 DEBUG 21228 --- [nio-8080-exec-8] org.hibernate.SQL                        : 
    select
        sc1_0.subject_id,
        sc1_0.parent_id,
        sc1_0.subject_name 
    from
        subject_category sc1_0 
    where
        sc1_0.subject_id=?
Hibernate: 
    select
        sc1_0.subject_id,
        sc1_0.parent_id,
        sc1_0.subject_name 
    from
        subject_category sc1_0 
    where
        sc1_0.subject_id=?
2024-06-03T11:06:47.863+09:00 DEBUG 21228 --- [nio-8080-exec-8] org.hibernate.SQL                        : 
    select
        sc1_0.subject_id,
        sc1_0.parent_id,
        sc1_0.subject_name 
    from
        subject_category sc1_0 
    where
        sc1_0.subject_id=?
Hibernate: 
    select
        sc1_0.subject_id,
        sc1_0.parent_id,
        sc1_0.subject_name 
    from
        subject_category sc1_0 
    where
        sc1_0.subject_id=?
2024-06-03T11:06:47.863+09:00 DEBUG 21228 --- [nio-8080-exec-8] org.hibernate.SQL                        : 
    select
        sc1_0.subject_id,
        sc1_0.parent_id,
        sc1_0.subject_name 
    from
        subject_category sc1_0 
    where
        sc1_0.subject_id=?
Hibernate: 
    select
        sc1_0.subject_id,
        sc1_0.parent_id,
        sc1_0.subject_name 
    from
        subject_category sc1_0 
    where
        sc1_0.subject_id=?
2024-06-03T11:06:47.864+09:00 DEBUG 21228 --- [nio-8080-exec-8] org.hibernate.SQL                        : 
    select
        sc1_0.subject_id,
        sc1_0.parent_id,
        sc1_0.subject_name 
    from
        subject_category sc1_0 
    where
        sc1_0.subject_id=?
Hibernate: 
    select
        sc1_0.subject_id,
        sc1_0.parent_id,
        sc1_0.subject_name 
    from
        subject_category sc1_0 
    where
        sc1_0.subject_id=?
2024-06-03T11:06:47.864+09:00 DEBUG 21228 --- [nio-8080-exec-8] org.hibernate.SQL                        : 
    select
        dow1_0.talent_talent_id,
        dow1_0.day_of_week 
    from
        talent_day_of_week dow1_0 
    where
        dow1_0.talent_talent_id=?
Hibernate: 
    select
        dow1_0.talent_talent_id,
        dow1_0.day_of_week 
    from
        talent_day_of_week dow1_0 
    where
        dow1_0.talent_talent_id=?
2024-06-03T11:06:47.865+09:00 DEBUG 21228 --- [nio-8080-exec-8] org.hibernate.SQL                        : 
    select
        f1_0.talent_id,
        f1_0.file_id,
        f1_0.file_url,
        f1_0.board_id,
        f1_0.ori_name,
        f1_0.user_id 
    from
        file f1_0 
    where
        f1_0.talent_id=?
Hibernate: 
    select
        f1_0.talent_id,
        f1_0.file_id,
        f1_0.file_url,
        f1_0.board_id,
        f1_0.ori_name,
        f1_0.user_id 
    from
        file f1_0 
    where
        f1_0.talent_id=?
2024-06-03T11:06:47.872+09:00 DEBUG 21228 --- [nio-8080-exec-8] org.hibernate.SQL                        : 
    update
        talent 
    set
        talent_content=?,
        gender=?,
        talent_hit=?,
        max_age=?,
        min_age=?,
        moddate=?,
        place_id=?,
        teached_subject_id=?,
        teaching_subject_id=?,
        talent_title=?,
        writer=? 
    where
        talent_id=?
Hibernate: 
    update
        talent 
    set
        talent_content=?,
        gender=?,
        talent_hit=?,
        max_age=?,
        min_age=?,
        moddate=?,
        place_id=?,
        teached_subject_id=?,
        teaching_subject_id=?,
        talent_title=?,
        writer=? 
    where
        talent_id=?

Talent 테이블은 물론이고, User, Place, Subject_Category, Day_of_Week, File 등을 각각 Select문으로 조회 후 조회수를 업데이트 시켜주고 있다.

여기서 Lazy loading의 N+1문제를 해결하기 위한 방법은 두 가지가 있다.

🐱 문제해결

1. Fetch Join

첫 번째 방법은 Fetch Join이다.

가장 보편적인 해결 방법이다.

JPQL로 쓸 때 그냥 Join으로 실행하면 방금과 똑같이 SQL문이 출력된다.

Join 바로 뒤에 꼭 Fetch를 붙여줘야 한다.

@Query("SELECT DISTINCT t FROM Talent t " +
	"LEFT JOIN FETCH t.writer w " +
	"LEFT JOIN FETCH w.file " +
	"LEFT JOIN FETCH t.files " +
	"LEFT JOIN FETCH t.place " +
	"LEFT JOIN FETCH t.teachedSubject ts " +
	"LEFT JOIN FETCH ts.parent " +
	"LEFT JOIN FETCH t.teachingSubject tgs " +
	"LEFT JOIN FETCH tgs.parent " +
	"WHERE t.id = :talentId")
Optional<Talent> findWithAllAssociationsById(Long talentId);

여기서 주의해야 할 점은 @OneToMany 로 연결되어 있는 엔티티를 join으로 가져온다면 중복이 발생할 수 있다.

이 중복을 해결하기 위해 꼭 SELECT 뒤에 DISTINCT를 붙여야 한다.

붙지지 않는다면 결과 값이 중복되어 나타날 것이다.

그런데 @OneToMany가 2개 이상이라면?

아쉽게도 DISTINCT를 붙인다 해도 반환되지 않고 org.hibernate.loader.MultipleBagFetchException 예외가 발생할 것이다.

이유는 컬렉션 * 컬렉션으로 Certesian 곱이 만들어지기 때문이다.

이제 만든 메서드를 Service에 적용시켜 보자

    @Override
    @Transactional
    public TalentDto.TalentReadResponse read(Long talentId) {
        Talent talent = talentRepository.findWithAllAssociationsById(talentId)
                .orElseThrow(() -> BoardNotFoundException.EXCEPTION);
        talent.updateHit();
        return new TalentDto.TalentReadResponse(talent);
    }
    select
        distinct t1_0.talent_id,
        t1_0.talent_content,
        f2_0.talent_id,
        f2_0.file_id,
        f2_0.file_url,
        f2_0.board_id,
        f2_0.ori_name,
        f2_0.user_id,
        t1_0.gender,
        t1_0.talent_hit,
        t1_0.max_age,
        t1_0.min_age,
        t1_0.moddate,
        p1_0.place_id,
        p1_0.place_name,
        t1_0.regdate,
        t1_0.teached_subject_id,
        ts1_0.subject_id,
        p2_0.subject_id,
        p2_0.parent_id,
        p2_0.subject_name,
        ts1_0.subject_name,
        t1_0.teaching_subject_id,
        ts2_0.subject_id,
        p3_0.subject_id,
        p3_0.parent_id,
        p3_0.subject_name,
        ts2_0.subject_name,
        t1_0.talent_title,
        w1_0.user_id,
        w1_0.active,
        w1_0.career_skills,
        w1_0.email,
        f1_0.file_id,
        f1_0.file_url,
        f1_0.board_id,
        f1_0.ori_name,
        f1_0.talent_id,
        w1_0.gender,
        w1_0.job,
        w1_0.my_subject,
        w1_0.password,
        w1_0.preferred_subject 
    from
        talent t1_0 
    left join
        users w1_0 
            on w1_0.user_id=t1_0.writer 
    left join
        file f1_0 
            on w1_0.user_id=f1_0.user_id 
    left join
        file f2_0 
            on t1_0.talent_id=f2_0.talent_id 
    left join
        place p1_0 
            on p1_0.place_id=t1_0.place_id 
    left join
        subject_category ts1_0 
            on ts1_0.subject_id=t1_0.teached_subject_id 
    left join
        subject_category p2_0 
            on p2_0.subject_id=ts1_0.parent_id 
    left join
        subject_category ts2_0 
            on ts2_0.subject_id=t1_0.teaching_subject_id 
    left join
        subject_category p3_0 
            on p3_0.subject_id=ts2_0.parent_id 
    where
        t1_0.talent_id=?
Hibernate: 
    select
        distinct t1_0.talent_id,
        t1_0.talent_content,
        f2_0.talent_id,
        f2_0.file_id,
        f2_0.file_url,
        f2_0.board_id,
        f2_0.ori_name,
        f2_0.user_id,
        t1_0.gender,
        t1_0.talent_hit,
        t1_0.max_age,
        t1_0.min_age,
        t1_0.moddate,
        p1_0.place_id,
        p1_0.place_name,
        t1_0.regdate,
        t1_0.teached_subject_id,
        ts1_0.subject_id,
        p2_0.subject_id,
        p2_0.parent_id,
        p2_0.subject_name,
        ts1_0.subject_name,
        t1_0.teaching_subject_id,
        ts2_0.subject_id,
        p3_0.subject_id,
        p3_0.parent_id,
        p3_0.subject_name,
        ts2_0.subject_name,
        t1_0.talent_title,
        w1_0.user_id,
        w1_0.active,
        w1_0.career_skills,
        w1_0.email,
        f1_0.file_id,
        f1_0.file_url,
        f1_0.board_id,
        f1_0.ori_name,
        f1_0.talent_id,
        w1_0.gender,
        w1_0.job,
        w1_0.my_subject,
        w1_0.password,
        w1_0.preferred_subject 
    from
        talent t1_0 
    left join
        users w1_0 
            on w1_0.user_id=t1_0.writer 
    left join
        file f1_0 
            on w1_0.user_id=f1_0.user_id 
    left join
        file f2_0 
            on t1_0.talent_id=f2_0.talent_id 
    left join
        place p1_0 
            on p1_0.place_id=t1_0.place_id 
    left join
        subject_category ts1_0 
            on ts1_0.subject_id=t1_0.teached_subject_id 
    left join
        subject_category p2_0 
            on p2_0.subject_id=ts1_0.parent_id 
    left join
        subject_category ts2_0 
            on ts2_0.subject_id=t1_0.teaching_subject_id 
    left join
        subject_category p3_0 
            on p3_0.subject_id=ts2_0.parent_id 
    where
        t1_0.talent_id=?
2024-06-03T17:52:48.179+09:00 DEBUG 34012 --- [p-nio-80-exec-1] org.hibernate.SQL                        : 
    select
        dow1_0.talent_talent_id,
        dow1_0.day_of_week 
    from
        talent_day_of_week dow1_0 
    where
        dow1_0.talent_talent_id=?
Hibernate: 
    select
        dow1_0.talent_talent_id,
        dow1_0.day_of_week 
    from
        talent_day_of_week dow1_0 
    where
        dow1_0.talent_talent_id=?
2024-06-03T17:52:48.240+09:00 DEBUG 34012 --- [p-nio-80-exec-1] org.hibernate.SQL                        : 
    update
        talent 
    set
        talent_content=?,
        gender=?,
        talent_hit=?,
        max_age=?,
        min_age=?,
        moddate=?,
        place_id=?,
        teached_subject_id=?,
        teaching_subject_id=?,
        talent_title=?,
        writer=? 
    where
        talent_id=?
Hibernate: 
    update
        talent 
    set
        talent_content=?,
        gender=?,
        talent_hit=?,
        max_age=?,
        min_age=?,
        moddate=?,
        place_id=?,
        teached_subject_id=?,
        teaching_subject_id=?,
        talent_title=?,
        writer=? 
    where
        talent_id=?

talent을 한번에 가져오고, 조회수를 +1 해주는 것을 볼 수 있다.

2. @EntityGraph

JQPL에서 fetch join을 하게 되면 위처럼 연관관계가 많다면 하드코딩을 해야 된다는 단점이 있다.

대신하여 @EntityGraph를 사용하면 최소화시킬 수 있다.

@EntityGraph(attributePaths = {"writer","writer.file","place","files","teachedSubject","teachedSubject.parent","teachingSubject","teachingSubject.parent"}
@Query("SELECT DISTINCT t FROM Talent t WHERE t.id = :talentId")
Optional<Talent> findWithAllAssociationsById(Long talentId);
    select
        distinct t1_0.talent_id,
        t1_0.talent_content,
        f1_0.talent_id,
        f1_0.file_id,
        f1_0.file_url,
        f1_0.board_id,
        f1_0.ori_name,
        f1_0.user_id,
        t1_0.gender,
        t1_0.talent_hit,
        t1_0.max_age,
        t1_0.min_age,
        t1_0.moddate,
        p1_0.place_id,
        p1_0.place_name,
        t1_0.regdate,
        t1_0.teached_subject_id,
        ts1_0.subject_id,
        p2_0.subject_id,
        p2_0.parent_id,
        p2_0.subject_name,
        ts1_0.subject_name,
        t1_0.teaching_subject_id,
        ts2_0.subject_id,
        p4_0.subject_id,
        p4_0.parent_id,
        p4_0.subject_name,
        ts2_0.subject_name,
        t1_0.talent_title,
        w1_0.user_id,
        w1_0.active,
        w1_0.career_skills,
        w1_0.email,
        f2_0.file_id,
        f2_0.file_url,
        f2_0.board_id,
        f2_0.ori_name,
        f2_0.talent_id,
        f2_0.user_id,
        w1_0.gender,
        w1_0.job,
        w1_0.my_subject,
        w1_0.password,
        w1_0.preferred_subject 
    from
        talent t1_0 
    left join
        file f1_0 
            on t1_0.talent_id=f1_0.talent_id 
    left join
        place p1_0 
            on p1_0.place_id=t1_0.place_id 
    join
        subject_category ts1_0 
            on ts1_0.subject_id=t1_0.teached_subject_id 
    left join
        subject_category p2_0 
            on p2_0.subject_id=ts1_0.parent_id 
    join
        subject_category ts2_0 
            on ts2_0.subject_id=t1_0.teaching_subject_id 
    left join
        subject_category p4_0 
            on p4_0.subject_id=ts2_0.parent_id 
    left join
        users w1_0 
            on w1_0.user_id=t1_0.writer 
    left join
        file f2_0 
            on w1_0.user_id=f2_0.user_id 
    where
        t1_0.talent_id=?
Hibernate: 
    select
        distinct t1_0.talent_id,
        t1_0.talent_content,
        f1_0.talent_id,
        f1_0.file_id,
        f1_0.file_url,
        f1_0.board_id,
        f1_0.ori_name,
        f1_0.user_id,
        t1_0.gender,
        t1_0.talent_hit,
        t1_0.max_age,
        t1_0.min_age,
        t1_0.moddate,
        p1_0.place_id,
        p1_0.place_name,
        t1_0.regdate,
        t1_0.teached_subject_id,
        ts1_0.subject_id,
        p2_0.subject_id,
        p2_0.parent_id,
        p2_0.subject_name,
        ts1_0.subject_name,
        t1_0.teaching_subject_id,
        ts2_0.subject_id,
        p4_0.subject_id,
        p4_0.parent_id,
        p4_0.subject_name,
        ts2_0.subject_name,
        t1_0.talent_title,
        w1_0.user_id,
        w1_0.active,
        w1_0.career_skills,
        w1_0.email,
        f2_0.file_id,
        f2_0.file_url,
        f2_0.board_id,
        f2_0.ori_name,
        f2_0.talent_id,
        f2_0.user_id,
        w1_0.gender,
        w1_0.job,
        w1_0.my_subject,
        w1_0.password,
        w1_0.preferred_subject 
    from
        talent t1_0 
    left join
        file f1_0 
            on t1_0.talent_id=f1_0.talent_id 
    left join
        place p1_0 
            on p1_0.place_id=t1_0.place_id 
    join
        subject_category ts1_0 
            on ts1_0.subject_id=t1_0.teached_subject_id 
    left join
        subject_category p2_0 
            on p2_0.subject_id=ts1_0.parent_id 
    join
        subject_category ts2_0 
            on ts2_0.subject_id=t1_0.teaching_subject_id 
    left join
        subject_category p4_0 
            on p4_0.subject_id=ts2_0.parent_id 
    left join
        users w1_0 
            on w1_0.user_id=t1_0.writer 
    left join
        file f2_0 
            on w1_0.user_id=f2_0.user_id 
    where
        t1_0.talent_id=?
2024-06-03T14:14:35.678+09:00 DEBUG 23360 --- [p-nio-80-exec-1] org.hibernate.SQL                        : 
    select
        dow1_0.talent_talent_id,
        dow1_0.day_of_week 
    from
        talent_day_of_week dow1_0 
    where
        dow1_0.talent_talent_id=?
Hibernate: 
    select
        dow1_0.talent_talent_id,
        dow1_0.day_of_week 
    from
        talent_day_of_week dow1_0 
    where
        dow1_0.talent_talent_id=?
2024-06-03T14:14:35.732+09:00 DEBUG 23360 --- [p-nio-80-exec-1] org.hibernate.SQL                        : 
    update
        talent 
    set
        talent_content=?,
        gender=?,
        talent_hit=?,
        max_age=?,
        min_age=?,
        moddate=?,
        place_id=?,
        teached_subject_id=?,
        teaching_subject_id=?,
        talent_title=?,
        writer=? 
    where
        talent_id=?
Hibernate: 
    update
        talent 
    set
        talent_content=?,
        gender=?,
        talent_hit=?,
        max_age=?,
        min_age=?,
        moddate=?,
        place_id=?,
        teached_subject_id=?,
        teaching_subject_id=?,
        talent_title=?,
        writer=? 
    where
        talent_id=?

실행하면 JPQL 없이도 잘 동작하는 것을 확인할 수 있다.

출처

JPA 모든 N+1 발생 케이스과 해결책

JPA의 사실과 오해 (사실은 JPQL과 Fetch Join의 오해)

profile
Velog에 기록 중

0개의 댓글