๐ JPA - @ManyToOne ์ฆ์ ๋ก๋ฉ๊ณผ ์ง์ฐ ๋ก๋ฉ(Eager Loading / Lazy Loading)
Spring Data JPA์์ @ManyToOne(N:1)์ผ๋ก ์ฐ๊ด๊ด๊ณ๊ฐ ์ค์ ๋์ด ์๋ 2๊ฐ์ Entity๊ฐ ์กด์ฌํ ๋,
๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ์์ ๋ณด๋ฉด join์ด ํ์ํฉ๋๋ค.
์ค์ @ManyToOne์ ๊ฒฝ์ฐ FK์ชฝ์ ์ํฐํฐ๋ฅผ ๊ฐ์ ธ์ฌ ๋ PK์ชฝ์ ์ํฐํฐ๋ ๊ฐ์ด ๊ฐ์ ธ์ค๊ฒ ๋๋๋ฐ์, ์ด๋ฌํ ๊ณผ์ ์ด ๊ผญ ํ์ํ๊ฑด์ง, ํ์ํ์ง ์๋ค๋ฉด ์ด๋ป๊ฒ ํด๊ฒฐํ ์ ์๋์ง ์ฆ์ ๋ก๋ฉ๊ณผ ์ง์ฐ ๋ก๋ฉ์ ๋ํด ์์ ๋ฅผ ํตํด ์ดํด๋ณด๊ฒ ์ต๋๋ค.
โจ๏ธ Board, Member ์ํฐํฐ
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
@Getter
@ToString(exclude = "member")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Board extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long bno;
@Column
private String title;
@Column
private String content;
@ManyToOne
private Member member;
}
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
@Getter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Member extends BaseEntity {
@Id
private String email;
@Column
private String password;
@Column
private String name;
}
์ ์ฝ๋์์ Board(๊ฒ์๊ธ)์ Member(ํ์)์ @ManyToOne(N:1)์ ๊ด๊ณ๋ฅผ ๋งบ๊ณ ์์ต๋๋ค.
์ฆ ํ ๋ช ์ ํ์(1)์ ์ฌ๋ฌ ๊ฒ์๊ธ(N)์ ์์ฑํ ์ ์์ต๋๋ค.
๋ฐ๋ผ์ ๊ฒ์๊ธ์ ๊ธฐ์ค์ผ๋ก ๋ดค์๋ ํ์๊ณผ์ ๊ด๊ณ๋ N:1์ด ๋ฉ๋๋ค.
๋ง์ฝ Board์ ์ํฐํฐ๋ฅผ ์กฐํํ๋ค๋ฉด Member์ ์ํฐํฐ๋ ํจ๊ป ์กฐํ๊ฐ ๋๋๋ฐ์,
@Test
@DisplayName("์ฆ์ ๋ก๋ฉ์ ์ฐ๊ด๊ด๊ณ๋ฅผ ๋งบ๊ณ ์๋ ์ํฐํฐ๋ ๋ชจ๋ ์กฐํ๊ฐ ๋๋ค")
void testRead1() {
/* given */
Optional<Board> result = boardRepository.findById(100L);
/* when */
Board board = result.get();
/* then */
System.out.println(board);
}
์คํ๋ SQL ์ฟผ๋ฆฌ๋ฌธ์ ํ์ธํด๋ณด๋ฉด board ํ ์ด๋ธ ์ธ์๋ member ํ ์ด๋ธ๋ ํจ๊ป ์กฐํ๋ฅผ ํ๊ณ join์ผ๋ก ์ฒ๋ฆฌ๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๋ง์ฝ Board๋ฅผ ์กฐํํ ๋ Member์ ํจ๊ป ์กฐํํ์ง ์์ผ๋ ค๋ฉด ์ด๋ป๊ฒ ํด์ผํ ๊น์?
โจ๏ธ ์ฆ์ ๋ก๋ฉ(Eager Loading)๊ณผ ์ง์ฐ ๋ก๋ฉ(Lazy Loading)
์ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์ ๊ฐ์ด ํน์ ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ์ฐ๊ด๋ ๋ชจ๋ ์ํฐํฐ๋ฅผ ๊ฐ์ด ๋ก๋ฉํ๋ ๊ฒ์ ์ฆ์ ๋ก๋ฉ(Eager Loading)์ด๋ผ๊ณ ํฉ๋๋ค.
์ด์ ๊ฐ์ ์ฆ์ ๋ก๋ฉ์ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ๋ชจ๋ ๊ฐ์ ธ์จ๋ค๋ ์ฅ์ ์ด ์์ง๋ง,
์ค๋ฌด์์ ์ํฐํฐ๊ฐ์ ๊ด๊ณ๊ฐ ๋ณต์กํด์ง์๋ก ์กฐ์ธ์ผ๋ก ์ธํ ์ฑ๋ฅ ์ ํ๋ฅผ ํผํ ์ ์๊ฒ ๋ฉ๋๋ค.
JPA์์ ์ฐ๊ด๊ด๊ณ์ ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ๊ฐ์ ธ์ฌ ๊ฒ์ธ๊ฐ๋ฅผ fetch(ํจ์น)๋ผ๊ณ ํ๋๋ฐ,
์ฐ๊ด๊ด๊ณ์ ์ด๋ ธํ ์ด์ ์์ฑ์ผ๋ก 'fetch'๋ชจ๋๋ฅผ ์ง์ ํฉ๋๋ค.
'์ฆ์ ๋ก๋ฉ'์ ๋ถํ์ํ ์กฐ์ธ๊น์ง ํฌํจํด์ ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง๊ธฐ ๋๋ฌธ์ '์ง์ฐ ๋ก๋ฉ'์ ์ฌ์ฉ์ ๊ถ์ฅํ๊ณ ์์ต๋๋ค.
(๋ณดํธ์ ์ผ๋ก '์ง์ฐ ๋ก๋ฉ'์ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ๊ณ , ์ํฉ์ ๋ง๊ฒ ํ์ํ ๋ฐฉ๋ฒ์ ์ฐพ๋๊ฒ์ด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค ๐)
์ง์ฐ ๋ก๋ฉ(Lazy Loading)์ด๋, ๊ฐ๋ฅํ ๊ฐ์ฒด์ ์ด๊ธฐํ๋ฅผ ์ง์ฐ์ํค๋๋ฐ ์ฌ์ฉํ๋ ํจํด์ ๋๋ค.
๊ฐ ์ฐ๊ด๊ด๊ณ์ default ์์ฑ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
@OneToMany: LAZY
@ManyToOne: EAGER
@ManyToMany: LAZY
@OneToOne: EAGER
์ง์ฐ ๋ก๋ฉ์ Board ์ํฐํฐ์ @ManyToOne ์ด๋ ธํ ์ด์ ์ ์ ์ฉ์ ํ ์ ์์ต๋๋ค.
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
@Getter
@ToString(exclude = "member")
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Board extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long bno;
@Column
private String title;
@Column
private String content;
// Lazy loading
@ManyToOne(fetch = FetchType.LAZY)
private Member member;
}
...
Board ์ํฐํฐ์ ์ง์ฐ ๋ก๋ฉ์ ์ ์ฉํ ํ SQL ์ฟผ๋ฆฌ๋ฌธ์ ํ์ธํด๋ณด๋ฉด, ์ด์ ๊ณผ๋ ๋ค๋ฅด๊ฒ Board ํ ์ด๋ธ๋ง ์กฐํ๊ฐ ๋ฉ๋๋ค.
ํ์ง๋ง ์ง์ฐ ๋ก๋ฉ์ ์ ์ฉํ ํ์๋ Board์ Member์ ์ ๊ทผํ๋ ค๊ณ ํ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋๋ฐ์,
@Transactional
@Test
void testRead1() {
/* given */
Optional<Board> result = boardRepository.findById(100L);
/* when */
Board board = result.get();
/* then */
System.out.println(board);
System.out.println(board.getMember()); // Error
}
์ ์ค๋ฅ ๋ด์ฉ์ธ proxy ~ 'no Session'์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ถ๊ฐ์ ์ธ ์ฐ๊ฒฐ์ด ํ์ํ๋ค๋ ๋ฉ์์ง ์ ๋๋ค.
์ง์ฐ ๋ก๋ฉ ๋ฐฉ์์ผ๋ก ๋ก๋ฉํ๊ธฐ ๋๋ฌธ์ board ํ ์ด๋ธ๋ง ๊ฐ์ ธ์ค๋ ๊ฒ์ ๋ฌธ์ ๊ฐ ์์ง๋ง, member ํ ์ด๋ธ์ ๊ฐ์ ธ์ฌ ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฌ์ฐ๊ฒฐ์ด ํ์ํ๋ฐ, @Transactional ์ด๋ ธํ ์ด์ ์ ํตํด ํด๊ฒฐํ ์ ์์ต๋๋ค.
@Transactional ์ด๋ ธํ ์ด์ ์ ํด๋น ๋ฉ์๋๋ฅผ ํ๋์ 'ํธ๋์ญ์ ' ์ผ๋ก ์ฒ๋ฆฌํ๋ผ๋ ์๋ฏธ์ ๋๋ค.
ํธ๋์ญ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ฉด ์์ฑ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ๋์ํ์ง๋ง, ๊ธฐ๋ณธ์ ์ผ๋ก๋ ํ์ํ ๋ ๋ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฐ๊ฒฐ์ด ์์ฑ๋๊ธฐ ๋๋ฌธ์ ์ ํ ์คํธ๋ ์ ์์ ์ผ๋ก ์คํ์ด ๋ฉ๋๋ค.
โจ๏ธ ์ฆ์ ๋ก๋ฉ(Eager Loading)๊ณผ ์ง์ฐ ๋ก๋ฉ(Lazy Loading)์ ์ฅ๋จ์
์ฆ์ ๋ก๋ฉ(Earge Loading) ์ฅ์
- ์ง์ฐ๋ ์ด๊ธฐํ์ ๊ด๋ จํด์ ์ฑ๋ฅ์ ์ธ ์ํฅ์ด ์์
์ฆ์ ๋ก๋ฉ(Earge Loading) ๋จ์
- ์ง์ฐ ๋ก๋ฉ๋ณด๋ค ๊ธด ์ด๊ธฐ์ ๋ก๋ฉ ์๊ฐ์ด ํ์ํจ
- ๋ถํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ง์ด ๋ก๋ฉํ๋ฉด ์ฑ๋ฅ์ ์ํฅ์ ์ค ์ ์์
์ง์ฐ ๋ก๋ฉ(Lazy Loading) ์ฅ์
- ๋ค๋ฅธ ์ ๊ทผ ๋ฐฉ์๋ณด๋ค ํจ์ฌ ์ ์ ์ด๊ธฐ์ ๋ก๋ฉ ์๊ฐ
- ๋ค๋ฅธ ์ ๊ทผ ๋ฐฉ์์ ๋นํด ๋ฉ๋ชจ๋ฆฌ ์๋น๋ ๊ฐ์
์ง์ฐ ๋ก๋ฉ(Lazy Loading) ๋จ์
- ์ด๊ธฐํ๊ฐ ์ง์ฐ๋๋ฉด ์ํ์ง ์๋ ์๊ฐ ์ฑ๋ฅ์ ์ํฅ์ ์ค ์ ์์
โจ๏ธ ์ฆ์ ๋ก๋ฉ(Eager Loading)๊ณผ ์ง์ฐ ๋ก๋ฉ(Lazy Loading)์ ์ฃผ์ํ ์
- ๊ฐ๊ธ์ ์ด๋ฉด ์ง์ฐ ๋ก๋ฉ(Lazy Loading)๋ง ์ฌ์ฉ(ํนํ ์ค๋ฌด์์)
- ์ฆ์ ๋ก๋ฉ(Eager Loading)์ ์ ์ฉํ๋ฉด ์์ํ์ง ๋ชปํ SQL์ด ๋ฐ์ํ ์ ์์
- ์ฆ์ ๋ก๋ฉ(Earge Loading)์ JPQL์์ N+1 ๋ฌธ์ ๋ฅผ ์ผ์ผํด
References
- http://www.yes24.com/Product/Goods/96051853
- https://www.baeldung.com/hibernate-lazy-eager-loading
- https://stackoverflow.com/questions/26601032/default-fetch-type-for-one-to-one-many-to-one-and-one-to-many-in-hibernate
- https://www.inflearn.com/course/ORM-JPA-Basic
'Spring > Spring Data JPA' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
JPQL @Query ์๋ฌ: For queries with named parameters you need to use provide names for method parameters (6) | 2021.07.06 |
---|---|
JPA ์ฐ๊ด๊ด๊ณ ์์์ฑ ์ ์ด(CASCADE) - CascadeType (3) | 2021.06.21 |
๋๊ธ