๋ค์ด๊ฐ๋ฉด์
์ต๊ทผ์ 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=?
๐ต๏ธ ์์ธ
์ฟผ๋ฆฌ ์ ํ ์ด๋ธ๋ช ์ ๋ณด๋ฉด ์กฐํ ์์๋ฅผ ์ ์ ์๋ค.
- User ํ ์ด๋ธ ์กฐํ
- BinaryContent ํ ์ด๋ธ ์กฐํ
- UserStatus ํ ์ด๋ธ ์กฐํ
- BinaryContent ํ ์ด๋ธ ์กฐํ
- UserStatus ํ ์ด๋ธ ์กฐํ
- BinaryContent ํ ์ด๋ธ ์กฐํ
- UserStatus ํ ์ด๋ธ ์กฐํ
- BinaryContent ํ ์ด๋ธ ์กฐํ
- 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 ์์ด
- 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
์ฐธ๊ณ ์๋ฃ
- JPA N+1 ๋ฌธ์ ํด๊ฒฐ ๋ฐฉ๋ฒ ๋ฐ ์ค๋ฌด ์ ์ฉ ํ - ์ฝ์ง์ค์ธ ๊ฐ๋ฐ์
- [JPA] N+1 ๋ฌธ์ ์์ธ ๋ฐ ํด๊ฒฐ๋ฐฉ๋ฒ ์์๋ณด๊ธฐ
- [JPA] N+1 ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ ์ฌ๋ฌ ์ํฉ๊ณผ ํด๊ฒฐ๋ฐฉ๋ฒ
- [JPA] JPA N+1 ๋ฌธ์ ๋ฐ ๊ทผ๋ณธ์ ์ธ ์์ธ์ ๋ํ ๊ฐ์ธ์ ์ธ ๊ณ ์ฐฐ
- [JPA] ์ฆ์๋ก๋ฉ๊ณผ ์ง์ฐ๋ก๋ฉ ์์๋ณด๊ธฐ(FetchType.EAGER, LAZY)