우리는 자바 스프링으로 웹 서버를 개발할 때 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
Reference
'Server > Spring' 카테고리의 다른 글
Spring - REST API에서 직접 정의한 Error code를 사용하는 이유! (0) | 2022.07.03 |
---|---|
Spring - REST에서 예외를 처리하는 다양한 방법! (0) | 2022.07.03 |
SpringBoot - 프로퍼티를 안전하게 가져오는 방법! (0) | 2022.06.14 |
SpringBoot - @SpringBootTest vs @WebMvcTest (0) | 2022.05.30 |
QueryDSL - Repository 구조잡기 (extends / implements 사용 X) (0) | 2022.05.29 |