Spring Data JPA를 사용할 때, 기본으로 제공해주는 @Query로는 다양한 조회 기능을 사용하기에 한계가 있다.
그래서 이 문제를 해결하기 위해 정적 타입을 지원하는 조회 프레임워크를 사용하는데, 자주 사용되는 것이 Querydsl이다.
기본적으로, Querydsl을 사용하기 위해서는 Q클래스들이 생성되어 있어야 한다. 개발환경과 Gradle 설정과 같은 내용은 아래 참조 링크에서 확인할 수 있다. 필자는 Maven 방식으로 진행했다.
먼저 Entity하나를 생성한다.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Academy {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String address;
@Builder
public Academy(String name, String address) {
this.name = name;
this.address = address;
}
}
그리고 테스트로 데이터를 넣고, 검증할 Repository도 하나 생성한다.
public interface AcademyRepository extends JpaRepository<Academy, Long> {
}
그럼 이제 Querydsl Repository를 하나 생성한다.
클래스명은 AcademyRepositorySupport이다.
@Repository
public class AcademyRepositorySupport extends QuerydslRepositorySupport {
private final JPAQueryFactory queryFactory;
public AcademyRepositorySupport(JPAQueryFactory queryFactory) {
super(Academy.class);
this.queryFactory = queryFactory;
}
public List<Academy> findByName(String name) {
return queryFactory
.selectFrom(academy)
.where(academy.name.eq(name))
.fetch();
}
}
이제 테스트 코드를 통해 검증해보자.
@RunWith(SpringRunner.class)
@SpringBootTest
public class BasicTest {
@Autowired
private AcademyRepository academyRepository;
@Autowired
private AcademyRepositorySupport academyRepositorySupport;
@After
public void tearDown() throws Exception {
academyRepository.deleteAllInBatch();
}
@Test
public void querydsl_기본_기능_확인() {
//given
String name = "hsayho";
String address = "hsayho@hsayho.com";
academyRepository.save(new Academy(name, address));
//when
List<Academy> result = academyRepositorySupport.findByName(name);
//then
assertThat(result.size(), is(1));
assertThat(result.get(0).getAddress(), is(address));
}
}
위와 같은 방식에는 한 가지 단점이 있다.
항상 2개의 Repository를 의존성으로 받아야 한다는 것이다.
그 이유는 Querydsl의 Custom Repository와 JpaRepository를 상속한 Repository가 기능을 나눠가졌기 때문이다.
이를 해결하기 위해 Spring Data JPA에서는 Custom Repository를 JpaRepository 상속 클래스에서 사용할 수 있도록 기능을 지원한다.
전체적인 그림은 아래와 같다.

Spring Data JPA - Reference Documentation를 참고하면 Custom Repository에 대해 더 자세한 내용이 나오니 참고할 것!
Custom이 붙은 인터페이스를 상속한 Impl 클래스의 코드는 Custom 인터페이스를 상속한 JpaRepository에서 사용할 수 있다.
먼저 기존 Repository와 같은 위치에 Custom인터페이스와 Impl클래스를 생성한다.

그리고 WikiRepositoryCustom 인터페이스와 WikiRepositoryImpl 클래스에 다음과 같은 코드를 추가한다.
public interface WikiRepositoryCustom{
List<String> findByName(String name);
}
@RequiredArgsConstructor
public class WikiRepositoryImpl implements WikiRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public List<Wiki> findByName(String name) {
return queryFactory.selectFrom(wiki)
.where(wiki.name.eq(name))
.fetch();
}
}
여기서 페이징이 필요할 경우 QuerydslSupport 상속 코드를 추가하면 된다.
그럼 이제 이 코드를 WikiRepository에서 사용할 수 있게 상속 구조로 변경해보겠다.
public interface WikiRepository extends JpaRepository<Wiki, Long>, WikiRepositoryCustom {
}
그럼 정상적으로 작동하는지 테스트 코드를 작성해보자
@RunWith(SpringRunner.class)
@SpringBootTest
public class CustomTest {
@Autowired
private WikiRepository wikiRepository;
@After
public void tearDown() throws Exception {
wikiRepository.deleteAllInBatch();
}
@Test
public void querydsl_Custom설정_기능_확인() {
//given
String name = "hsayho";
String address = "hsayho@hsayho.com";
wikiRepository.save(new Wiki(name, address));
//when
List<Wiki> result = wikiRepository.findByName(name);
//then
assertThat(result.size(), is(1));
assertThat(result.get(0).getAddress(), is(address));
}
}
이 외에도 따로 상속/구현 없이 따로 Repository를 생성하여 JPAQueryFactory를 사용해 Querydsl을 사용하는 방법이 있다.
1번 방법을 사용할 경우, DataJpaTest로 작성할 경우에 queryFactory 및 상속용 클래스들이 빈으로 등록되지 않는 문제가 발생한다.
이럴 경우에는
@TestConfiguration
static class TestConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
위와 같은 TestConfig 파일을 test 전용 configuration 폴더에 옮긴 후에, @DataJpaTest를 사용하는 곳에서 해당 클래스를 Import하는 방식으로 jpaQueryFactory의 의존성 주입을 해결할 수 있다.