Post

LAZY ์ „๋žต์˜ Proxy

๐ŸŽฌ Intro

LAZY ์ „๋žต์—์„œ ์‚ฌ์šฉํ•˜๋Š” Proxy์— ๋Œ€ํ•ด ์•Œ์•„๋ด…์‹œ๋‹ค.

โœ… LAZY

  • JPA ์—ฐ๊ด€๊ด€๊ณ„์—์„œ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์ ธ์˜ฌ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์ „๋žต ์ค‘ ํ•˜๋‚˜์ด๋‹ค.
  • ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์‹œ์ ์—์„œ ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๊ณ  ํ”„๋ก์‹œ ํ˜•ํƒœ๋กœ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋œ๋‹ค.
  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์‚ฌ์šฉ๋˜๋Š” ์‹œ์ ์— ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. ๋•Œ๋ฌธ์— N + 1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

โœ… Proxy

  • ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์•„๋‹Œ ๊ฐ€์งœ ๊ฐ์ฒด์ด๋‹ค.
  • ํ”„๋ก์‹œ ๊ฐ์ฒด์˜ ์ดˆ๊ธฐํ™”๋Š” ํ•œ๋ฒˆ๋งŒ ์ผ์–ด๋‚œ๋‹ค. ์‚ฌ์šฉ๋˜๋Š” ์‹œ์  or Hibernate.initialize() ๋ฅผ ํ†ตํ•ด์„œ ์ดˆ๊ธฐํ™” ๋œ๋‹ค. ์ดˆ๊ธฐํ™” ๋œ ๋ฐ์ดํ„ฐ๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ 1์ฐจ ์บ์‹œ์— ์ €์žฅ๋œ๋‹ค.
  • ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ์ดˆ๊ธฐํ™” ๋˜๋”๋ผ๋„ ๊ฐ์ฒด์— ์ถ”๊ฐ€๋กœ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ์™€์˜ ์—ฐ๊ด€๊ด€๊ณ„ ์ „๋žต์ด LAZY๋ผ๋ฉด ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ”„๋ก์‹œ๋กœ ๊ฐ€์ ธ์˜จ๋‹ค.
  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์ ˆ๋Œ€๋กœ ์‹ค์ œ ๊ฐ์ฒด๋กœ ๋ฐ”๋€Œ์ง€ ์•Š๋Š”๋‹ค. ์ฆ‰, ์ดˆ๊ธฐํ™”๊ฐ€ ๋˜๋”๋ผ๋„ ํ”„๋ก์‹œ ๊ฐ์ฒด๋กœ ๋‚จ๋Š”๋‹ค.
  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์‚ฌ์šฉ๋˜๋Š” ์‹œ์ ์— DB์—์„œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด DB์—์„œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ๋ ค๋ฉด ํŠธ๋žœ์žญ์…˜์•ˆ์— ์žˆ์–ด์•ผํ•œ๋‹ค. ํŠธ๋žœ์žญ์…˜์ด ์—†๋Š”๋ฐ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด์„œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ๋ ค๊ณ  ํ•˜๋ฉด LazyInitializationException์ด ๋ฐœ์ƒํ•œ๋‹ค.

๐Ÿ“ ์˜ˆ์ œ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class Book {

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

  private String title;

  private String author;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "book_store_id")
  private BookStore bookStore;

  @Builder
  public Book(final Long id, final String title, final String author, final BookStore bookStore) {
    this.id = id;
    this.title = title;
    this.author = author;
    this.bookStore = bookStore;
  }

}


@Entity
@Table
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Data
public class BookStore {

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

  private String name;
  @Builder
  public BookStore(final Long id, final String name) {
    this.id = id;
    this.name = name;
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Service
@RequiredArgsConstructor
public class BookService {


  private final BookRepository bookRepository;
  private final BookStoreRepository bookStoreRepository;
  private final EntityManager em;


  @Transactional
  public BookStore getProxyBookStore() {

    BookStore bookStore = BookStore.builder()
      .name("ํ…Œ์ŠคํŠธ ๋ถ์Šคํ† ์–ด1")
      .build();

    Book book = Book.builder()
      .title("ํ…Œ์ŠคํŠธ ๋ถ1")
      .bookStore(bookStore)
      .build();

    bookStoreRepository.save(bookStore);
    bookRepository.save(book);

    // ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด DB์— ๋‚ด์šฉ์„ ๋ฐ˜์˜ํ•˜๊ณ , 1์ฐจ ์บ์‹œ๋ฅผ ์ง€์šด๋‹ค.
    em.flush();
    em.clear();

    // save๋ฅผ ํ•˜๋Š” ์‹œ์ ์—์„œ book์˜ id๊ฐ€ ์ž๋™์œผ๋กœ ํ• ๋‹น ๋œ๋‹ค.
    // ๋”ฐ๋ผ์„œ 1์ฐจ ์บ์‹œ๋ฅผ ์ง€์šฐ๋”๋ผ๋„ id๋Š” ์ด๋ฏธ ํ• ๋‹น๋œ ์ƒํƒœ
    Long bookId = book.getId();

    // book๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด LAZY ์ „๋žต์— ์˜ํ•ด bookStore๋Š” ํ”„๋ก์‹œ ๊ฐ์ฒด๋กœ ๊ฐ€์ ธ์™€์ง„๋‹ค.
    Book getBook = bookRepository.findById(bookId).orElse(null);

    BookStore getBookStore = getBook.getBookStore();

    /**
     * ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ๊ฐ•์ œ๋กœ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์‹ค์ œ ๊ฐ์ฒด์— ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ์ „๋ถ€ ๊ฐ€์ ธ์˜จ๋‹ค.
     * ๋ถ์Šคํ† ์–ด์™€ ์ถ”๊ฐ€๋กœ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋“ค์€ LAZY์ผ ๊ฒฝ์šฐ ํ”„๋ก์‹œ๋กœ ๊ฐ€์ ธ์˜ค๊ณ , EAGER๋ผ๋ฉด ์ฆ‰์‹œ ๊ฐ€์ ธ์˜จ๋‹ค.
     */
    // Hibernate.initialize(getBookStore);

    return getBookStore;

  }
}
  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด em.flush()๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์Œ“์ธ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ , em.clear()๋ฅผ ํ˜ธ์ถฃํ•˜์—ฌ 1์ฐจ ์บ์‹œ๋ฅผ ์ง€์šด๋‹ค.
  • book์˜ id๋Š” saveํ•˜๋Š” ์‹œ์ ์— ์‹ค์ œ ๊ฐ์ฒด์— ์ €์žฅ๋˜๊ธฐ ๋•Œ๋ฌธ์— 1์ฐจ ์บ์‹œ๋ฅผ ์ง€์›Œ๋„ id ๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋‹ค.
  • findBy()๋ฅผ ํ†ตํ•ด ์ €์žฅํ•œ book์„ ๊ฐ€์ ธ์˜ค๋ฉด ์ด์™€ ์—ฐ๊ด€๋œ bookStore๋Š” LAZY ์ „๋žต์— ์˜ํ•ด ํ”„๋ก์‹œ ๊ฐ์ฒด๋กœ ๊ฐ€์ ธ์™€์ง„๋‹ค.
  • Hibernate.initialize()๋ฅผ ํ†ตํ•ด์„œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์ดˆ๊ธฐํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์— ํ•„์š”ํ•œ ์ •๋ณด๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋œ๋‹ค. ๋งŒ์•ฝ BookStore์— ์„ ์–ธ๋œ ์—”ํ‹ฐํ‹ฐ์˜ ์—ฐ๊ด€๊ด€๊ณ„ ์ „๋žต์ด LAZY๋ผ๋ฉด ์—ฐ๊ด€๋œ ๊ฐ์ฒด๋Š” ํ”„๋ก์‹œ ๊ฐ์ฒด๋กœ, EAGER๋ผ๋ฉด ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ์˜ ์‹ค์ œ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
  • ๊ฒฐ๊ณผ์ ์œผ๋กœ getProxyBookStore() ๋Š” BookStore ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@SpringBootTest
public class JpaTest6 {

  @Autowired
  private BookService bookService;

  private BookStore proxyBookStore;


  @BeforeEach
  void setUp() throws Exception {

    proxyBookStore = bookService.getProxyBookStore();

  }

  @DisplayName("LazyInitializationException ๋ฐœ์ƒ ์„ฑ๊ณต ํ…Œ์ŠคํŠธ")
  @Test
  void proxyTest1() throws Exception {
    // given

    System.out.println("proxyBookStore : " + proxyBookStore.getClass());
    System.out.println("BookStore : " + BookStore.class);

    // when

    // then

    // ํ”„๋ก์‹œ ๋ถ ์Šคํ† ์–ด๋Š” ํ”„๋ก์‹œ ํƒ€์ž…์ด๋‹ค.
    assertThat(proxyBookStore).isInstanceOf(HibernateProxy.class);
    // ํ”„๋ก์‹œ ๋ถ ์Šคํ† ์–ด๋Š” ๋ถ ์Šคํ† ์–ด ์ธ์Šคํ„ด์Šค ํƒ€์ž…์ด๋‹ค.
    assertThat(proxyBookStore).isInstanceOf(BookStore.class);
    // ํ”„๋ก์‹œ ๋ถ ์Šคํ† ์–ด์˜ ํด๋ž˜์Šค๋Š” ๋ถ์Šคํ† ์–ด ํด๋ž˜์Šค์™€ ๋™์ผํ•˜์ง€ ์•Š๋‹ค.
    assertThat(proxyBookStore.getClass()).isNotEqualTo(BookStore.class);
    assertThatThrownBy(() -> proxyBookStore.getName())
      .isInstanceOf(LazyInitializationException.class);
  }

  @DisplayName("LazyInitializationException ๋ฐœ์ƒ ์‹คํŒจ ํ…Œ์ŠคํŠธ")
  @Test
  @Transactional
  void proxyTest2() throws Exception {
    //given

    //when

    //then

    assertThat(proxyBookStore.getName()).isNotNull();
    assertThat(Hibernate.isInitialized(proxyBookStore)).isTrue();

  }
}
1
2
proxyBookStore : class com.example.jparemind1.domain1.BookStore$HibernateProxy$ULvIdbkE
BookStore : class com.example.jparemind1.domain1.BookStore

img_10.png

proxyTest1()

  • getProxyBookStore()์—์„œ ๋ฐ˜ํ™˜ํ•œ bookStore๋ฅผ ์ถœ๋ ฅํ•ด๋ณด๋ฉด ํ”„๋ก์‹œ ๊ฐ์ฒด๋ผ๋Š”๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
  • HibernateProxy.class๋Š” ํ”„๋ก์‹œ ๊ฐ์ฒด ํƒ€์ž…์ด๋‹ค. ๋”ฐ๋ผ์„œ bookStore๋Š” ํ•ด๋‹น ํƒ€์ž…๊ณผ ๊ฐ™๋‹ค.
  • bookStore๋Š” ํ”„๋ก์‹œ ๊ฐ์ฒด ์ด์ง€๋งŒ ์ธ์Šคํ„ด์Šค ํƒ€์ž…์€ BookStore.class์™€ ๊ฐ™๋‹ค.
  • ํ•˜์ง€๋งŒ ์‹ค์ œ bookStore์˜ ์‹ค์ œ class ๋Š” BookStore.class์™€ ๊ฐ™์ง€ ์•Š๋‹ค.
  • ๋”ฐ๋ผ์„œ ๋‘˜์˜ ๋น„๊ต๋Š” โ€œ==โ€ ๋™์ผ์„ฑ์œผ๋กœ ํ•˜๋ฉด ์•ˆ๋œ๋‹ค.

    proxyTest2()

  • getProxyBookStore()์˜ ํŠธ๋žœ์žญ์…˜๊ณผ ํ…Œ์ŠคํŠธ proxyTest2()์˜ ํŠธ๋žœ์žญ์…˜์ด ๋ฌถ์—ฌ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— proxyTest2() ํ…Œ์ŠคํŠธ๊ฐ€ ๋๋‚˜๋Š” ์‹œ์ ์— ๋กค๋ฐฑ์ด ๋œ๋‹ค. ๋”ฐ๋ผ์„œ ์‹ค์ œ๋กœ DB์— SELECT์„ ์ œ์™ธํ•œ ์ฟผ๋ฆฌ๋Š” ์‹คํ–‰๋˜์ง€ ์•Š๋Š”๋‹ค.
  • proxyBookStore๊ฐ€ getName()์„ ํ†ตํ•ด ์ดˆ๊ธฐํ™” ๋˜์—ˆ๋‹ค. ๋”ฐ๋ผ์„œ Hibernate.isInitialized()๋Š” true๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

โœจ Summary

  • LAZY ์ „๋žต์€ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง€์—ฐ ๋กœ๋”ฉํ•˜์—ฌ ์ฒ˜์Œ์—” ํ”„๋ก์‹œ ๊ฐ์ฒด๋กœ ๊ฐ€์ ธ์˜ค๊ณ , ์‹ค์ œ ์‚ฌ์šฉ ์‹œ DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.
  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋Œ€๋ฆฌํ•˜๋ฉฐ, ์ดˆ๊ธฐํ™”๋  ๋•Œ๊นŒ์ง€๋Š” ๊ฐ€์งœ ๊ฐ์ฒด๋กœ ๋‚จ์•„ ์žˆ๋‹ค. ์ดˆ๊ธฐํ™” ํ›„์—๋„ ํ”„๋ก์‹œ ๊ฐ์ฒด ์ž์ฒด๋Š” ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ๋กœ ๋ฐ”๋€Œ์ง€ ์•Š๋Š”๋‹ค.
  • Hibernate.initialize()๋Š” ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ๊ฐ•์ œ๋กœ ์ดˆ๊ธฐํ™”ํ•˜์—ฌ, ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ DB์—์„œ ๊ฐ€์ ธ์˜จ๋‹ค. ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ๋งŒ ์ดˆ๊ธฐํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ๋™์ผ์„ฑ(==) ๋น„๊ต๋ฅผ ํ•˜๋ฉด ์•ˆ๋œ๋‹ค. ํ”„๋ก์‹œ์˜ ํด๋ž˜์Šจ๋А ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค์™€ ๋‹ค๋ฅด๋‹ค. ๋”ฐ๋ผ์„œ instanceof๋กœ ๋น„๊ตํ•ด์•ผํ•œ๋‹ค.
  • ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ์ดˆ๊ธฐํ™” ๋˜๋ฉด LazyInitializationException์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค.
This post is licensed under CC BY 4.0 by the author.