Spring Framwork & JPA/JPA

프로젝트 JPA 성능 개선기 (1) - 로그인 시 성능을 올려보자~!

DEV_GOLF 2022. 6. 4. 14:21
반응형

OneToOne은 기본적으로 Lazy 로딩을 지원하지 않는다. 그렇기 때문에 조회 시 Lazy로 설정 시 다음과 같은 문제가 발생한다. 

 

일단 사전에 코드를 보여주면 다음과 같다.

 

Member.java 

public class Member extends BaseTimeEntity {
    // 속성 ... //

    @OneToOne(mappedBy = "member", cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, orphanRemoval = true)
    private MemberCount memberCount;

    // == 연관관계 로직 == //
    public void addMemberCount(final MemberCount memberCount) {
        this.memberCount = memberCount;
    }
    
    // 비즈니스 로직 ... //
}

 

MemberCount.java

public class MemberCount extends BaseTimeEntity {
    // 속성 ... //

    @OneToOne(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    @JoinColumn(name = "member_id", unique = true, nullable = false)
    private Member member;
 
    // 비즈니스 로직 ... //   
}

 

AuthService.java

@Transactional
public TokenDTO login(final Email email, final Password password) {
    final String userPw = password.password();
    
    CustomUserDetails userDetails = membeRepository.findByEmail(email)
    	    .map(CustomUserDetails::of)
            .orElseThrow(() -> new MemberNotFoundException(ErrorCode.USER_NOT_FOUND));
    
    UsernamePasswordAuthenticationToken token
            = new UsernamePasswordAuthenticationToken(userDetails, userPw);
    
    Authentication authenticate = managerBuilder.getObject().authenticate(token);
    SecurityContextHolder.getContext().setAuthentication(authenticate);
    
    return tokenProvider.createToken(userDetails.getId(), authenticate);
}

보면 로그인 시 시큐리티 컨텍스트에 userDetails 정보를 넣어주기 때문에  findByEmail로 회원을 조회해오고 있다. 이 때 Login을 실행하고 쿼리를 보면 결과가 다음과 같이 나온다.

정말 보기 불편한 상황이다. MemberCount를 Join을 해서 가져올 수 도 있는 상황인데 굳이 select쿼리가 두 번이나 나가 성능을 떨어트리고 있다. 이를 해결하기 위해 필자는 @EntityGraph를 사용했다. @EntityGraph는 서로 연관된 엔티티를 JPA가 불러올 때 어떻게 불러올 것인지를 비교적 유연하게 (fetch = Lazy or EAGER 선언 시 정적이므로 런타임 중에는 변경 하지 못한다.) 표현하기 위해 사용한다.

다음과 같이 선언해주면 이제 JPA는 반드시 Member가 조회되면 memberCount도 조회해오기 때문에 N + 1문제가 발생하지 않을 것이다. 실제로 결과를 보자.

Join을 해서 memberCount를 한번에 가져온다. 그렇기 때문에 한번 더 select쿼리가 나갈 이유가 없어져 N + 1 문제가 해결 되었다. 

 

하지만 여기서도 문제가 역시 발생한다. 각자 생각해보자 로그인을 할 때 과연 memberCount가 필요할 까? 전혀 필요하지 않다고 생각하기 때문에 이것 역시 불필요한 join문이다. 2부에서 이어가보자