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

jpa

[Anifriends] JPA N+1 ๋ฌธ์ œ ๊ฐœ์„ ๊ธฐ

๐Ÿš€  ๊ฐœ์š”

๋ฐ๋ธŒ์ฝ”์Šค 4๊ธฐ์—์„œ ๋ด‰์‚ฌ ํ”Œ๋žซํผ์ธ Anifriends ์„ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ ์กฐํšŒ ์„œ๋น„์Šค์—์„œ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€๋‹ค. N+1 ๋ฌธ์ œ๋ž€ ๋ฌด์—‡์ด๊ณ  ํ•ด๊ฒฐ ๋ฐฉ์•ˆ์€ ์–ด๋–ค ๊ฒƒ๋“ค์ด ์žˆ๋Š”์ง€ ํ•™์Šตํ•ด ๋ณด๋ฉฐ ์ ์šฉํ•ด ๋ณด์ž.

๐Ÿ“– N+1 ๋ฌธ์ œ๋ž€?

JPA ์˜ ์—ฐ๊ด€ ๊ด€๊ณ„๊ฐ€ ์„ค์ •๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•  ๊ฒฝ์šฐ ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๊ฐ–๋Š” ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•˜๊ธฐ ์œ„ํ•ด N ๊ฐœ์˜ ์ถ”๊ฐ€์ ์ธ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์ด๋‹ค. ์ฆ‰, 1๋ฒˆ์˜ ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ ธ์„ ๋•Œ N ๊ฐœ์˜ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ์ด๋‹ค. ์ด๋Š” ์˜๋„์น˜ ์•Š์€ ์ฟผ๋ฆฌ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ฑ๋Šฅ ์ €ํ•˜๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋‹ค.

โœ… N+1 ๋ฌธ์ œ ๊ฐœ์„ ํ•˜๊ธฐ

๋‚ด๊ฐ€ ๋งก์€ ๋ถ€๋ถ„์€ ๋ณดํ˜ธ ๋™๋ฌผ ์กฐํšŒ์ด๋‹ค. ๋ณดํ˜ธ ๋™๋ฌผ ๊ด€๋ จ ERD ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ๋ณดํ˜ธ ๋™๋ฌผ & ๋ณดํ˜ธ ๋™๋ฌผ ์ด๋ฏธ์ง€ : oneToMany
  • ๋ณดํ˜ธ๋™๋ฌผ & ๋ณดํ˜ธ์†Œ : manyToOne
  • ๋ณดํ˜ธ์†Œ & ๋ณดํ˜ธ์†Œ ์ด๋ฏธ์ง€ : OneToOne

๋ณดํ˜ธ ๋™๋ฌผ ๋‹จ์ผ ์กฐํšŒ, ๋ณดํ˜ธ์†Œ์˜ ๋ณดํ˜ธ ๋™๋ฌผ ๋ชฉ๋ก ์กฐํšŒ, ๋ด‰์‚ฌ์ž์˜ ๋ณดํ˜ธ ๋™๋ฌผ ๋ชฉ๋ก ์กฐํšŒ์—์„œ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€๋‹ค. ํ•˜๋‚˜์”ฉ ๊ฐœ์„ ํ•ด ๋ณด์ž.

๋ณดํ˜ธ ๋™๋ฌผ ๋‹จ์ผ ์กฐํšŒ

๋ณดํ˜ธ ๋™๋ฌผ๊ณผ ๋ณดํ˜ธ ๋™๋ฌผ ์ด๋ฏธ์ง€๋Š” oneToMany ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๊ฐ–๋Š”๋‹ค.

@Entity
public class Animal {
    ...
    @OneToMany(mappedBy = "animal", fetch = FetchType.LAZY, ...)
    private List<AnimalImage> images = new ArrayList<>();
}

๋ณดํ˜ธ ๋™๋ฌผ์„ ๋‹จ์ผ ์กฐํšŒํ•  ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

Hibernate: 
    select
        a1_0.animal_id,
        ...
    from
        animal a1_0 
    where
        a1_0.animal_id=?
Hibernate: 
    select
        i1_0.animal_id,
        i1_0.animal_image_id,
        ...
    from
        animal_image i1_0 
    where
        i1_0.animal_id=?
  1. animal ์„ ๋‹จ์ผ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ 1๋ฒˆ
  2. animal_image ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ 1๋ฒˆ

fetch join ์„ ์‚ฌ์šฉํ•ด์„œ ๊ฐœ์„ ํ•ด ๋ณด์ž.

๐Ÿ“– fetch join ์ด๋ž€?

์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๊ฐ–๋Š” ์—”ํ‹ฐํ‹ฐ๋‚˜ ์ปฌ๋ ‰์…˜์„ join ํ•˜์—ฌ ํ•œ ๋ฒˆ์— ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค. JPQL ์—์„œ ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด ์ œ๊ณตํ•˜๋Š” join ์œผ๋กœ ์ผ๋ฐ˜ SQL join ๊ณผ๋Š” ๋‹ค๋ฅด๋‹ค.(SQL join ์€ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•จ๊ป˜ ์กฐํšŒํ•ด์˜ค์ง„ ์•Š๋Š”๋‹ค.)

@Query("select a from Animal a "
        + "join fetch a.images "
        + "where a.animalId = :animalId")
Optional<Animal> findByAnimalIdWithImages(@Param("animalId") Long animalId);

์ด์ œ ๋‹ค์‹œ ์กฐํšŒ ๋กœ์ง์„ ์‹คํ–‰ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ๋ณดํ˜ธ ๋™๋ฌผ์˜ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋œ๋‹ค.

Hibernate: 
select
    a1_0.animal_id,
    ...
from
    animal a1_0 
join
    animal_image i1_0 
        on a1_0.animal_id=i1_0.animal_id 
where
    a1_0.animal_id=?

๋ณดํ˜ธ์†Œ์˜ ๋ณดํ˜ธ ๋™๋ฌผ ๋ชฉ๋ก ์กฐํšŒ

๋ณดํ˜ธ์†Œ๋Š” ์ž์‹ ์ด ๋“ฑ๋กํ•œ ๋ณดํ˜ธ ๋™๋ฌผ ๋ชฉ๋ก๋งŒ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋ณดํ˜ธ ๋™๋ฌผ ๋ชฉ๋ก ์กฐํšŒ ์‹œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

Hibernate: 
    select
        a1_0.animal_id,
        ...
    from
        animal a1_0 
    where
        a1_0.shelter_id=?
    order by
        a1_0.created_at desc
    offset
        ? rows
    fetch
        first ? rows only
Hibernate: 
    select
        i1_0.animal_id,
        i1_0.animal_image_id,
        ...
    from
        animal_image i1_0 
    where
        i1_0.animal_id=?
Hibernate: 
    select
        i1_0.animal_id,
        i1_0.animal_image_id,
        ...
    from
        animal_image i1_0 
    where
        i1_0.animal_id=?
Hibernate: 
    select
        i1_0.animal_id,
        i1_0.animal_image_id,
        ...
    from
        animal_image i1_0 
    where
        i1_0.animal_id=?
....
  1. shelter_id ์— ํ•ด๋‹นํ•˜๋Š” animal ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ 1๋ฒˆ
  2. ๊ฐ animal_id ์— ํ•ด๋‹นํ•˜๋Š” animal_image ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ N๋ฒˆ

์ด N + 1๋ฒˆ์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ์ƒ์„ธ ์กฐํšŒ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ fetch join ์„ ์‚ฌ์šฉํ•ด์„œ ๊ฐœ์„ ํ•ด ๋ณด์ž.

List<Animal> animals = query.select(animal)
    .from(animal)
    .join(animal.images).fetchJoin()
    .where(
        animal.shelter.shelterId.eq(shelterId),
        ...
    )
    .orderBy(animal.createdAt.desc())
    .limit(pageable.getPageSize())
    .offset(paeable.getOffset())
    .fetch();

์ด์ œ ๋‹ค์‹œ ๋ณดํ˜ธ ๋™๋ฌผ ๋ชฉ๋ก์„ ์กฐํšŒํ•ด ๋ณด์ž.

Hibernate: 
select
    a1_0.animal_id,
    ...
from
    animal a1_0 
join
    animal_image i1_0 
        on a1_0.animal_id=i1_0.animal_id 
where
    a1_0.shelter_id=?
order by
    a1_0.created_at desc

์—ฅ? offset ๊ณผ limit ์ด ์ „ํ˜€ ๋™์ž‘ํ•˜์ง€ ์•Š๊ณ  ์žˆ๋‹ค. ๋˜ํ•œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ•˜์ด๋ฒ„๋„ค์ดํŠธ WARN ์ด ๋ฐœ์ƒํ•œ๋‹ค.

db ์— ์ €์žฅ๋˜์–ด ์žˆ๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์ƒ์œผ๋กœ ๊ฐ€์ ธ์™€์„œ ํ•˜์ด๋ฒ„๋„ค์ดํŠธ๊ฐ€ ๊ฒฝ๊ณ ๋ฅผ ์ค€ ๊ฒƒ์ด๋‹ค. offset ๊ณผ limit ์ด ๋™์ž‘ํ•˜์ง€ ์•Š๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ผ๊นŒ?

oneToMany ๊ด€๊ณ„์—์„œ fetch join ์„ ํ•˜๊ฒŒ ๋˜๋ฉด Many ์˜ ๊ฐœ์ˆ˜๋งŒํผ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค. ๊ฐœ๋ฐœ์ž๊ฐ€ ์›ํ•˜๋Š” ํŽ˜์ด์ง•์˜ ๊ธฐ์ค€์€ Many ๊ฐ€ ์•„๋‹Œ One ์ด๋‹ค. ์ฆ‰, ๋ณดํ˜ธ ๋™๋ฌผ ์ด๋ฏธ์ง€๊ฐ€ ์•„๋‹Œ ๋ณดํ˜ธ ๋™๋ฌผ์ด ๊ธฐ์ค€์ด ๋˜์–ด์•ผ ํ•œ๋‹ค.

Hibernate ๋Š” oneToMany ๊ด€๊ณ„์—์„œ fetch join ์„ ํ•  ๊ฒฝ์šฐ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ db ์—์„œ ์กฐํšŒํ•˜์—ฌ ๋ฉ”๋ชจ๋ฆฌ์— ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์˜ฌ๋ ค๋‘๊ณ  ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ  WARN ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ๋ฐฉ๋ฒ•์„ ์„ ํƒํ–ˆ๋‹ค. ์ด ๊ฒฝ์šฐ ์œ„ํ—˜ํ•œ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. ๋งŒ์•ฝ ๋ณดํ˜ธ ๋™๋ฌผ์ด 100๋งŒ๊ฐœ ๋˜๋Š” ๊ทธ ์ด์ƒ์ด db ์— ์ €์žฅ๋˜์–ด ์žˆ๋‹ค๋ฉด ํŽ˜์ด์ง•์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š๊ณ  ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ฆฌ๊ฒŒ ๋˜๋ฏ€๋กœ ์ง€์—ฐ์‹œ๊ฐ„ ์ฆ๊ฐ€๋Š” ๋ฌผ๋ก  ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ์œผ๋กœ ์ธํ•ด OutOfMemoryError ๊ฐ€ ๋ฐœ์ƒํ•  ์œ„ํ—˜์ด ์žˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด ๋˜ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์ธ BatchSize ๋ฅผ ์„ค์ •ํ•ด ๋ณด์ž.

๐Ÿ“– BatchSize ๋ž€?

์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๊ฐ–๋Š” ์—”ํ‹ฐํ‹ฐ๋‚˜ ์ปฌ๋ ‰์…˜์„ ์กฐํšŒํ•  ๋•Œ BatchSize ๋งŒํผ where in ์ ˆ์„ ์‚ฌ์šฉํ•˜์—ฌ ์กฐํšŒํ•ด ์˜ค๋Š” ๋ฐฉ์‹์ด๋‹ค.

@OneToMany(mappedBy = "animal", fetch = FetchType.LAZY, ... )
@BatchSize(size = 100)
private List<AnimalImage> images = new ArrayList<>();

 ๋ณ€๊ฒฝ๋˜๋Š” ์ฟผ๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค. 

Hibernate: 
    select
        a1_0.animal_id,
        ...
    from
        animal a1_0
    where
        a1_0.shelter_id=?
    order by
        a1_0.created_at desc
    offset
        ? rows
    fetch
        first ? row only
Hibernate: 
    select 
        i1_0.animal_id,
        i1_0.animal_image_id,
        ...
    from
        animal_image i1_0
    where
        i1_0.animal_id in (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)

animal_image ๋ฅผ join ํ•ด์˜ค๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์„ค์ •ํ•œ BatchSize ๋งŒํผ where in ๋ฌธ์„ ํ†ตํ•ด ๊ฐ€์ ธ์˜ค๊ฒŒ ๋œ๋‹ค.

๋ด‰์‚ฌ์ž์˜ ๋ณดํ˜ธ ๋™๋ฌผ ๋ชฉ๋ก ์กฐํšŒ

๋ณดํ˜ธ ๋™๋ฌผ๊ณผ ๋ณดํ˜ธ์†Œ๋Š” manyToOne ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๊ฐ–์œผ๋ฉฐ, ๋ณดํ˜ธ์†Œ์™€ ๋ณดํ˜ธ์†Œ ์ด๋ฏธ์ง€๋Š” oneToOne ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ๊ฐ–๋Š”๋‹ค.

@Entity
public class Animal {
    ...
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "shelter_id")
    private Shelter shelter;
}
@Entity
public class Shelter {
    ...
    @OneToOne(mappedBy = "shelter", fetch = FetchType.LAZY, ...)
    private ShelterImage image;
}

๋ด‰์‚ฌ์ž๋Š” ๋“ฑ๋ก๋œ ๋ชจ๋“  ๋ณดํ˜ธ ๋™๋ฌผ ๋ชฉ๋ก์„ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋ฐœ์ƒ๋˜๋Š” ์ฟผ๋ฆฌ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

Hibernate: 
    select
        a1_0.animal_id,
        ...
    from
        animal a1_0
    where
        a1_0.shelter_id=?
    order by
        a1_0.created_at desc
    offset
        ? rows
    fetch
        first ? row only
Hibernate: 
     select
         s1_0.shelter_id,
         ...
    from
        shelter s1_0
    where 
        s1_0.shelter_id=?
Hibernate: 
    select
        s1_0.shelter_image_id,
    ...
    from
        shelter_image s1_0
    where
        s1_0.shelter_id=?
...
Hibernate:
    select
        i1_0.animal_id,
        i1_0.animal_image_id,
        ...
    from
        animal_image i1_0
    where
        i1_0.animal_id=?
...
  1. animal ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ 1๋ฒˆ
  2. ๊ฐ animal ์˜ animal_image ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ N๋ฒˆ
  3. ๊ฐ animal ์˜ shelter ๋ฅผ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ N ๋ฒˆ
  4. ๊ฐ shelter ์˜ shelter_image ๋ชฉ๋ก์„ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ N๋ฒˆ

์ด N + N + N + 1๋ฒˆ์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๊ฐœ์„ ํ•˜๊ธฐ์— ์•ž์„œ ํ•œ ๊ฐ€์ง€ ์˜๋ฌธ์ ์ด ์žˆ๋‹ค. shelter_image ๋Š” ์–ด๋””์—์„œ๋„ ์ ‘๊ทผํ•˜๊ณ  ์žˆ์ง€ ์•Š์€๋ฐ fetchType ์ด LAZY ์ž„์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์ฆ‰์‹œ ๋กœ๋”ฉ์ด ๋˜๊ณ  ์žˆ๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ผ๊นŒ?

๊ทธ ์ด์œ ๋Š” JPA ์—์„œ์˜ ์ง€์—ฐ ๋กœ๋”ฉ์€ ํ”„๋ก์‹œ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

db ์ƒ์—์„œ FK ๋Š” shelter_image ๊ฐ€ ๊ฐ–๊ณ  ์žˆ๋‹ค. ๋งŒ์•ฝ ์—ฐ๊ด€๊ด€๊ณ„ ์ฃผ์ธ์ธ shelter_image๋ฅผ ์กฐํšŒํ•œ๋‹ค๋ฉด shelter ๋Š” ํ”„๋ก์‹œ๋กœ ์ €์žฅ๋  ๊ฒƒ์ด๋‹ค. ํ•˜์ง€๋งŒ, shelter ๋ฅผ ์กฐํšŒํ•œ๋‹ค๋ฉด shelter ๋Š” shelter_image_id ๋ฅผ ๊ฐ–๊ณ  ์žˆ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์กด์žฌํ•˜์ง€ ์•Š์€ ๊ฒƒ์„ ํ”„๋ก์‹œ๋กœ ๊ฐ์Œ€ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์ฆ‰์‹œ ๋กœ๋”ฉ์„ ํ•˜๊ฒŒ ๋œ๋‹ค.

์ฆ‰, oneToOne ์—ฐ๊ด€๊ด€๊ณ„์—์„œ ์—ฐ๊ด€๊ด€๊ณ„ ์ฃผ์ธ์ด ์•„๋‹Œ ๊ณณ์—์„œ ์กฐํšŒ๋ฅผ ํ•  ๊ฒฝ์šฐ FK ๋ฅผ ๊ฐ–๊ณ  ์žˆ์ง€ ์•Š์œผ๋ฏ€๋กœ ์ฆ‰์‹œ๋กœ๋”ฉ์œผ๋กœ ๋™์ž‘ํ•˜๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

๋˜ ํ•œ ๊ฐ€์ง€ ๋ฌธ์ œ๋Š” ๋ด‰์‚ฌ์ž์˜ ๋ณดํ˜ธ๋™๋ฌผ ์กฐํšŒ ๋กœ์ง์˜ ๊ฒฝ์šฐ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” animal_id, animal_name, animal_imageUrl, created_at, shelter_name, shelter_address ์ด 6๊ฐœ์ธ๋ฐ 30๊ฐœ ์ด์ƒ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์žˆ๋‹ค. ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์€ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ์ผ์œผํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. dto projection ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค์ž.

๐Ÿ“– dto projection ์ด๋ž€?

select ์ ˆ์— ๋Œ€์ƒ์„ ์ง€์ •ํ•˜์—ฌ ์›ํ•˜๋Š” ๊ฐ’๋งŒ ์กฐํšŒํ•ด ์˜ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. dto projection ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜์ง€ ์•Š๊ณ  ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์กฐํšŒํ•ด ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

@Getter
public class FindAnimalsResult {

    private final Long animalId;
    private final String animalName;
    private final LocalDateTime createdAt;
    private final String shelterName;
    private final String shelterAddress;
    private final String animalImageUrl;

    @QueryProjection
    public FindAnimalsResult(
        Long animalId,
        String animalName,
        LocalDateTime createdAt,
        String shelterName,
        String shelterAddress,
        String animalImageUrl
    ) {
        this.animalId = animalId;
        this.animalName = animalName;
        this.createdAt = createdAt;
        this.shelterName = shelterName;
        this.shelterAddress = shelterAddress;
        this.animalImageUrl = animalImageUrl;
    }
}
List<FindAnimalsResult> animals = query.select(new QFindAnimalsResult(
        animal.animalId,
        animal.name.name,
        animal.createdAt,
        animal.shelter.name.name,
        animal.shelter.addressInfo.address,
        ExpressionUtils.as(
            select(animalImage.imageUrl)
                .from(animalImage)
                .where(animalImage.animalImageId.eq(
                    select(animalImage.animalImageId.min())
                        .from(animalImage)
                        .where(animalImage.animal.eq(animal)
                        ))), "animalImageUrl")
    ))
    ...

์ด์ œ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ์„ ํƒํ•ด์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

Hibernate: 
    select
        a1_0.animal_id,
        a1_0.name,
        a1_0.created_at,
        s1_0.name,
        s1_0.address,
        (select
            a2_0.image_url 
        from
            animal_image a2_0 
        where
            a2_0.animal_image_id=(
                select
                    min(a3_0.animal_image_id) 
                from
                    animal_image a3_0 
                where
                    a3_0.animal_id=a1_0.animal_id
            )
        ) 
    from
        animal a1_0 
    join
        shelter s1_0 
            on s1_0.shelter_id=a1_0.shelter_id 
    ...

postman ์œผ๋กœ ์‘๋‹ต ์‹œ๊ฐ„์„ ํ…Œ์ŠคํŠธํ•ด ๋ณด๋‹ˆ ๋ชจ๋“  ์ฟผ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๊ฒฝ์šฐ 1.5์ดˆ, ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๊ฐ€์ ธ์˜ฌ ๊ฒฝ์šฐ 0.13์ดˆ๋กœ ์ง€์—ฐ ์‹œ๊ฐ„์ด ์•ฝ 1/10์œผ๋กœ ๊ฐ์†Œ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๐Ÿ”š ๊ฒฐ๋ก 

N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” ์—ฌ๋Ÿฌ๊ฐ€์ง€๊ฐ€ ์žˆ๊ณ  ์ƒํ™ฉ๋งˆ๋‹ค ์ ์ ˆํ•œ ํ•ด๊ฒฐ์ฑ…์ด ์กด์žฌํ•œ๋‹ค.

fetch join

  • toOne ์—ฐ๊ด€๊ด€๊ณ„์ผ ๊ฒฝ์šฐ
  • toMany ์—ฐ๊ด€๊ด€๊ณ„์˜ ๋‹จ์ผ ์กฐํšŒ์ผ ๊ฒฝ์šฐ

BatchSize

  • toMany ์—ฐ๊ด€๊ด€๊ณ„์˜ ๋ฉ€ํ‹ฐ ์กฐํšŒ์ด๋ฉฐ ํŽ˜์ด์ง€๋„ค์ด์…˜์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ
  • toMany ์—ฐ๊ด€๊ด€๊ณ„์—์„œ ์ปฌ๋ ‰์…˜์ด ๋‘ ๊ฐœ ์ด์ƒ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ

dto Projection

  • ์‹ค์ œ ์‚ฌ์šฉํ•˜๋Š” ์ปฌ๋Ÿผ๋ณด๋‹ค ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ปฌ๋Ÿผ์ด ํ›จ์”ฌ ๋งŽ์•„ ์„ฑ๋Šฅ ๊ฐœ์„ ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ
  • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š” ์—†๋Š” ๊ฒฝ์šฐ