๐ŸŒฟSpring

[Spring] JPA N+1 ๋ฌธ์ œ ๋ฐœ์ƒ ์›์ธ๊ณผ ํ•ด๊ฒฐ๋ฒ•์— ๋Œ€ํ•œ ๋ถ„์„๊ณผ ๊ณ ์ฐฐ feat. @OneToOne๊ณผ ์‚ฝ์งˆ

์†Œ์˜ ๐Ÿ€ 2025. 3. 27. 18:48

๋“ค์–ด๊ฐ€๋ฉด์„œ

 

์ตœ๊ทผ์— Spring Data JPA๋ฅผ ๋ฐฐ์šฐ๊ณ  ์ ์šฉํ•ด๋ณด์•˜๋Š”๋ฐ
N+1 ๋ฌธ์ œ์— ๋Œ€ํ•ด ํ—ท๊ฐˆ๋ฆฌ๋Š” ๋ถ€๋ถ„์ด ๋งŽ์Šต๋‹ˆ๋‹ค.

 

๊ตฌ๊ธ€๋ง์„ ํ•ด๋„ ๋ญ”๊ฐ€ ๋ฌ˜ํ•˜๊ฒŒ ๋น„์Šทํ•œ ๋“ฏ ๋‹ค๋ฅธ ์„ค๋ช…์ธ ๊ฒƒ ๊ฐ™๊ณ ..
๊ถ๊ธˆ์ ์ด ํ’€๋ฆฌ์ง€ ์•Š์€ ๋ถ€๋ถ„์ด ์žˆ์–ด ๊ฐœ์ธ์ ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๋Œ๋ ค๊ฐ€๋ฉด์„œ ํ…Œ์ŠคํŠธํ•˜๊ณ  ๋‹ต์„ ์–ป์–ด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ ๊ธ€๋“ค์„ ์ฝ์€ ๋ฐ”ํƒ•์œผ๋กœ ์ œ๊ฐ€ ์ƒ๊ฐํ•˜๋Š” ๋‚ด์šฉ์„ ๋ง๋ถ™์—ฌ ํ‹€๋ฆฐ ๋ถ€๋ถ„์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.(ํŠนํžˆ "๊ณ ์ฐฐ"์ด๋ผ๊ณ  ์ ์€ ๋ถ€๋ถ„)


์ผ๋ฐ˜์ ์ธ ์‰ฌ์šด ์˜ˆ์‹œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ œ๊ฐ€ ์ง€๊ธˆ ๊ฒช๋Š” ๋ฌธ์ œ๋ฅผ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…ํ•œ๊ฑฐ๋ผ ๋‚ด์šฉ์ด ๋ณต์žกํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
ํ‹€๋ฆฐ ๋‚ด์šฉ์€ ๋Œ“๊ธ€ ๋‹ฌ์•„์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค ๐Ÿ˜Š


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

Spring Data JPA ์ด์šฉ ์‹œ
์—”ํ‹ฐํ‹ฐ ๊ฐ„ ์—ฐ๊ด€ ๊ด€๊ณ„์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋กœ

1๊ฐœ์˜ ์—”ํ‹ฐํ‹ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋Š”๋ฐ ์ถ”๊ฐ€๋กœ ์กฐํšŒ๋œ ์—”ํ‹ฐํ‹ฐ์˜ ๊ฐœ์ˆ˜ N๋ฒˆ ๋งŒํผ ์ฟผ๋ฆฌ๊ฐ€ ์ถ”๊ฐ€์ ์œผ๋กœ ์‹คํ–‰๋˜๋Š” ํ˜„์ƒ์„ ๋งํ•œ๋‹ค.

์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ์ฟผ๋ฆฌ(1 ๋ฒˆ) + ์กฐํšŒ๋œ ์—”ํ‹ฐํ‹ฐ์˜ ๊ฐœ์ˆ˜(N ๊ฐœ)๋งŒํผ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•˜๊ธฐ ์œ„ํ•œ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ (N ๋ฒˆ)

 

๊ฐœ์ธ์ ์ธ ์ƒ๊ฐ์œผ๋กœ๋Š” 1+N ๋ฌธ์ œ๋ผ๊ณ  ๋ถ€๋ฅด๋Š” ๊ฒŒ ์ดํ•ด๊ฐ€ ๋” ์‰ฝ๋‹ค.

์ค‘์š”ํ•œ ํฌ์ธํŠธ๋Š” ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค.

โš ๏ธ ํŠธ๋Ÿฌ๋ธ” ์ƒํ™ฉ

๋ฏธ์…˜์—์„œ ์ฑ„ํŒ… ์„œ๋น„์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์žˆ๋Š”๋ฐ ๋งˆ์นจ ์ข‹์€ ์˜ˆ์‹œ๊ฐ€ ์žˆ์–ด ๋“ค๊ณ  ์™”๋‹ค.

์œ ์ €๋ฅผ ์˜๋ฏธํ•˜๋Š” User ์—”ํ‹ฐํ‹ฐ์™€ ์„œ๋ฒ„์— ์—…๋กœ๋“œ๋˜๋Š” ํŒŒ์ผ์„ ํ‘œํ˜„ํ•˜๋Š” BinaryContent๊ฐ€ ์žˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์œ ์ €์˜ ์ ‘์† ์ƒํƒœ๋ฅผ ํ‘œํ˜„ํ•˜๋Š” UserStatus๊ฐ€ ์žˆ๋‹ค.

 

์ด๋•Œ ํ•œ ๋ช…์˜ ์œ ์ €๋Š” ํ•˜๋‚˜์˜ ํ”„๋กœํ•„ ์‚ฌ์ง„๊ณผ ํ•˜๋‚˜์˜ ์ ‘์† ์ƒํƒœ ์ •๋ณด๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ User์™€ BinaryContent, User์™€ UserStatus์˜ ๊ด€๊ณ„ ๋ชจ๋‘ 1:1์ด๋‹ค.

 

DB ์Šคํ‚ค๋งˆ

 

JPA ์—ฐ๊ด€๊ด€๊ณ„ ์ •๋ฆฌ

์ด ๊ด€๊ณ„๋Š” User ์—”ํ‹ฐํ‹ฐ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ”๋“œ๋กœ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๋‹ค. BinaryContent์™€ UserStatus ํด๋ž˜์Šค๋Š” ์—ฌ๊ธฐ์„œ๋Š” ์•ˆ ์ค‘์š”ํ•˜๋ฏ€๋กœ ๋‹ค๋ฃจ์ง€ ์•Š๋Š”๋‹ค.

 

  • User Entity
@Entity
@Table(name = "users")
@NoArgsConstructor
@Getter
public class User extends BaseUpdatableEntity {

  private String username;

  private String email;

  @JsonIgnore // ๋น„๋ฐ€๋ฒˆํ˜ธ ๋…ธ์ถœ ๋ฐฉ์ง€
  private String password

  @OneToOne
  @JoinColumn(name = "profile_id")
  private BinaryContent profile;

  @OneToOne(mappedBy = "user")
  @JoinColumn(name = "status_id")
  private UserStatus status;

  ...

}

 

UserRepository extends JpaRepository์—์„œ findAll()๋กœ ๋ชจ๋“  ์œ ์ €๋ฅผ ์กฐํšŒํ–ˆ์„ ๋•Œ ์‹คํ–‰๋˜๋Š” ์ฟผ๋ฆฌ๋Š” ์•„๋ž˜ ์ฟผ๋ฆฌ 1๊ฐœ๋ฉด ๋œ๋‹ค.

 

select * from users

 

๊ทธ๋Ÿฌ๋‚˜ ์ด 9๊ฐœ์˜ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋œ๋‹ค!

Hibernate: select u1_0.id,u1_0.created_at,u1_0.email,u1_0.password,u1_0.profile_id,u1_0.updated_at,u1_0.username from users u1_0
Hibernate: select bc1_0.id,bc1_0.content_type,bc1_0.created_at,bc1_0.file_name,bc1_0.size from binary_contents bc1_0 where bc1_0.id=?
Hibernate: select us1_0.id,us1_0.created_at,us1_0.last_active_at,us1_0.updated_at,u1_0.id,u1_0.created_at,u1_0.email,u1_0.password,p1_0.id,p1_0.content_type,p1_0.created_at,p1_0.file_name,p1_0.size,u1_0.updated_at,u1_0.username from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id left join binary_contents p1_0 on p1_0.id=u1_0.profile_id where us1_0.user_id=?
Hibernate: select bc1_0.id,bc1_0.content_type,bc1_0.created_at,bc1_0.file_name,bc1_0.size from binary_contents bc1_0 where bc1_0.id=?
Hibernate: select us1_0.id,us1_0.created_at,us1_0.last_active_at,us1_0.updated_at,u1_0.id,u1_0.created_at,u1_0.email,u1_0.password,p1_0.id,p1_0.content_type,p1_0.created_at,p1_0.file_name,p1_0.size,u1_0.updated_at,u1_0.username from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id left join binary_contents p1_0 on p1_0.id=u1_0.profile_id where us1_0.user_id=?
Hibernate: select bc1_0.id,bc1_0.content_type,bc1_0.created_at,bc1_0.file_name,bc1_0.size from binary_contents bc1_0 where bc1_0.id=?
Hibernate: select us1_0.id,us1_0.created_at,us1_0.last_active_at,us1_0.updated_at,u1_0.id,u1_0.created_at,u1_0.email,u1_0.password,p1_0.id,p1_0.content_type,p1_0.created_at,p1_0.file_name,p1_0.size,u1_0.updated_at,u1_0.username from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id left join binary_contents p1_0 on p1_0.id=u1_0.profile_id where us1_0.user_id=?
Hibernate: select bc1_0.id,bc1_0.content_type,bc1_0.created_at,bc1_0.file_name,bc1_0.size from binary_contents bc1_0 where bc1_0.id=?
Hibernate: select us1_0.id,us1_0.created_at,us1_0.last_active_at,us1_0.updated_at,u1_0.id,u1_0.created_at,u1_0.email,u1_0.password,p1_0.id,p1_0.content_type,p1_0.created_at,p1_0.file_name,p1_0.size,u1_0.updated_at,u1_0.username from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id left join binary_contents p1_0 on p1_0.id=u1_0.profile_id where us1_0.user_id=?

๐Ÿ•ต๏ธ ์›์ธ

์ฟผ๋ฆฌ ์† ํ…Œ์ด๋ธ”๋ช…์„ ๋ณด๋ฉด ์กฐํšŒ ์ˆœ์„œ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

  1. User ํ…Œ์ด๋ธ” ์กฐํšŒ
  2. BinaryContent ํ…Œ์ด๋ธ” ์กฐํšŒ
  3. UserStatus ํ…Œ์ด๋ธ” ์กฐํšŒ
  4. BinaryContent ํ…Œ์ด๋ธ” ์กฐํšŒ
  5. UserStatus ํ…Œ์ด๋ธ” ์กฐํšŒ
  6. BinaryContent ํ…Œ์ด๋ธ” ์กฐํšŒ
  7. UserStatus ํ…Œ์ด๋ธ” ์กฐํšŒ
  8. BinaryContent ํ…Œ์ด๋ธ” ์กฐํšŒ
  9. UserStatus ํ…Œ์ด๋ธ” ์กฐํšŒ

์ฆ‰, ์ „์ฒด ์œ ์ € ์กฐํšŒํ•˜๋Š” ๊ฒƒ ์ž์ฒด๋Š” User ํ…Œ์ด๋ธ”์—์„œ ์ฟผ๋ฆฌ 1ํšŒ ์‹คํ–‰์œผ๋กœ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ
๊ฐ ์œ ์ €์™€ ๊ด€๋ จ๋œ UserStatus์™€ BinaryContent ์ •๋ณด๊นŒ์ง€ ๊ฐ€์ ธ์™€์•ผ ํ•˜๋ฉด์„œ
์ถ”๊ฐ€๋กœ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

์ง€๊ธˆ ์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—๋Š” 4๋ช…์˜ ์œ ์ €๊ฐ€ ์žˆ๋‹ค. -> N = 4

์œ ์ €๋ฅผ ์กฐํšŒํ•˜๊ธฐ ์œ„ํ•œ ์ฟผ๋ฆฌ 1ํšŒ + 4๋ฒˆ์˜ BinaryContent ์กฐํšŒ ์ฟผ๋ฆฌ + 4๋ฒˆ์˜ UserStatus ์กฐํšŒ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

 

๊ฐ€์ ธ์˜ฌ User ๋ฐ์ดํ„ฐ๊ฐ€ 4๊ฐœ ๋ฟ์ด๋‹ˆ ์ง€๊ธˆ ์‹คํ–‰๋˜๋Š” ์ฟผ๋ฆฌ๋Š” 9๊ฐœ ๋ฟ์ด๋‹ค.
ํ•˜์ง€๋งŒ ํ‰๋ฒ”ํ•œ ์„œ๋น„์Šค์—์„œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋ช‡ ๋ฐฑ, ์ฒœ, ๋งŒ ๋‹จ์œ„๊นŒ์ง€ ์˜ฌ๋ผ๊ฐˆ ์ˆ˜ ์žˆ๋‹ค.
๊ทธ๋•Œ๋งˆ๋‹ค N๋ฒˆ์˜ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด DB ์กฐํšŒ ์„ฑ๋Šฅ์ด ์•„์ฃผ ๋ถ€๋‹ด๋  ๊ฒƒ์ด๋‹ค.

๋ฐœ์ƒ ์›์ธ ๋ถ„์„

N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ด์œ ๋กœ ์ง€์—ฐ ๋กœ๋”ฉ์„ ๋งŽ์ด ์–ธ๊ธ‰ํ•œ๋‹ค.

์ง€์—ฐ ๋กœ๋”ฉ์ด๋ž€, ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ์˜ ๋ฐ์ดํ„ฐ๋Š” ์‹ค์ œ๋กœ ์‚ฌ์šฉ๋  ๋•Œ๊นŒ์ง€ ๋กœ๋”ฉ์„ ์ง€์—ฐ์‹œํ‚ค๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.


User ์—”ํ‹ฐํ‹ฐ์™€ ์—ฐ๊ด€๋œ ํ•˜์œ„ ์—”ํ‹ฐํ‹ฐ๋Š” ์‹ค์ œ๋กœ ๊ทธ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์ „๊นŒ์ง€๋Š” DB์—์„œ ๋กœ๋”ฉ๋˜์ง€ ์•Š๋Š”๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ๋‹จ์ˆœํžˆ ์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋Š” ๊ฒŒ ๋ฐ”๋กœ ์ดํ•ด๋˜์ง€ ์•Š์•˜๊ณ  ์ง€๊ธˆ ๋‚ด ์ฝ”๋“œ๊ฐ€ ์ง€์—ฐ ๋กœ๋”ฉ์ด๋ผ๋Š” ํ™•์‹ ๋„ ์•ˆ ๋“ค์—ˆ๋‹ค.

๊ณ ์ฐฐ: ์ง€์—ฐ ๋กœ๋”ฉ์ผ ๋•Œ๋งŒ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๊ฐ€?

์ฒ˜์Œ์—๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ๊ฑธ ์ฝ”๋“œ์—์„œ ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”, ์ฆ‰ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘์ ์œผ๋กœ ํ˜ธ์ถœํ•œ๋‹ค๊ณ  ๋ฐ›์•„๋“ค์˜€๋‹ค.
๊ทธ๋ฆฌ๊ณ  ๋‚ด๊ฐ€ ๋ช…์‹œํ•˜์ง€ ์•Š์€ ๊ฒฝ์šฐ ์ง€์—ฐ ๋กœ๋”ฉ์ด ๋””ํดํŠธ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

public List<UserDto> findAll() {
    List<User> users = userRepository.findAll();
    return users.stream()
        .sorted(Comparator.comparing(user -> user.getCreatedAt()))
        .map(userMapper::toDto)
        .toList();
  }

 

์ง€๊ธˆ ์ด ์„œ๋น„์Šค ์ฝ”๋“œ์—์„œ๋Š” Mapper๋ฅผ ํ†ตํ•ด DTO๋กœ ๋ณ€ํ™˜ํ•˜๋ฉฐ User ์—”ํ‹ฐํ‹ฐ ์† BinaryContent์™€ UserStatus ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
(Mapper ์ฝ”๋“œ๊นŒ์ง€๋Š” ํ•„์š” ์—†์„ ๊ฒƒ ๊ฐ™์•„ ์ฒจ๋ถ€ํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค.)

 

๊ทธ๋Ÿผ ๋งŒ์•ฝ findAll()๋กœ User ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ๋งŒ ํ•˜๊ณ  ์•„๋ฌด๊ฒƒ๋„ ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋‹ˆ N+1 ๋ฌธ์ œ๊ฐ€ ์•ˆ ๋ฐœ์ƒํ•˜๋Š” ๊ฑธ๊นŒ?

 

์„œ๋น„์Šค ์ฝ”๋“œ๋ฅผ ์•„๋ž˜์ฒ˜๋Ÿผ ํ•ด๋ณด์•˜๋‹ค. ์˜ฌ๋ฐ”๋ฅธ ์‘๋‹ต์€ ์•„๋‹ˆ์ง€๋งŒ ์•„๋ฌดํŠผ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ํ•„์š”์—†์„ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ–ˆ๋‹ค.

public List<UserDto> findAll() {
    List<User> users = userRepository.findAll();
    return new ArrayList<>();
  }
(์›๋ž˜๋Š” ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ๋œจ๋Š”๋ฐ ์ž„์˜๋กœ * ํ‘œ์‹œํ•˜์˜€๋‹ค.)

Hibernate: select * from users u1_0
Hibernate: select * bc1_0 where bc1_0.id=?
Hibernate: select * from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id left join binary_contents p1_0 on p1_0.id=u1_0.profile_id where us1_0.user_id=?
Hibernate: select * from binary_contents bc1_0 where bc1_0.id=?
Hibernate: select * from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id left join binary_contents p1_0 on p1_0.id=u1_0.profile_id where us1_0.user_id=?
Hibernate: select * from binary_contents bc1_0 where bc1_0.id=?
Hibernate: select * from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id left join binary_contents p1_0 on p1_0.id=u1_0.profile_id where us1_0.user_id=?
Hibernate: select * from binary_contents bc1_0 where bc1_0.id=?
Hibernate: select * from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id left join binary_contents p1_0 on p1_0.id=u1_0.profile_id where us1_0.user_id=?

 

๊ทธ๋Ÿฐ๋ฐ ์—ฌ์ „ํžˆ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

 

๊ทธ ์ด์œ ๋Š” @OneToOne ์• ๋…ธํ…Œ์ด์…˜์„ ์“ฐ๋Š” ๊ฒฝ์šฐ ์ง€์—ฐ ๋กœ๋”ฉ์ด ์•„๋‹ˆ๋ผ ์ฆ‰์‹œ ๋กœ๋”ฉ ์ „๋žต์ด ๋””ํดํŠธ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์—ฐ๊ด€๊ด€๊ณ„ ๋””ํดํŠธ fetch ์ „๋žต
@ManyToOne: EAGER
@OneToOne: EAGER
@ManyToMany: LAZY
@OneToMany: LAZY

 

User ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•˜๋ฉด @OneToOne ๊ด€๊ณ„์ธ UserStatus์™€ BinaryContent ๊ฐ์ฒด๋“ค๋„ ์ฆ‰์‹œ ํ•จ๊ป˜ ์กฐํšŒํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

 

๐Ÿ“Œ ๋ณดํ†ต ์ง€์—ฐ ๋กœ๋”ฉ ๋•Œ๋ฌธ์— N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๊ณ  ๋งŽ์ด ํ‘œํ˜„ํ•˜๋˜๋ฐ ์ฆ‰์‹œ ๋กœ๋”ฉ์ด์–ด๋„ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ณ ์ฐฐ: ์™œ @OneToOne์€ ์ฆ‰์‹œ ๋กœ๋”ฉ์ด ๋””ํดํŠธ์ผ๊นŒ?

์•ž์„œ ๋งํ–ˆ๋“ฏ์ด ๋‚œ ๋””ํดํŠธ๋Š” ๋‹ค ์ง€์—ฐ ๋กœ๋”ฉ์ผ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ–ˆ๋‹ค.
์™œ๋ƒ๋ฉด ์ฆ‰์‹œ ๋ชจ๋“  ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋„ ๊ฐ€์ ธ์˜ค๋Š” ์ฆ‰์‹œ ๋กœ๋”ฉ๋ณด๋‹ค๋Š” ์ง€์—ฐ ๋กœ๋”ฉ์ด ์„ฑ๋Šฅ๋ฉด์—์„œ ๋‚ซ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

๊ทธ๋Ÿฐ๋ฐ ์™œ @OneToOne์—์„œ๋Š” ์ฆ‰์‹œ ๋กœ๋”ฉ์ด ๋””ํดํŠธ์ผ๊นŒ?


๋‚˜๋Š” ์ด๊ฒƒ์„ Spring Data JPA์˜ ๊ตฌํ˜„์ฒด Hibernate๊ฐ€ User ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋งตํ•‘ํ•˜๋Š” ๊ณผ์ •์œผ๋กœ ์ดํ•ดํ–ˆ๋‹ค.

User ๊ฐ์ฒด๋ฅผ DB์—์„œ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ์œ„ํ•ด JpaRepository์—์„œ findAll() ๋ฉ”์„œ๋“œ๋กœ select * from users ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
select * from users ์ฟผ๋ฆฌ๋กœ users ํ…Œ์ด๋ธ”์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™”๋‹ค.

 

 

(users ํ…Œ์ด๋ธ”์—์„œ status๋Š” ๊ฐ€์ง€๊ณ  ์žˆ์ง€ ์•Š์œผ๋ฏ€๋กœ ์ผ๋‹จ profile_id ์ปฌ๋Ÿผ๋งŒ ์‚ดํŽด๋ณด์ž.)
ํ”„๋กœํ•„ ์‚ฌ์ง„ ์ฆ‰ BinaryContent ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•œ ์ •๋ณด๋Š” DB์—์„œ๋Š” binaryContents ํ…Œ์ด๋ธ”์˜ PK๋ฅผ FK๋กœ๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.

 

ํ•˜์ง€๋งŒ OneToOne ๊ด€๊ณ„์ด๋ฏ€๋กœ User ๊ฐ์ฒด์—๋Š” ๋‹น์—ฐํžˆ BinaryContent ๊ฐ์ฒด๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋Š” ๊ฒŒ ์ž์—ฐ์Šค๋Ÿฝ๋‹ค.
๋”ฐ๋ผ์„œ User ์—”ํ‹ฐํ‹ฐ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๋‹น์—ฐํžˆ BinaryContent ์ •๋ณด๋„ ์žˆ์„ ๊ฒƒ์ด๋ผ ๋ฏฟ๊ฒŒ ๋˜๊ณ , ๋”ฐ๋ผ์„œ BinaryContent์˜ PK๊ฐ€ ์•„๋‹ˆ๋ผ BinaryContent profile ๊ฐ์ฒด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

 

์ด ๊ฐ์ฒด๋Š” select * from users ์ฟผ๋ฆฌ๋กœ ์–ป์€ profile_id๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ binaryContents ํ…Œ์ด๋ธ”์—์„œ ์กฐํšŒํ•˜์—ฌ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

@Entity
@Table(name = "users")
@NoArgsConstructor
@Getter
public class User extends BaseUpdatableEntity {

  (์ƒ๋žต)

  @OneToOne
  @JoinColumn(name = "profile_id")
  private BinaryContent profile;

  ...

}

 

์ฆ‰ Hibernate๋Š” User ์—”ํ‹ฐํ‹ฐ๋ฅผ ์™„์ „ํ•˜๊ฒŒ ๋งตํ•‘ํ•˜๊ธฐ ์œ„ํ•ด
OneToOne ๊ด€๊ณ„์—์„œ๋Š” ์ฆ‰์‹œ ๋กœ๋”ฉ ์ „๋žต์„ ์ทจํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
select * from users ์ฟผ๋ฆฌ๋กœ ์–ป์€ FK ๊ฐ’์„ ๋ฐ”ํƒ•์œผ๋กœ
ํ•„์š”ํ•œ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”(binaryContents)์—์„œ๋„ select ๋ฌธ์„ ์‹คํ–‰ํ•˜์—ฌ
BinaryContent profile ๊ฐ์ฒด๋ฅผ ๋งตํ•‘ํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์น˜๋Š” ๊ฒƒ์ด๋‹ค.
๋”ฐ๋ผ์„œ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

 

์ด ๊ณผ์ •์„ ์ •๋ฆฌํ•˜์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

์—”ํ‹ฐํ‹ฐ A ์กฐํšŒ๋ฅผ ์œ„ํ•ด JPA์—์„œ DB ํ…Œ์ด๋ธ”์„ ๊ฐ์ฒด๋กœ ๋งตํ•‘ํ•  ๋•Œ,
A๊ฐ€ ๊ฐ€์ง„ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ B๊ฐ€ @OneToOne, @ManyToOne ๊ด€๊ณ„๋ผ๋ฉด
์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ B๋ฅผ ํฌํ•จํ•œ ์™„์ „ํ•œ A ์—”ํ‹ฐํ‹ฐ๋กœ ๋งตํ•‘ํ•˜๊ธฐ ์œ„ํ•ด
๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” A๋ฅผ ๋กœ๋”ฉํ•  ๋•Œ B๋„ ์ฆ‰์‹œ ๋กœ๋”ฉํ•œ๋‹ค.

๊ณ ์ฐฐ: ์—ฐ๊ด€ ๊ด€๊ณ„ ๋ฐฉํ–ฅ์— ๋”ฐ๋ฅธ ์ฐจ์ด

 

User:BinaryContent์™€ User:UserStatus๋Š” ๋‘˜ ๋‹ค 1:1 ๊ด€๊ณ„์ง€๋งŒ ๋‹จ๋ฐฉํ–ฅ, ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„๋กœ ์กฐ๊ธˆ ๋‹ค๋ฅด๋‹ค.
์ด๋ฒˆ์— ๊ทธ ๊ด€๊ณ„์— ๋”ฐ๋ฅธ ์ฐจ์ด๊ฐ€ ์žˆ๋Š”์ง€ ์‚ดํŽด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.

 

๋งŒ์•ฝ User ์—”ํ‹ฐํ‹ฐ์—์„œ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ ๋ชจ๋‘๋ฅผ ์ง€์—ฐ ๋กœ๋”ฉ์œผ๋กœ ๋ช…์‹œํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

@Entity
@Table(name = "users")
@NoArgsConstructor
@Getter
public class User extends BaseUpdatableEntity {

  (์ƒ๋žต)

  @OneToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "profile_id")
  private BinaryContent profile;

  @OneToOne(mappedBy = "user", fetch = FetchType.LAZY)
  @JoinColumn(name = "status_id")
  private UserStatus status;

  ...

}
UserService

public List<UserDto> findAll() {
    List<User> users = userRepository.findAll();
    return users.stream()
        .sorted(Comparator.comparing(user -> user.getCreatedAt()))
        .map(userMapper::toDto)
        .toList();
  }

 

๊ทธ ๊ฒฐ๊ณผ ์—ฌ์ „ํžˆ N+1 ๋ฌธ์ œ๋Š” ๋ฐœ์ƒํ•˜๋Š”๋ฐ ์ฒ˜์Œ๊ณผ ์ฟผ๋ฆฌ ์‹คํ–‰ ์ˆœ์„œ๊ฐ€ ๋‹ค๋ฅด๋‹ค.


UserStatus๋ฅผ ๋จผ์ € ์กฐํšŒํ•œ ํ›„, BinaryContent๋ฅผ ์กฐํšŒํ•˜๊ณ  ์žˆ๋‹ค.

๋˜‘๊ฐ™์ด Lazy Loading์ธ๋ฐ ์กฐํšŒ๋œ๋‹ค๋ฉด findAll()์„ ํ•  ๋•Œ๋Š” ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰ ์•ˆ ๋˜๊ณ , DTO ๋ณ€ํ™˜ ๊ณผ์ •์—์„œ UserStatus์™€ BinaryContent ๋ฒˆ๊ฐˆ์•„๊ฐ€๋ฉด์„œ ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ์•„๊ฐ€์•ผ ํ•˜๋Š” ๊ฑฐ ์•„๋‹๊นŒ?

 

๋‹ต์€ '์•„๋‹ˆ๋‹ค'์ด๋‹ค.

๋‚ด๊ฐ€ ์„ธ์šด ๊ฐ€์„ค์€ "๋‘ ํ…Œ์ด๋ธ” ์กฐํšŒ์˜ ๋ชฉ์ ์ด ๋‹ค๋ฅด๊ธฐ ๋•Œ๋ฌธ"์ด๋‹ค.

Hibernate: select (์ƒ๋žต) from users u1_0
Hibernate: select (์ƒ๋žต) from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where us1_0.user_id=?
Hibernate: select (์ƒ๋žต) from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where us1_0.user_id=?
Hibernate: select (์ƒ๋žต) from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where us1_0.user_id=?
Hibernate: select (์ƒ๋žต) from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where us1_0.user_id=?
Hibernate: select (์ƒ๋žต) from binary_contents bc1_0 where bc1_0.id=?
Hibernate: select (์ƒ๋žต) from binary_contents bc1_0 where bc1_0.id=?
Hibernate: select (์ƒ๋žต) from binary_contents bc1_0 where bc1_0.id=?
Hibernate: select (์ƒ๋žต) from binary_contents bc1_0 where bc1_0.id=?

 

๐ŸŽฏ UserStatus ํ…Œ์ด๋ธ”์„ ์กฐํšŒํ•œ ์ด์œ 

 

User ์—”ํ‹ฐํ‹ฐ์—์„œ UserStatus๋Š” mappedBy="user"๋กœ FK๊ฐ€ UserStatus ํ…Œ์ด๋ธ”์— ์กด์žฌํ•œ๋‹ค.
๊ทธ๋ ‡๋‹ค๋ฉด select * from users๋ฅผ ํ–ˆ์„ ๋•Œ UserStatus์— ๋Œ€ํ•œ ์ •๋ณด๋Š” ํ•˜๋‚˜๋„ ์•Œ ์ˆ˜ ์—†๋‹ค.

 

ํ•˜์ง€๋งŒ ์•ž์„œ ๋งํ–ˆ๋“ฏ์ด 1:1 ๊ด€๊ณ„์ด๋ฏ€๋กœ User ์—”ํ‹ฐํ‹ฐ์— ๋‹น์—ฐํžˆ UserStatus ๊ฐ์ฒด๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

๋•Œ๋ฌธ์— Hibernate๊ฐ€ User ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋งตํ•‘ํ•  ๋•Œ UserStatus๋ฅผ ์ง€๊ธˆ ์‚ฌ์šฉํ•˜์ง€๋Š” ์•Š๋”๋ผ๋„ ์ผ๋‹จ ์—ฐ๊ด€ ๊ฐ์ฒด๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋ ค ํ•œ๋‹ค.

Hibernate: select us1_0.id,us1_0.created_at,us1_0.last_active_at,us1_0.updated_at,u1_0.id,u1_0.created_at,u1_0.email,u1_0.password,u1_0.profile_id,u1_0.updated_at,u1_0.username from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where 

 

๋”ฐ๋ผ์„œ ์ด ์ฟผ๋ฆฌ์˜ ์‹คํ–‰ ๋ชฉ์ ์€ UserStatus ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค๊ธฐ ๋ณด๋‹ค๋Š”
Hibernate๊ฐ€ User ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋งตํ•‘ํ•˜๊ธฐ ์œ„ํ•ด์„œ userRepository.findAll()์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ์—ฐ๊ด€๋œ ๋ฐ์ดํ„ฐ UserStatus๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

๊ฐ€์„ค์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์—”ํ‹ฐํ‹ฐ ์ฝ”๋“œ์—์„œ BinaryContent์™€ UserStatus ๊ฐ์ฒด๋ฅผ ๋ชจ๋‘ Lazy๋กœ ํ•˜๊ณ , ์„œ๋น„์Šค์—์„œ๋Š” User ์—”ํ‹ฐํ‹ฐ๋งŒ ์กฐํšŒํ•  ๋ฟ ๋‘ ๊ฐ์ฒด ๋ชจ๋‘ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  UserRepository์—์„œ findAl()๋งŒ ํ–ˆ์„ ๋•Œ ์ฟผ๋ฆฌ๋ฅผ ๋ณด์ž.

Hibernate: select (์ƒ๋žต) from users u1_0
Hibernate: select (์ƒ๋žต) from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where us1_0.user_id=?
Hibernate: select (์ƒ๋žต) from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where us1_0.user_id=?
Hibernate: select (์ƒ๋žต) from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where us1_0.user_id=?
Hibernate: select (์ƒ๋žต) from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where us1_0.user_id=?

 

์ด์ฒ˜๋Ÿผ UserStatus ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ์—๋„ User ์—”ํ‹ฐํ‹ฐ ์กฐํšŒ ์‹œ UserStatus๋ฅผ ํ•จ๊ป˜ ์กฐํšŒํ•˜๊ณ  ์žˆ๋‹ค.

 

์ฆ‰ Lazy Loading์œผ๋กœ ์„ค์ •ํ–ˆ์Œ์—๋„ mappedBy๋กœ ์—ฐ๊ฒฐ๋œ ์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„์—์„œ๋Š”, Hibernate๊ฐ€ ์‹ค์ œ๋กœ ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ๋‚ด๋ถ€์ ์œผ๋กœ ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ ๋ฐ์ดํ„ฐ์˜ ์กด์žฌ๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. 


์ด ๊ฒฝ์šฐ Lazy Loading์œผ๋กœ ์„ค์ •ํ•œ ๋ณด๋žŒ์ด ๋ณ„๋กœ ์—†๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.


๐ŸŽฏ BinaryContent ํ…Œ์ด๋ธ”์„ ์กฐํšŒํ•œ ์ด์œ 

 

๋ฐ˜๋ฉด BinaryContent๋Š” profile_id ์ปฌ๋Ÿผ์œผ๋กœ ๊ทธ ์กด์žฌ๋ฅผ ํ™•์ธํ–ˆ๊ธฐ์— Hibernate๊ฐ€ ๋‹ค์‹œ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๋กœ ํ™•์ธํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
์ง€์—ฐ ๋กœ๋”ฉ์ด๊ธฐ ๋•Œ๋ฌธ์— BinaryContent๋Š” ๊ทธ ๊ฐ์ฒด๊ฐ€ ์‚ฌ์šฉ๋  ๋•Œ ํ˜ธ์ถœ๋œ๋‹ค.


userRepository.findAll()์—์„œ๋Š” binaryContents ํ…Œ์ด๋ธ”์„ ์กฐํšŒํ•˜์ง€ ์•Š๋Š”๋‹ค.
return ๋ฌธ์—์„œ DTO๋กœ ๋ณ€ํ™˜ํ•˜๋ฉฐ BinaryContent ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—(๋ฐ์ดํ„ฐ ์‚ฌ์šฉ ์‹œ์ ) User์™€ UserStatus๋ฅผ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ๋ณด๋‹ค ๋‚˜์ค‘์— ์‹คํ–‰๋œ๋‹ค.

 

๋งŒ์•ฝ ์„œ๋น„์Šค ์ฝ”๋“œ์—์„œ BinaryContent ๊ฐ์ฒด๋ฅผ ์“ฐ์ง€ ์•Š์œผ๋ฉด

public List<UserDto> findAll() {
    List<User> users = userRepository.findAll();
    return new ArrayList<>();
  }
Hibernate: select (์ƒ๋žต)e from users u1_0
Hibernate: select (์ƒ๋žต) from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where us1_0.user_id=?
Hibernate: select (์ƒ๋žต) from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where us1_0.user_id=?
Hibernate: select (์ƒ๋žต) from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where us1_0.user_id=?
Hibernate: select (์ƒ๋žต) from user_statuses us1_0 left join users u1_0 on u1_0.id=us1_0.user_id where us1_0.user_id=?

 

์œ„์—์„œ ์–ธ๊ธ‰ํ•œ UserStatus๋งŒ ์กฐํšŒํ•˜๊ณ  BinaryContent๋Š” ์กฐํšŒํ•˜์ง€ ์•Š๋Š”๋‹ค.

 

ํ•˜์ง€๋งŒ ์•ž์„œ ๋ดค๋“ฏ์ด BinaryContent๋ฅผ ์ฝ”๋“œ์—์„œ ํ˜ธ์ถœํ•˜๋Š” ์ˆœ๊ฐ„ ์กฐํšŒ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋œ๋‹ค. 
์ผ๋ฐ˜์ ์ธ ์„œ๋น„์Šค์—์„œ ์œ ์ € ์ •๋ณด๋ฅผ ๋ณผ ๋•Œ ํ”„๋กœํ”Œ ์‚ฌ์ง„๋„ ๋Š˜ ํ•จ๊ป˜ ๋ณด๋“ฏ, 
1:1 ๊ด€๊ณ„๋ผ๋ฉด ๋Œ€๋ถ€๋ถ„ ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ๊นŒ์ง€ ์“ฐ์ง€ ์•Š์„๊นŒ? 
๊ทธ๋ ‡๋‹ค๋ฉด ์ง€์—ฐ ๋กœ๋”ฉ์ด์–ด๋„ ์–ด์ฐจํ”ผ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๋Š” ๋ฐœ์ƒํ•  ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.

 

๐Ÿ“Œ ์ด๋ ‡๊ฒŒ ์—ฐ๊ด€ ๊ด€๊ณ„์— ๋”ฐ๋ผ์„œ ๋‹ค๋ฅด๊ฒŒ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๊ณ , ์ง€์—ฐ ๋กœ๋”ฉ ์‹œ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์„ ์ดํ•ดํ–ˆ๋‹ค.

๊ณ ์ฐฐ: ์ฆ‰์‹œ ๋กœ๋”ฉ์ธ๋ฐ ์™œ join์ด ์•„๋‹๊นŒ?

๊ทธ๋Ÿฐ๋ฐ ์ฆ‰์‹œ ๋กœ๋”ฉ์ด๋ผ๋ฉด User ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ joinํ•ด์„œ ๊ฐ€์ ธ์˜จ๋‹ค๋ฉด
ํ•˜๋‚˜์˜ ์ฟผ๋ฆฌ๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์„ ํ…๋ฐ ์™œ join์ด ์•„๋‹ˆ๋ผ ์ถ”๊ฐ€ select ์ฟผ๋ฆฌ๊ฐ€ ๋‚˜๊ฐ€๋Š”๊ฑธ๊นŒ?

์ง€๊ธˆ๊นŒ์ง€ ์šฐ๋ฆฌ๊ฐ€ ์˜ˆ์‹œ๋กœ ๋ณธ User ์—”ํ‹ฐํ‹ฐ๋Š” ๋‘ ๊ฐœ์˜ @OneToOne ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์—ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ํ•„๋“œ ํ•˜๋‚˜๋ฅผ ์ง€์›Œ๋ณด๊ณ  ์กฐํšŒํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

@Entity
@Table(name = "users")
@NoArgsConstructor
@Getter
public class User extends BaseUpdatableEntity {

  private String username;

  private String email;

  @JsonIgnore // ๋น„๋ฐ€๋ฒˆํ˜ธ ๋…ธ์ถœ ๋ฐฉ์ง€
  private String password

  @OneToOne
  @JoinColumn(name = "profile_id")
  private BinaryContent profile;

  ...

}

 

์ฟผ๋ฆฌ๊ฐ€ 1๊ฐœ๋งŒ ์‹คํ–‰๋œ๋‹ค! N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.
User ํ…Œ์ด๋ธ”์„ ์กฐํšŒํ•  ๋•Œ LEFT JOIN์œผ๋กœ BinaryContents๋ฅผ ํ•จ๊ป˜ ์กฐํšŒํ•˜๊ณ  ์žˆ๋‹ค.
์‚ฌ์‹ค @OneToOne ๊ด€๊ณ„๊ฐ€ ํ•˜๋‚˜๋ผ๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ Join์„ ํ•˜์—ฌ ํ•˜๋‚˜์˜ ์ฟผ๋ฆฌ๋งŒ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

 

@OneToOne ์—ฐ๊ด€ ๊ด€๊ณ„๊ฐ€ ํ•˜๋‚˜๋ผ๋ฉด ํ•œ ๋ฒˆ์˜ Join๋งŒ ์“ฐ๋ฉด ๋˜๋‹ˆ JPA๋Š” ํ•œ ๋ฒˆ์— ๋ฌด์‚ฌํžˆ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๋Š” ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

select
  u1_0.id,
  u1_0.created_at,
  u1_0.email,
  u1_0.password,
  p1_0.id,
  p1_0.content_type,
  p1_0.created_at,
  p1_0.file_name, 
  p1_0.size,
  u1_0.updated_at,
  u1_0.username 
from users u1_0 
left join binary_contents p1_0 
  on p1_0.id=u1_0.profile_id

 

๊ทธ๋Ÿฌ๋‚˜ @OneToOne ๊ด€๊ณ„๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ ์žˆ๋Š” ๊ฒฝ์šฐ JPA๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ํ…Œ์ด๋ธ”์„ JOIN ํ•˜์ง€ ์•Š๊ณ  ์ถ”๊ฐ€์ ์ธ SELECT ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

JOIN์ด ๋งŽ์•„์ง€๋ฉด ๋ณต์žกํ•˜๊ณ  ์ž˜๋ชป๋œ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์ƒ๊ธฐ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

> ๐Ÿ“Œ @OneToOne ๊ด€๊ณ„๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ ์žˆ๋‹ค๋ฉด
join์ด ์•„๋‹ˆ๋ผ ์ถ”๊ฐ€ select๋กœ ์ฆ‰์‹œ ๊ฐ€์ ธ์˜จ๋‹ค.

์ด๋Ÿฌํ•œ ์ฆ‰์‹œ ๋กœ๋”ฉ์€ ํ•„์š”ํ•œ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜จ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์ง€๋งŒ
์˜๋„์น˜ ์•Š๊ฒŒ join์„ ํ•  ์ˆ˜ ์žˆ์–ด์„œ
์‹ค๋ฌด์—์„œ ์—”ํ‹ฐํ‹ฐ ๊ฐ„์˜ ๊ด€๊ณ„๊ฐ€ ๋ณต์žกํ•ด์งˆ์ˆ˜๋ก ์กฐ์ธ์œผ๋กœ ์ธํ•œ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ํ”ผํ•  ์ˆ˜ ์—†๊ธฐ์—
์ง€์—ฐ ๋กœ๋”ฉ์„ ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉํ•  ๊ฒƒ์„ ๊ถŒ์žฅํ•˜๊ณ  ์žˆ๋‹ค.

 

๊ณ ์ฐฐ: ์™œ N+1 ๋ฌธ์ œ์—์„œ ์ง€์—ฐ ๋กœ๋”ฉ ์–ธ๊ธ‰์ด ๋งŽ์„๊นŒ?

์ฆ‰์‹œ ๋กœ๋”ฉ์„ ์–ธ๊ธ‰ํ•˜๋Š” ๊ธ€๋„ ๋งŽ์ง€๋งŒ
๋งŽ์€ ๊ธ€์—์„œ '์ง€์—ฐ ๋กœ๋”ฉ ์‹œ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค'๊ณ  ํ•œ๋‹ค.

์ฆ‰์‹œ ๋กœ๋”ฉ์—์„œ๋„ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š”๋ฐ ์™œ ์‚ฌ๋žŒ๋“ค์ด ์ง€์—ฐ ๋กœ๋”ฉ๋งŒ ์–ธ๊ธ‰ํ• ๊นŒ ๊ถ๊ธˆํ–ˆ๋‹ค.
๋‚ด๊ฐ€ ์ƒ๊ฐํ•œ ์ด์œ ๋Š” ์ฆ‰์‹œ ๋กœ๋”ฉ์—์„œ๋Š” N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ด์ „ ๊ณ ์ฐฐ์—์„œ @OneToOne ์—ฐ๊ด€๊ด€๊ณ„ ํ•˜๋‚˜์ธ ๊ฒฝ์šฐ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•˜๋‹ค.
์ด์ฒ˜๋Ÿผ ์ฆ‰์‹œ ๋กœ๋”ฉ์—์„œ ํ•ญ์ƒ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ง€์—ฐ ๋กœ๋”ฉ์„ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ๊ฒƒ์„ ๊ถŒ์žฅํ•˜๊ธฐ์—
๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ์ง€์—ฐ ๋กœ๋”ฉ์„ ๋” ๊ฐ•์กฐํ•ด ์„ค๋ช…ํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

 

N+1 ๋ฌธ์ œ ๋ฐœ์ƒ ์›์ธ ์ •๋ฆฌ

๋ถ€๋ชจ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ ์—ฐ๊ด€๋œ ์ž์‹ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ถ”๊ฐ€๋กœ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋ผ๊ณ  ํ•˜๋Š” ๊ฒŒ ๊ฐ€์žฅ ์ •ํ™•ํ•œ ๋‹ต๋ณ€์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

  • ์ฆ‰์‹œ ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•  ๋•Œ: ์—ฐ๊ด€๊ด€๊ณ„๊ฐ€ ์žˆ๋Š” ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฆ‰์‹œ ๋กœ๋”ฉ ์ „๋žต์„ ์‚ฌ์šฉํ•˜๋ฉด ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋“ค์„ Join์œผ๋กœ ํ•œ ๋ฒˆ์— ์กฐํšŒํ•˜์ง€๋งŒ ๊ฒฝ์šฐ์— ๋”ฐ๋ผ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
  • ์ง€์—ฐ ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•  ๋•Œ: ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ•  ๋•Œ๋Š” ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์ง€๋งŒ, ์‹ค์ œ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•  ๋•Œ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๐Ÿ’ก ํ•ด๊ฒฐ๋ฐฉ์•ˆ

Fetch Join

ํ•œ์ชฝ ํ…Œ์ด๋ธ”์„ ์กฐํšŒํ•˜๊ณ  ์—ฐ๊ฒฐ๋œ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์„ ๋”ฐ๋กœ ์กฐํšŒํ•˜๊ธฐ ๋•Œ๋ฌธ์— N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด,
๋ฏธ๋ฆฌ ๋‘ ํ…Œ์ด๋ธ”์„ Joinํ•˜์—ฌ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ๋œ๋‹ค.

๋ ˆํฌ์ง€ํ† ๋ฆฌ์—์„œ JPQL๋กœ ์ง์ ‘ Fetch Join์„ ์ž‘์„ฑํ•ด์ฃผ์ž.

@Query("SELECT u FROM User u LEFT JOIN FETCH u.profile JOIN FETCH u.status")
  List<User> findAllFetchJoin();

 

๋กœ๊ทธ์—์„œ ์ฟผ๋ฆฌ๊ฐ€ 1๊ฐœ๋งŒ ์‹คํ–‰๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
profile์€ LEFT JOIN์„ ํ•œ ์ด์œ ๋Š” '์œ ์ €๋Š” ํ”„๋กœํ•„ ์‚ฌ์ง„์„ ๊ฐ€์ง€์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ๋‹ค'๋Š” ์š”๊ตฌ์‚ฌํ•ญ ๋•Œ๋ฌธ์— ํ”„๋กœํ•„ ์‚ฌ์ง„์ด ์—†๋Š” ์œ ์ €๊นŒ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด์„œ๋‹ค.

Hibernate: select u1_0.id,u1_0.created_at,u1_0.email,u1_0.password,p1_0.id,p1_0.content_type,p1_0.created_at,p1_0.file_name,p1_0.size,s1_0.id,s1_0.created_at,s1_0.last_active_at,s1_0.updated_at,u1_0.updated_at,u1_0.username from users u1_0 left join binary_contents p1_0 on p1_0.id=u1_0.profile_id join user_statuses s1_0 on u1_0.id=s1_0.user_id

 

Join๊ณผ Fetch Join์€ ๋ญ๊ฐ€ ๋‹ค๋ฅผ๊นŒ?

  • Join์€ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์กฐ์ธ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋Š” ๊ฒƒ์ด๋‹ค.
    • ๋งŒ์•ฝ Fetch ์—†์ด @Query("SELECT u FROM User u LEFT JOIN u.profile JOIN u.status")๋ฅผ ์‹คํ–‰ํ•œ๋‹ค๋ฉด User์— ๋งž๋Š” profile๊ณผ status ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ๊ธฐ๋งŒ ํ–ˆ์ง€, ๊ทธ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ฆ‰์‹œ ๊ฐ€์ ธ์˜ค์ง€๋Š” ์•Š๋Š”๋‹ค. ์—ฌ์ „ํžˆ ๋กœ๋”ฉ์ด ์ง€์—ฐ๋œ ์ƒํƒœ์ด๋‹ค.
    • profile๊ณผ status ์ •๋ณด๋„ ํ•„์š”ํ•˜๋‹ค๋ฉด ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•ด์•ผ ํ•œ๋‹ค.
  • Fetch Join์€ ์—ฐ๊ด€๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ๊ฐ€์ ธ์™€์„œ User์™€ ํ•จ๊ป˜ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ๋“ฑ๋กํ•œ๋‹ค.

Fetch Join์˜ ๋‹จ์ 

  • ํŽ˜์ด์ง• ๋ถˆ๊ฐ€๋Šฅ: ์ฟผ๋ฆฌ ํ•œ ๋ฒˆ์— ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์— ํŽ˜์ด์ง• ๋‹จ์œ„๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†๋‹ค.
  • ์„ฑ๋Šฅ ๋ถ€๋‹ด: @OneToMany ๊ด€๊ณ„์—์„œ Fetch Join์„ ํ•˜๋Š” ๊ฒฝ์šฐ Many์ชฝ ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŽ๋‹ค๋ฉด ์กฐ์ธ์œผ๋กœ ์ธํ•œ ๋ถ€๋‹ด์ด ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.
  • ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€.

@EntityGraph

@EntityGraph์˜ attributePaths์— ์ฟผ๋ฆฌ ์ˆ˜ํ–‰ ์‹œ ๋ฐ”๋กœ ๊ฐ€์ ธ์˜ฌ ํ•„๋“œ๋ช…์„ ์ง€์ •ํ•˜๋ฉด Eager Loading ๋ฐฉ์‹์œผ๋กœ ๊ฐ€์ ธ์™€ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.

User Entity
@OneToOne
@JoinColumn(name = "profile_id")
private BinaryContent profile;

@OneToOne(mappedBy = "user")
@JoinColumn(name = "status_id")
private UserStatus status;

 

์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์—์„œ ์“ด ํ•„๋“œ๋ช…์„ ๊ทธ๋Œ€๋กœ attributePaths์— ๋ช…์‹œํ•œ๋‹ค.

UserRepository

@EntityGraph(attributePaths = {"profile", "status"})
List<User> findAll();

 

์ฟผ๋ฆฌ๊ฐ€ ํ•œ ๋ฒˆ๋งŒ ๋ฐœ์ƒํ•œ๋‹ค.

Hibernate: select u1_0.id,u1_0.created_at,u1_0.email,u1_0.password,p1_0.id,p1_0.content_type,p1_0.created_at,p1_0.file_name,p1_0.size,s1_0.id,s1_0.created_at,s1_0.last_active_at,s1_0.updated_at,u1_0.updated_at,u1_0.username from users u1_0 left join binary_contents p1_0 on p1_0.id=u1_0.profile_id left join user_statuses s1_0 on u1_0.id=s1_0.user_id
  • Fetch Join์€ inner join์„ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.
    • ์œ„ fetch join์—์„œ๋Š” user_statuses ํ…Œ์ด๋ธ”๊ณผ์˜ join์„ ๋ณด๋ฉด ๋œ๋‹ค.
  • @EntityGraph๋Š” left join(outer join)์„ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ ์ฟผ๋ฆฌ ๋ฐ˜ํ™˜ ๊ฒฐ๊ณผ๊ฐ€ ์ ์€ inner join์ด ์„ฑ๋Šฅ์ด ๋” ์ข‹๊ณ 
Fetch Join์—์„œ๋„ ํ•„์š”ํ•˜๋ฉด outer join์„ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—
๊ตณ์ด @EntityGraph๋ฅผ ์“ธ ์ด์œ ๋Š” ์—†์„ ๊ฒƒ ๊ฐ™๋‹ค.

@BatchSize

@ManyToOne์ด๋‚˜ @OneToMany ๊ด€๊ณ„๋ผ๋ฉด
์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ•œ ๋ฒˆ์— ๋ฌถ์–ด ์กฐํšŒํ•˜๋Š” ์šฉ๋„๋กœ ์ด ์• ๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

@Entity
public class User {
    @OneToMany(fetch = FetchType.LAZY)
    @BatchSize(size = 10)
    private List<Post> posts;
}

 

@BatchSize(size = 10)๋Š” User ์—”ํ‹ฐํ‹ฐ์—์„œ Post ์—”ํ‹ฐํ‹ฐ๋ฅผ 10๊ฐœ์”ฉ ๋ฌถ์–ด์„œ ์กฐํšŒํ•˜๊ฒ ๋‹ค๋Š” ์˜๋ฏธ์ด๋‹ค.
Post ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋งŽ์„ ๊ฒฝ์šฐ ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ์ฒ˜๋ฆฌ๋˜์ง€๋งŒ, ํ•œ ๋ฒˆ์— ์ตœ๋Œ€ 10๊ฐœ์”ฉ ๋ฌถ์–ด์„œ ์กฐํšŒํ•˜๋ฏ€๋กœ ์„ฑ๋Šฅ์ด ๊ฐœ์„ ๋œ๋‹ค.

 

๊ทธ๋Ÿฌ๋‚˜ ์ฟผ๋ฆฌ ์ˆ˜๋ฅผ ์ค„์ด๋Š” ๋ฐ ๋„์›€์ด ๋˜์ง€๋งŒ
๊ฐ€์ ธ์˜ฌ ๋ฐ์ดํ„ฐ ์ˆ˜๊ฐ€ size๋ณด๋‹ค ํฌ๋‹ค๋ฉด ์—ฌ์ „ํžˆ ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋œ๋‹ค๋Š” ์ ์—์„œ
N+1 ๋ฌธ์ œ์˜ ์™„์ „ํ•œ ํ•ด๊ฒฐ๋ฒ•์ด๋ผ๊ณ  ๋ณด๊ธฐ ์–ด๋ ต๋‹ค.

 

ํ•˜์ง€๋งŒ @BatchSize๋Š” ์ง€์—ฐ ๋กœ๋”ฉ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด์„œ ์ถ”๊ฐ€์ ์ธ ์กฐ์ธ์„ ํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ๋‹จ์ˆœํžˆ ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋ฌถ์–ด์„œ ํ•œ ๋ฒˆ์— ์กฐํšŒํ•˜๋Š” ๋ฐฉ์‹์ด๋ผ ๋” ๊ฐ„๋‹จํ•œ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋œ๋‹ค๊ณ  ํ•œ๋‹ค.

์ƒํ™ฉ์— ๋งž๊ฒŒ ์ ์ ˆํ•œ ๋ฐฉ๋ฒ•์„ ํƒํ•˜๋ฉด ๋œ๋‹ค.

 

์ƒˆ๋กญ๊ฒŒ ์•Œ๊ฒŒ ๋œ ์  ์ •๋ฆฌ

  • N+1 ๋ฌธ์ œ๋ž€
    • 1๊ฐœ์˜ ์—”ํ‹ฐํ‹ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋Š”๋ฐ ์ถ”๊ฐ€๋กœ ์กฐํšŒ๋œ ์—”ํ‹ฐํ‹ฐ์˜ ๊ฐœ์ˆ˜ N๋ฒˆ ๋งŒํผ ์ฟผ๋ฆฌ๊ฐ€ ์ถ”๊ฐ€์ ์œผ๋กœ ์‹คํ–‰๋˜๋Š” ํ˜„์ƒ
  • ์—ฐ๊ด€๊ด€๊ณ„ ๋””ํดํŠธ fetch ์ „๋žต
    • @ManyToOne: EAGER
    • @OneToOne: EAGER
    • @ManyToMany: LAZY
    • @OneToMany: LAZY
  • ์ฆ‰์‹œ ๋กœ๋”ฉ๊ณผ ์ง€์—ฐ ๋กœ๋”ฉ ๋ชจ๋‘ N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.
  • fetch = FetchType.LAZY๋กœ ์„ค์ •ํ•˜์—ฌ๋„ ๊ทธ ์—ฐ๊ด€ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์ด๋ผ๋ฉด ์ง€์—ฐ ๋กœ๋”ฉ์ด ์•ˆ ๋  ์ˆ˜๋„ ์žˆ๋‹ค.
  • @OneToOne์˜ ๊ธฐ๋ณธ ์„ค์ •์ด ์ฆ‰์‹œ ๋กœ๋”ฉ์ด์–ด๋„ ๊ด€๊ณ„๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ ์žˆ๋‹ค๋ฉด join์ด ์•„๋‹ˆ๋ผ ์ถ”๊ฐ€ select ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
  • N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ
    • Fetch Join
    • @Entity Graph
    • @BatchSize

์ฐธ๊ณ ์ž๋ฃŒ

๋ฐ˜์‘ํ˜•