영속성 전이를 통한 Insert 시
다대일 단방향
memberDetail(Many) -> member(One)
의도대로 member, memberdetail1, memberdetail2 -> insert 3번
일대다 단방향
MemberDetail(One) -> member(Many)
member, memberdetail1, memberdetail2 -> insert 3번 후
-> member update 2회
객체 그래프 탐색 측면에서는 단방향이 양방향보다 좋을 수 있으나
영속성 전이를 통해 insert 수행을 할 때는 그렇지 않다.
@OneToOne, @ManyToOne => 기본 FetchType.EAGER
@OneToMany, @ManyToMany => 기본 FetchType.Lazy
Fetch 전략이 LAZY 로 설정했더라도 연관 Entity를 참조하면 그 순간 추가적인
쿼리가 발생한다.
findAll() 에서도 단일 레코드 조회가 아닌 경우 ( JPQL 을 수행하는 경우)
해당 JPQL 을 먼저 수행 (Entity 에 설정된 Fetch 전략 적용 안 됨)
반환된 레코드 하나 하나에 대해 Entity에 설정된 Fetch 전략을 적용해서 연관 Entity 가져옴
때문에 findAll() 메서드 호출도 역시 N + 1 문제 발생 가능
Pagination 쿼리에 Fetch JOIN 을 적용하면 실제로는 모든 레코드를 가져오는 쿼리가 실행된다.
다 긁어오고 메모리에서 limit 를 계산함.
@Entity
public class Member {
@OneToMany
private List<MemberDetail> details;
}
@Entity
public class MemberDetail {
@EmbeddedId
private Pk pk;
@Embeddable
public static class PK {
private String type;
}
}
public interface MemberRepository extends JpaRepository<Member, Long> {
// select * from Member m
// inner join MemberDetail md
// on m.member_id = md.member_id
// where md.type = {type}
List<Member> findByDetails_Pk_Type(String Type);
Page
Slice
Class 기반 ( DTO ) Projection
@Value
public class MemberDto {
private final String name;
private final LocalDateTime createDate;
}
public interface Member Repository extends JpaRepository<Member, Long> {
Collection<MemberDto> findByName(String name);
}
Interface 기반 Projection
@Entity
@Table(name = "Members")
public class Member {
private String name;
}
//projection interface
public interface MemberNameOnly {
String getName();
}
public interface MemberRepository extends JpaRepository<Member, Long> {
Collection<NMemberNameOnly> findByCreateDateAfter(LocalDateTime createDate);
}
Interface 기반 Projection
@Entity
public class Member {
private String name;
@OneToMany
private List<MemberDetail> details;
}
@Entty
public class MemberDetial {
@EmbeddedId
private Pk pk;
private String description;
@Embeddable
public static class Pk {
private String type;
}
}
public interface MemberDto {
String getName();
List<MemberDetailDto> getDetails();
interface MemberDetailDto {
@Value("#{target.pk.type}") // MemberDetail Entity 가 target 에 매핑
String getType();
String getDescription();
}
}
Dynamic Projection
public interface MemberRepository extends JpaRepository<Member, Long> {
<T> Collection<T> findByCreateDateAfter(LocalDateTime createDate, Class<T> type);
}
Collection<MemberNameOnly> nameOnlies
= memberRepository.findByCreateDateAfter(LocalDateTime.now(), MemberNameOnly.class);
Collection<MemberDto> memberDtos
= memberRepository.findByCreateDateAfter(LocalDateTime.now(), MemberDto.class);
인자로 넘기는 class 타입대로 projection 발생하여 반환
연관관계 매핑
Spring Data JPA Repository