Spring/[인프런 김영한 실전 Querydsl]

[인프런 김영한 실전 Querydsl] 실무 활용 - 스프링 데이터 JPA와 Querydsl

h2boom 2024. 9. 26. 15:59

스프링 데이터 JPA와 Querydsl

사용자 정의 레포지토리

  • 사용자 정의 레포지토리 : 스프링 데이터 JPA가 제공하는 인터페이스 메소드를 직접 구현하는 방법
    • 주로 Querydsl을 사용해서 동적 쿼리를 작성할 때 사용자 정의 레포지토리를 사용한다.

 

  • 사용자 정의 레포지토리 사용법
    1. 사용자 정의 인터페이스 작성
    2. 사용자 정의 인터페이스 구현
      1. 사용자 정의 인터페이스 구현 클래스의 이름은 스프링 데이터 레포지토리명 + Impl로 반드시 적어줘야한다.
    3. 스프링 데이터 레포지토리에 사용자 정의 인터페이스 상속

 

//사용자 정의 인터페이스
public interface MemberRepositoryCustom {
    List<MemberTeamDto> search(MemberSearchCondition memberSearchCondition);
}

//사용자 정의 인터페이스 구현 클래스
public class MemberRepositoryImpl implements MemberRepositoryCustom {
    private final JPAQueryFactory queryFactory;

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

    @Override
    public List<MemberTeamDto> search(MemberSearchCondition condition) {
        return queryFactory
                .select(new QMemberTeamDto(
                        member.id,
                        member.username,
                        member.age,
                        team.id,
                        team.name))
                .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;
    }
}

//Spring JPA Repository
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
    //select m from Member m where m.username = ?
    List<Member> findByUsername(String username);
}
  • 사용자 정의 인터페이스를 작성 후 구현 클래스를 통해 인터페이스 메소드를 구현하고 스프링 Data 레포지토리에서 사용자 정의 인터페이스를 상속받도록 하면 된다.
    • 사용자 정의 인터페이스 구현 클래스명은 Spring Data Repository명 + Impl로 작성한다.
      => 여기서는 MemberRepository+Impl이 구현 클래스명
    • 인터페이스끼리의 상속은 다중 상속이 가능하므로 Spring Data Repository를 만들기 위한 JpaRepository와 사용자 정의 인터페이스를 상속한다.

 

@Repository
public class MemberQueryRepository {
    private final JPAQueryFactory queryFactory;

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

    public List<MemberTeamDto> search(MemberSearchCondition condition) {
        return queryFactory
                .select(new QMemberTeamDto(
                        member.id,
                        member.username,
                        member.age,
                        team.id,
                        team.name))
                .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;
    }
}
  • 특정 API나 화면에 특화된 기능인 경우 별도로 사용자 정의 레포지토리를 만들지 않고 별도의 Repository를 만들어서 주입받아서 사용해도 된다.

 

  • Spring Data Repository에는 핵심 비지니스 로직이나 재사용성이 있는 것들을 넣고 특정 API, 화면에 종속적인 것들은 별도의 Repository를 만들어서 사용하는 것도 좋다.

스프링 데이터 페이징 활용

//사용자 정의 인터페이스
public interface MemberRepositoryCustom {
    Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable);
}

//사용자 정의 인터페이스 구현 클래스
public class MemberRepositoryImpl implements MemberRepositoryCustom {
    private final JPAQueryFactory queryFactory;

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

    @Override
    public Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable) {
        QueryResults<MemberTeamDto> results = queryFactory
                .select(new QMemberTeamDto(
                        member.id,
                        member.username,
                        member.age,
                        team.id,
                        team.name))
                .from(member)
                .leftJoin(member.team, team)
                .where(
                        usernameEq(condition.getUsername()),
                        teamNameEq(condition.getTeamName()),
                        ageGoe(condition.getAgeGoe()),
                        ageLoe(condition.getAgeLoe())
                )
                .offset(pageable.getOffset())
                .limit(pageable.getPageSize())
                .fetchResults();

        List<MemberTeamDto> content = results.getResults();
        long total = results.getTotal();

        return new PageImpl<>(content, pageable, total);
    }
}
  • 스프링 데이터의 페이징을 활용하기 위해서 Page 인터페이스와 구현체 PageImpl 클래스를 사용한다.
    • fetchResults()로 결과를 가져오는 경우 content와 total count를 모두 가져올 수 있다.
    • getResults()를 사용하면 content를, getTotal()을 사용하면 total count를 가져온다.

 

  • count 쿼리가 생략 가능한 경우
    • 페이지 시작이면서 content 사이즈가 페이지 사이즈보다 작을 때
      = 페이지 시작일 때 실제 가져온 데이터 사이즈가 limit보다 작을 때
    • 마지막 페이지일 때
      = offset + content 사이즈 => 전체 사이즈

 

  • 조인이 없는 단순한 엔티티의 경우 스프링 데이터 페이징이 제공하는 Sort를 Querydsl의 OrderSpecifier로 변환해서 사용
    • 복잡해지는 경우(동적 정렬 기능 등...) 스프링 데이터 페이징이 제공하는 Sort를 사용하기 보다 파라미터를 받아서 직접 처리하는 것이 좋다.

출처 : [인프런 김영한 실전 Querydsl]

https://www.inflearn.com/course/querydsl-%EC%8B%A4%EC%A0%84/dashboard

 

실전! Querydsl 강의 | 김영한 - 인프런

김영한 | Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, 복잡한 쿼리, 동적 쿼리는 이제 안녕! Querydsl로 자바 백엔드 기술을 단단하게. 🚩 본 강의는 로드맵 과정입니다. 본 강의는 자

www.inflearn.com