Criteria의 단점 너무 복잡하고 어렵다는 것 그래서 JPQL이 어떻게 생성되는지 파악이 어렵다.
그래서 나온게 이 QueryDSL이다.. 코드로 작성하는데 간결하고 알아보기 쉽다.
QueryDSL은 오픈소스 프로젝트이다. 단순 CRUD보다는 이름에 걸맞게 데이터를 조회 그러니까 통계형 쿼리를 짤때 적합하지 않을까 생각한다.
build.gradle
buildscript {
ext {
...
querydslVersion = '1.0.10'
}
dependencies {
....
classpath "gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:$querydslVersion"
}
}
subprojects {
apply plugin: 'com.ewerk.gradle.plugins.querydsl'
ext {
querydslDir = "$buildDir/generated/querydsl"
}
dependencies {
...
implementation 'com.querydsl:querydsl-jpa'
}
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main.java.srcDir querydslDir
}
configurations {
querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
}
이렇게 설정을 해주었는데 중요한 부분은 subproject.ext.querydslDir 부분이다.
buildDir은 모듈의 빌드된 폴더 build/를 의미하며 build/generated/querydsl` 폴더에 Entity클래스 앞에 Q가 붙은 클래스가 빌드되어 있다.
이것으로 QueryDSL을 세팅해주는 것이다.
QuerydslConfig.java
@Configuration
public class QuerydslConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
전에 말했듯 JavaEE환경에서는 @PersistenceContext를 활성화하면 알아서 주입받는다.
JPAQueryFactory 가 QueryDSL을 사용하기 위해서 구현해야 하는 것이다.
이렇게 설정해주면 QueryDSL을 사용할 수가 있다.
이제 테스트 코드를 작성해 보자.
@DataJpaTest
public class QuerydslTest {
@PersistenceUnit
EntityManagerFactory emf;
EntityManager em;
EntityTransaction tx;
JPAQueryFactory jpaQueryFactory;
private Member member;
@BeforeEach
void setUp() {
em = emf.createEntityManager();
tx = em.getTransaction();
jpaQueryFactory = new JPAQueryFactory(em);
tx.begin();
Address address = Address.builder()
.city("city")
.street("street")
.zipcode("zipcode")
.build();
Address comAddress = Address.builder()
.city("cocity")
.street("costreet")
.zipcode("cozipcode")
.build();
Period period = Period.of("20210714", "20210714");
member = Member.builder()
.name("kim")
.age(26)
.period(period)
.homeAddress(address)
.companyAddress(comAddress)
.build();
em.persist(member);
em.flush();
}
@Test
@DisplayName("QueryDSL 시작")
void querydslTest() {
QMember qMember = new QMember("m"); //생성된 별칭
List<Member> members = jpaQueryFactory.selectFrom(qMember)
.where(qMember.name.eq("kim")).orderBy(qMember.id.desc()).fetch();
assertThat(members.get(0).getName()).isEqualTo("kim");
assertThat(members.get(0).getAge()).isEqualTo(26);
}
}
책에서는 orderBy(qMember.id.desc()).list(qMember); 로 사용했는데
버전이 바뀌면서 list 메소드는 없어졌다고 한다.
그래서 공식문서를 찾아보니까


QueryDSL로 select 조회 쿼리를 만들었을때 로그이다.
이상한점 이라고 느낄수 있다면 어? 하고 코드상에는 jpaQueryFactory.from으로 시작하기 때문에 이것이 어떤 CRUD인지 모를 수 있다.
🤣fetch()를 해주면 반환이 List인데 그래도 타입은 캐스팅을 해주어야 한다.
자동으로 뭔가 Q클래스를 맞춰줄줄 알았다.
ㅋㅋㅋㅋㅋㅋㅋ 아니었다..... 그냥 기본값이 select가 아니라 list(qMember) 를 해주던 버전에서는 이 list 메소드가 엔티티 타입을 맞춰서 List에 넣어주었는데 지금은 selectFrom(qMember)를 해주면 이것이 List 타입을 맞춰준다.
이렇게 또 하나 깨달음을 얻는다 👍
@Test
void 페이징_테스트() {
QItem item = QItem.item;
List<Item> result = jpaQueryFactory.selectFrom(item).where(item.price.lt(500))
.orderBy(item.price.desc(), item.name.desc())
.offset(1).limit(3).fetch();
result.forEach(r -> System.out.println(r.toString()));
}
여기서 where 조건에 lt는 부등호로 < 이고, gt는 >이다. orderBy절에서는 쿼리 타입인(Q)에서 asc(), desc()를 지원해준다. 페이징은 offset 과 limit 을 조합해서 사용하면 된다.
이렇게해서 얻은 결과이다.

전체 데이터 수를 알고 싶을때는 이것 역시 바뀌었다.listResults()를 사용한다.
SearchResults<T> 가 아니라 버전이 바뀌면서 QueryResults<T>로 변경되었다.
listResults() 가 아니라 fetchResults()로 바뀌게 되었다.
@Test
@DisplayName("조회 결과 테스트")
void listResultsTest() {
QueryResults<Item> result = jpaQueryFactory.selectFrom(item).where(item.price.lt(500))
.orderBy(item.price.desc(), item.name.desc())
.offset(1).limit(3).fetchResults();
long total = result.getTotal(); //500보다 작은수 총 count
long limit = result.getLimit(); //limit 3
long offset = result.getOffset(); // offset 1
List<Item> results = result.getResults();
assertThat(total).isEqualTo(4L);
assertThat(limit).isEqualTo(3L);
assertThat(offset).isEqualTo(1L);
results.forEach(r -> System.out.println(r.toString()));
}
getTotal은 오류의 여지가 있어보인다. 왜냐면 저 QueryDSL 전체를 시켰을때의 count가 아니라 count가 먼저 실행되기 때문에 where() 조건까지 수행한 count가 나온다.
결과의 조회는 QueryResults의 getResults()를 사용하여 페이징 정렬할때처럼의 결과를 얻을수가 있다.
그룹은 groupBy를 사용하고 그 다음에 조건을 해주려면 having을 사용하면 된다.
List<Item> results = jpaQueryFactory.selectFrom(item)
.groupBy(item.price)
.having(item.price.lt(500))
.fetch();
