Spring/[인프런 김영한 실전 Querydsl]
[인프런 김영한 실전 Querydsl] 실무 활용 - 스프링 데이터 JPA와 Querydsl
h2boom
2024. 9. 26. 15:59
스프링 데이터 JPA와 Querydsl
사용자 정의 레포지토리
- 사용자 정의 레포지토리 : 스프링 데이터 JPA가 제공하는 인터페이스 메소드를 직접 구현하는 방법
- 주로 Querydsl을 사용해서 동적 쿼리를 작성할 때 사용자 정의 레포지토리를 사용한다.
- 사용자 정의 레포지토리 사용법
- 사용자 정의 인터페이스 작성
- 사용자 정의 인터페이스 구현
- 사용자 정의 인터페이스 구현 클래스의 이름은 스프링 데이터 레포지토리명 + Impl로 반드시 적어줘야한다.
- 스프링 데이터 레포지토리에 사용자 정의 인터페이스 상속
//사용자 정의 인터페이스
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와 사용자 정의 인터페이스를 상속한다.
- 사용자 정의 인터페이스 구현 클래스명은 Spring Data Repository명 + Impl로 작성한다.
@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 사이즈 => 전체 사이즈
- 페이지 시작이면서 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