본문 바로가기
개념 공부/Spring & ORM

[Spring / Jpa / querydsl ] 커스텀 리포지토리 구조

by clean01 2025. 3. 23.

스프링 데이터 JPA와 querydsl

Custom Repository를 사용하기

Spring Data Jpa와 Querydsl을 활용한 custom repository(사용자 정의 리포지토리)를 사용하려면 아래와 같은 상속 구조로 구현해주면 된다.

위 사진보다 더 직관적으로 이해가 잘가는 사진이 있어서 가져왔다.

사진 출처: https://wildeveloperetrain.tistory.com/322

MemberRepositoryCustom (인터페이스)

사용자 정의 메서드를 정의한다.

package study.querydsl.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import study.querydsl.entity.Member;

import java.util.List;

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

}

MemberRepository (인터페이스)

JpaRepository와 MemberRepositoryCustom을 상속(extend)받도록 한다.

package study.querydsl.repository;

import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.data.jpa.repository.JpaRepository;
import study.querydsl.dto.MemberSearchCondition;
import study.querydsl.dto.MemberTeamDto;
import study.querydsl.entity.Member;

import java.util.List;

public interface MemberRepositoryCustom  {

    List<MemberTeamDto> search(MemberSearchCondition condition);
}

MemberRepositoryImpl

이 구현체는 이름을 반드시 [ 리포지토리 + Impl ]로 지어주어야한다.

이 구현체는 MemberRepositoryCustom을 구현(implement) 한다.

네이밍 규칙을 지켜야하는 이유는, Spring Data Jpa에 의해서 MemberRepository가 스프링 빈으로 등록될때 이 리포지토리의 구현체로서 “Impl”을 postfix로 가지는 구현체가 있는지 자동으로 스캔한다.

즉, 이 상황에서는 “MemberRepositoryImpl”이라는 이름의 클래스가 있는지 찾게 된다.

(MemberRepositoryImpl이 반드시 존재해야하는 것은 아니다.)

그리고 MemberRepository를 빈으로 등록하며, MemberRepositoryImpl을 의존성 주입한다.

package study.querydsl.repository;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import org.springframework.util.StringUtils;
import study.querydsl.dto.MemberSearchCondition;
import study.querydsl.dto.MemberTeamDto;
import study.querydsl.dto.QMemberTeamDto;

import java.util.List;

import static study.querydsl.entity.QMember.member;
import static study.querydsl.entity.QTeam.team;

// 여기는 이름을 꼭 맞춰주어야함 ~~Repository + Impl
public class MemberRepositoryImpl implements MemberRepositoryCustom {
    private final JPAQueryFactory queryFactory;

    // JPAQueryFactory가 스프링 빈으로 등록돼 있다면 그걸 사용. 아니라면 이렇게 사용해도 상관없다.
    public MemberRepositoryImpl(EntityManager em) {
        queryFactory = new JPAQueryFactory(em);
    }

    @Override
    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) { // BooleanExpression은 조합이 가능하다.
        return StringUtils.hasText(username) ? member.username.eq(username) : null;
    }

    private BooleanExpression teamNameEq(String teamName) {
        return StringUtils.hasText(teamName) ? member.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;
    }
}

즉, 위 구조를 다시 정리하자면

MemberRepository는 애플리케이션(서비스) 레이어에서 사용하는 인터페이스이고,

이에 대한 구현은 JpaRepository의 구현체와 MemberRepositoryImpl이 하게된다.

하지만 이와 다른 구조로도 커스텀 리포지토리를 구성할 수 있다.

예를 들면

  • MemberRepository (모든 메서드가 정의되어있음. impl에 있는 것 + MemberJpaRepository에 있는것)
  • MemberJpaRepository (JpaRepository를 extends하며 Spring Data Jpa의 메서드들이 들어감. ex. findByName)
  • MemberRepositoryImpl (MemberRepository를 구현함. 내부에서 MemberJpaRepository를 빈으로 주입받아, 메서드를 구현)

Reference

인프런 강의 <실전! Querydsl> (김영한)

https://wildeveloperetrain.tistory.com/322

https://yeon-kr.tistory.com/229