Server/Spring

Spring - JpaRepository와 Querydsl 연결하기! (사용자 정의 리포지토리)

JaeHoney 2022. 6. 18. 22:43

우리는 자바 스프링으로 웹 서버를 개발할 때 Spring Data JPA와 QueryDSL을 동시에 사용하게 된다. 문제는 JpaRepository는 인터페이스이고, QuerydslRepository는 클래스이다.

 

그래서 흔히 아래의 두 클래스를 따로 구현하곤 한다.

  • UserJpaRepository
  • UserQuerydslRepository

이를 해결하기 위한 방법을 알아보자.

사용자 정의 리포지토리

사용자 정의 리포지토리를 사용하면 이런 문제를 해결할 수 있다.

 

사용자 정의 리포지토리는 다음과 같이 사용한다.

  • 사용자 정의 인터페이스 생성 (상속 X)
  • 사용자 정의 인터페이스 구현
  • JpaRepository에 사용자 정의 인터페이스 상속

Class Diagram으로 아래와 같이 표현할 수 있다.

구현

먼저 사용자 정의 인터페이스를 작성해보자. search 메서드는 동적 쿼리를 사용한 조회 메서드이다. (Querydsl Repository에서 해당 인터페이스를 구현한다.)

public interface MemberRepositoryCustom {
    List<MemberTeamDto> search(MemberSearchCondition condition);
}

다음은 구현체를 만들자. Querydsl로 조회할 메서드를 구현한다.

public class MemberRepositoryImpl implements MemberRepositoryCustom {

    private final JPAQueryFactory queryFactory;

    public List<MemberTeamDto> search(MemberSearchCondition condition) {
        return queryFactory
                .select(new QMemberTeamDto(
                        member.id.as("memberId"),
                        member.username,
                        member.age,
                        team.id.as("teamId"),
                        team.name.as("teamName")))
                .from(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                )
                .fetch();
    }

    private BooleanExpression usernameEq(String username) {
        return hasText(username) ? member.username.eq(username) : null;
    }

    private BooleanExpression teamNameEq(String teamName) {
        return hasText(teamName) ? team.name.eq(teamName) : null;
    }

    private BooleanExpression ageGoe(Integer ageGoe) {
        return ageGoe != null ? member.age.goe(ageGoe) : null;
    }

    private BooleanExpression ageLoe(Integer ageLoe) {
        return ageLoe != null ? member.age.loe(ageLoe) : null;
    }

    public MemberRepositoryImpl(EntityManager em) {
        this.queryFactory = new JPAQueryFactory(em);
    }

}

이후에는 기존의 JpaRepository에서 커스텀 리포지토리도 추가로 상속받으면 된다.

public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
    List<Member> findByUsername(String username);
}

결과적으로 MemberRepository는 JpaRepository와 QuerydslRepository의 메서드를 전부 가질 수 있게 된다. 커스텀 리포지토리에서는 MemberRepository가 가진 메서드 중 Querydsl을 사용할 메서드만 추상화해서 가지면 된다.

 

즉, Spring Data JPA와 QueryDSL을 동시에 사용하면서도 DIP 원칙을 지킬 수 있다.

 

Repository 구현을 위해 4개의 클래스/인터페이스를 생성해야 한다. 굳이 이런 반복이 필요없다고 느껴지거나, Repository가 특정 상황에 대한 내용이라면 아래의 포스팅에서 상속을 하지 않고 Repository를 구현하는 방법도 다뤘으니 살펴보길 바란다.

- https://jaehoney.tistory.com/211

 

QueryDSL - Repository 구조잡기 (extends / implements 사용 X)

해당 포스팅은 "[우아콘2020] 수십억건에서 QUERYDSL 사용하기" 라는 영상의 내용을 정리한 글입니다. 해당 영상은 아래 Reference에서 시청할 수 있습니다. - https://www.youtube.com/watch?v=zMAX7g6rO_Y exten..

jaehoney.tistory.com


Reference