[SpringBoot]QueryDsl 도입기

유형찬·2022년 9월 3일
0

GDSC_BLOG_PROJECT

목록 보기
4/5

QueryDsl

  • Query Domain Specific Language
    • 약자로 도메인 + 특화 + 언어 라는 개념
    • 데이터를 다룰 때 객체 단위로 ( Entity ) 다루게 되는데 데이터 베이스 Table 에 종속 되지 않고 객체에 특화 된 쿼리 언어라는 것

What is Querydsl?

  • 쿼리를 자바코드로 작성할 수 있게 도와주는 기술이다.
  • Spring Data JPA로 해결하지 못하는 복잡한 쿼리/동적 쿼리를 해결할 수 있다.
  • 자바 코드로 작성하기 때문에 문법오류를 컴파일 시점에 잡아낼 수 있다.
    • JPQL 이라는 것으로도 복잡한 쿼리 동적 쿼리 등을 만들어 내 사용 할 수 있지만
    • 사전에 기초 SQL Exeption 등의 오류를 잡지 못하는 등 IDE 의 도움을 받을 수 없었다.

Why introduce querydsl

GDSC MSA 中

Auth Server 에서 SlackInfo 추가 , MemberInfo 정보 등이 서비스를 확장 시켜나감에 따라 고민이 많았다.

  • 서비스가 확장 되며 현재 SlackInfo , MemberDetailInfo 등의 멤버 정보 테이블을 추가해 나갔다.
  • 기본 MemberTable 스키마를 변경 및 추가하여 서비스 되는 Mysql DB , JPA Entity 변경을 하지 않기 위해 외부 조인 등을 생각했다.

QueryDsl 을 사용 하기 전

@Transactional(readOnly = true)
    public Member getUserId(String userId) {
        Member member = memberRepository.findByUserId(userId);
        SlackMemberInfo slackMemberInfo = slackMemberService.getSlackMemberByUserId(member);
        if (slackMemberInfo != null) {

            // deep copy
            return Member.builder()
                    .username(member.getUsername())
                    .memberInfo(member.getMemberInfo())
                    .userId(member.getUserId())
                    .password(member.getPassword())
                    .role(member.getRole())
                    .memberInfo(member.getMemberInfo())
                    .email(member.getEmail())
                    .emailVerifiedYn(member.getEmailVerifiedYn())
                    .modifiedAt(member.getModifiedAt())
                    .profileImageUrl(slackMemberInfo.getProfileImage512())
                    .providerType(member.getProviderType())
                    .build();
        }
        return member;
    }
  • Service 계층에서 상당히 비 효율 적인 것을 알 수 있다.
    • Service 계층의 로직이 간단하지만 더러워 보이는 문제가 있다.
  • 정보 하나를 얻기 위해서 Select Query 문을 2개를 날린다. 이게 과연 효율적 인지? 항상 고민 해온 문제 이다.
  • 데이터 베이스와 Spring Web App 간 Network 통신 시간이 더 길어지는 단점도 있을 것 이다.

이러한 문제를 해결 하기 위해서 간단하게 Query DSL 을 도입 했다.

QueryDsl 을 사용 하기 후

// MemberService 
@Transactional(readOnly = true)
    public Member getUserId(String userId) {
        return memberRepository.findByUserId(userId);
    }
@Repository
public class MemberRepositoryImpl implements CustomMemberRepository {

    private final EntityManager em;

    @Autowired
    public MemberRepositoryImpl(EntityManager em) {
        this.em = em;
    }

    // 이 아래 작성
    public Member findByUserId(String id) {
        // querydsl 사용
        JPAQueryFactory query = new JPAQueryFactory(em);

        QMember m = new QMember("m");
        QSlackMemberInfo s = new QSlackMemberInfo("s");
        QMemberInfo i = new QMemberInfo("i");
        Tuple t = query.select(m, s, i)
                .from(m)
                .leftJoin(i)
                .on(m.eq(i.member))
                .leftJoin(s)
                .on(m.eq(s.userId)).where(m.userId.eq(id)).fetchOne();

        if (Objects.isNull(t)) {
            throw new IllegalArgumentException("해당 유저가 존재하지 않습니다.");
        }
        Member returnMember =
                Member.builder()
                .userId(t.get(m).getUserId())
                .email(t.get(m).getEmail())
                .memberInfo(t.get(i))
                .emailVerifiedYn(t.get(m).getEmailVerifiedYn())
                .modifiedAt(t.get(m).getModifiedAt())
                .profileImageUrl(t.get(m).getProfileImageUrl())
                .providerType(t.get(m).getProviderType())
                .role(t.get(m).getRole())
                .uploadDate(t.get(m).getUploadDate())
                .username(t.get(m).getUsername())
                .build();
        if(t.get(s) != null) {
            returnMember.setProfileImageUrl(Objects.requireNonNull(t.get(s)).getProfileImage512());
        }
        return returnMember;

    }
}
  • Join 문 , Custom Data Set을 만드는 로직을 Repository 계층으로 넘겨 서비스 계층의 로직이 깔끔해진 것을 볼 수 있다.
  • 외부 조인이 가능해짐에 따라 JPA 관계에 큰 영향을 받지 않도록 테이블 구성을 자유롭게 할 수 있었다.
  • Tuple Object 는 DTO 를 만들지 않더라도 Query문으로 불러온 속성들을 QOject를 통해서 접근 할 수 있다.
    - QOject는 QueryDsl 이 Domain Object 에 접근 하기 위해서 컴파일 시 만들어지는 객체 이다.
    // 수정 추가
  • 기존 JPA Entity와 @ManyToOne , @OneToOne 등 관계를 맺고 있다면 위와 같이 3중 조인을 하지 않아도 된다. Member(memberInfo , ) 의 @OneToOne 이라면 Member 만 select 해도 된다. FetchType.Eager 라면 자동적으로 Join 쿼리를 날려주게 된다.
  • Select 문이 2개에서 1개로 감소 했다!

설치

// querydsl
        implementation 'com.querydsl:querydsl-core'
        implementation 'com.querydsl:querydsl-jpa'
        annotationProcessor 'com.querydsl:querydsl-apt::jpa'
        annotationProcessor 'javax.persistence:javax.persistence-api'
        annotationProcessor 'javax.annotation:javax.annotation-api'

gradle 종속성으로 다음을 추가 하면 된다!

profile
rocoli에요

0개의 댓글