반응형
QueryDSL이란?
- 자바를 사용하는 개발자들이 SQL문을 작성하지 않고도 데이터베이스 쿼리를 생성할 수 있도록 도와주는 오픈소스 라이브러리입니다.
왜 사용할까?
- 객체지향적인 방식으로 쿼리를 작성할 수 있게 해주기 때문에, 코드의 가독성이 높아지고 유지보수성이 좋아집니다. 또한, 동적 쿼리를 작성할 때 유용하게 사용될 수 있습니다.
- 다양한 데이터베이스 시스템과 연동할 수 있으며, JPA, Hibernate, MyBatis 등 다양한 ORM 프레임워크와도 연동이 가능합니다.
- SQL문을 직접 작성하는 것보다 쿼리 작성에 필요한 시간과 노력을 줄여줄 뿐 아니라, 자바 코드를 통해 안전하고 쉽게 쿼리를 작성할 수 있게 해줍니다.
- 코드로 쿼리를 작성함으로써, 컴파일 시점에 문법 오류를 쉽게 확인할 수 있습니다.
예시
- Spring JPA를 사용할경우
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByAgeGreaterThanEqual(int age);
}
- NativeQuery를 사용할경우
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query(value = "SELECT * FROM member WHERE age >= ?1", nativeQuery = true)
List<Member> findByAgeGreaterThanEqualNative(int age);
}
- JPQL을 사용할경우
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("SELECT m FROM Member m WHERE m.age >= :age")
List<Member> findByAgeGreaterThanEqualJPQL(@Param("age") int age);
}
- QueryDSL을 사용할경우
@Repository
public class MemberCustomRepositoryImpl extends QuerydslRepositorySupport implements MemberCustomRepository {
public MemberCustomRepositoryImpl() {
super(Member.class);
}
@Override
public List<Member> findByAgeGreaterThanEqual(int age) {
QMember member = QMember.member;
return from(member).where(member.age.goe(age)).fetch();
}
}
QueryDSL 사용법
Gradle설정
dependencies {
//QueryDSL을 사용하여 JPA 엔티티를 쉽게 조회하고 조작할 수 있습니다.
implementation 'com.querydsl:querydsl-jpa'
//Annotation Processor를 사용하면 컴파일 시점에 QueryDSL이 생성하는 Q객체를 생성할 수 있습니다.
implementation 'com.querydsl:querydsl-apt'
//Annotation Processor를 사용하여 Q객체를 생성하기 위해, 컴파일 시점에 JPA 엔티티 클래스를 스캔하고 Q객체를 생성합니다.
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa" // querydsl JPAAnnotationProcessor 사용 지정
//JPA 엔티티를 정의할 때 사용되는 어노테이션들이 포함되어 있습니다.
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
//어노테이션을 사용하여 QueryDSL Annotation Processor를 구현할 때 필요합니다.
annotationProcessor("jakarta.annotation:jakarta.annotation-api")
}
//Annotation Processor가 생성한 Q객체를 저장할 디렉토리 경로를 설정합니다.
def generated='src/main/generated'
//Java 소스 파일의 위치를 지정합니다.
sourceSets {
//Java 소스 파일의 위치에 Annotation Processor가 생성한 Q객체를 저장하는 디렉토리를 추가합니다.
main.java.srcDirs += [ generated ]
}
//Java 소스 코드 컴파일 태스크에 대한 설정을 지정합니다.
tasks.withType(JavaCompile) {
//options.annotationProcessorGeneratedSourcesDirectory 옵션에 Annotation Processor가 생성한 Q객체를 저장하는 디렉토리를 지정합니다.
options.annotationProcessorGeneratedSourcesDirectory = file(generated)
}
clean.doLast {
//Annotation Processor가 생성한 Q객체를 저장하는 디렉토리를 삭제합니다.
file(generated).deleteDir()
}
QueryDsl Config 설정
@Configuration
public class QuerydslConfiguration {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
Q클래스 생성
Gradle 옵션 - Tasks - other - compileJava를 실행시키면 Q클래스 파일이 생성된다.
생성 | 생성된파일 |
---|---|
![]() |
![]() |
Repository
- MemberRepository
public interface MemberRepository extends JpaRepository<Member,Long> {
List<Member> findByAge(int age);
}
- MemberRepositoryCustom
public interface MemberRepositoryCustom {
List<Member> findByAgeQueryDsl(int age);
}
- MemberRepositoryImpl
@Repository
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom{
private final JPAQueryFactory jpaQueryFactory;
@Override
public List<Member> findByAgeQueryDsl(int age) {
QMember member = QMember.member;
return jpaQueryFactory.selectFrom(member)
.where(member.age.eq(age))
.fetch();
}
}
문제점
하지만 위처럼 적용할경우 사용을할떄 두가지의 상속을 받아야 한다
@Autowired
private MemberRepository memberRepository;
@Autowired
private MemberRepositoryCustom memberRepositoryCustom;
이 문제를 해결하기위해서 MemberRepository에 memberRepositoryCustom을 상속받자
그럼 MemberRepository만 의존성 주입을해도 모두 사용 할 수 있다!
- MemberRepository
public interface MemberRepository extends JpaRepository<Member,Long> ,MemberRepositoryCustom{
List<Member> findByAge(int age);
}
QueryDSL 동적 쿼리
아래의 코드에선 동적으로 쿼리를 생성하는 예시를 보여준다.
만약 name
값이 Null
일 경우는 쿼리 조건에 포함되지않는다
public List<Member> searchMembers(String name, String email, LocalDate startDate, LocalDate endDate) {
QMember member = QMember.member;
BooleanBuilder builder = new BooleanBuilder();
if(StringUtils.hasText(name)) {
builder.and(member.name.contains(name));
}
if(StringUtils.hasText(email)) {
builder.and(member.email.contains(email));
}
if(startDate != null) {
builder.and(member.createdDate.goe(startDate.atStartOfDay()));
}
if(endDate != null) {
builder.and(member.createdDate.lt(endDate.plusDays(1).atStartOfDay()));
}
return queryFactory.selectFrom(member).where(builder).fetch();
}
테스트
테스트 코드를 작성하여 잘 실행이 되는지 확인해보자
아래 코드처럼 진행하면 Spring JPA로 찾은 findByAge
와 findByAgeQueryDsl
의 결과가 같아
문제없이 assertThat이 통과되는 모습을 확인 할 수 있다.
@SpringBootTest
class MemberRepositoryTest {
@Autowired
private MemberRepository memberRepository;
@Test
@Transactional
public void test(){
memberRepository.save(new Member("홍길동",10));
memberRepository.save(new Member("김길동",10));
memberRepository.save(new Member("오길동",10));
memberRepository.save(new Member("홍길동",11));
List<Member> members = memberRepository.findByAge(10);
List<Member> members2 = memberRepository.findByAgeQueryDsl(10);
assertThat(members).isEqualTo(members2);
}
}
참고
반응형
'공부 > JAVA SPRING' 카테고리의 다른 글
[Java] Mybatis 동시성 제어 (0) | 2023.09.27 |
---|---|
코드 커버리지 적용(Jacoco) (0) | 2023.04.07 |
[SpringBoot] Kakao REST API 검색어로 위도 경도 좌표 받기 (0) | 2023.03.13 |
[SpringBoot] TMDB에서 API 파싱(RestTemplate, WebClient차이) (0) | 2023.03.03 |
[SpringBoot] Swagger 적용하기 (0) | 2023.02.20 |
댓글