Query Dsl
- 주의사항
-> 엔터티클래스에 필드추가되면 gradle 재 빌드하기
build -> clean
other -> complieQuerydsl
커스텀 인터페이스
- 한 Repo에서 다중상속 받아서 여러가지 커스텀 쿼리 사용 가능
package com.spring.jpastudy.chap06_querydsl.repository;
import com.spring.jpastudy.chap06_querydsl.entity.Idol;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface IdolRepository extends JpaRepository<Idol, Long>, IdolCustomRepository{
@Query("SELECT i FROM Idol i ORDER BY i.age DESC")
List<Idol> findAllBySorted();
}
package com.spring.jpastudy.chap06_querydsl.repository;
import com.spring.jpastudy.chap06_querydsl.entity.Idol;
import java.util.List;
public interface IdolCustomRepository {
List<Idol> findAllSortedByName();
List<Idol> findByGroupName();
}
package com.spring.jpastudy.chap06_querydsl.repository;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.spring.jpastudy.chap06_querydsl.entity.Idol;
import com.spring.jpastudy.chap06_querydsl.entity.QIdol;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
import static com.spring.jpastudy.chap06_querydsl.entity.QIdol.*;
@Repository
@RequiredArgsConstructor
public class IdolRepositoryCustomImpl implements IdolCustomRepository {
private final JdbcTemplate template;
private final JPAQueryFactory factory;
@Override
public List<Idol> findAllSortedByName() {
String sql = "SELECT * FROM tbl_idol ORDER BY idol_name ASC";
return template.query(sql, (rs, n) -> {
String idolName = rs.getString("idol_name");
int age = rs.getInt("age");
return new Idol(
idolName,
age,
null
);
});
}
@Override
public List<Idol> findByGroupName() {
return factory
.select(idol)
.from(idol)
.orderBy(idol.group.groupName.asc())
.fetch()
;
}
}
- service
- Repo에서 다양한 방법으로 쿼리를 짤 수 있음
package com.spring.jpastudy.chap06_querydsl.service;
import com.spring.jpastudy.chap06_querydsl.entity.Idol;
import com.spring.jpastudy.chap06_querydsl.repository.IdolRepository;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional
public class IdolService {
private final IdolRepository idolRepository;
public List<Idol> getIdols() {
List<Idol> idolList = idolRepository.findByGroupName();
return idolList;
}
}
정렬과 페이징 처리
@Test
@DisplayName("QueryDSL로 기본 정렬하기")
void sortingTest() {
List<Idol> sortedIdols = factory
.selectFrom(idol)
.orderBy(idol.age.desc())
.fetch();
}
@Test
@DisplayName("페이징 처리 하기")
void pagingTest() {
int pageNo = 1;
int amount = 2;
List<Idol> pagedIdols = factory
.selectFrom(idol)
.orderBy(idol.age.desc())
.offset((pageNo - 1) * amount)
.limit(amount)
.fetch();
Long totalCount = factory
.select(idol.count())
.from(idol)
.fetchOne();
}
@Test
@DisplayName("나이 내림차순 정렬 및 페이징 처리 조회")
void testSortByAgeDescAndPaging() {
int pageNumber = 0;
int pageSize = 3;
List<Idol> pagedIdols = factory
.selectFrom(idol)
.orderBy(idol.age.desc())
.offset(pageNumber * pageSize)
.limit(pageSize)
.fetch();
assertNotNull(pagedIdols);
assertEquals(pageSize, pagedIdols.size());
assertEquals("사쿠라", pagedIdols.get(0).getIdolName());
assertEquals(26, pagedIdols.get(0).getAge());
}
@Test
@DisplayName("특정 그룹의 아이돌을 이름 기준으로 오름차순 정렬 및 페이징 처리 조회")
void testSortByNameAscAndPagingForGroup() {
String groupName = "아이브";
int pageNumber = 0;
int pageSize = 2;
List<Idol> pagedIdols = factory
.selectFrom(idol)
.where(idol.group.groupName.eq(groupName))
.orderBy(idol.idolName.asc())
.offset(pageNumber * pageSize)
.limit(pageSize)
.fetch();
assertNotNull(pagedIdols);
assertEquals(pageSize, pagedIdols.size());
}
QueryDsl의 GROUP BY, HAVING
@Test
@DisplayName("성별별, 그룹별로 그룹화하여 아이돌의 숫자가 3명 이하인 그룹만 조회")
void groupByGenderTest() {
List<Tuple> idolList = factory
.select(idol.group, idol.gender, idol.count())
.from(idol)
.groupBy(idol.gender, idol.group)
.having(idol.count().loe(3))
.fetch();
System.out.println("\n\n\n");
for (Tuple tuple : idolList) {
Group group = tuple.get(idol.group);
String gender = tuple.get(idol.gender);
Long count = tuple.get(idol.count());
System.out.println(
String.format("\n그룹명 : %s, 성별: %s, 인원수: %d\n"
, group.getGroupName(), gender, count)
);
}
}
@Test
@DisplayName("연령대별로 그룹화하여 아이돌 수를 조회")
void ageGroupTest() {
NumberExpression<Integer> ageGroupExpression = new CaseBuilder()
.when(idol.age.between(10, 19)).then(10)
.when(idol.age.between(20, 29)).then(20)
.when(idol.age.between(30, 39)).then(30)
.otherwise(0);
List<Tuple> result = factory
.select(ageGroupExpression, idol.count())
.from(idol)
.groupBy(ageGroupExpression)
.fetch();
}
}
@Test
@DisplayName("그룹별로 그룹화해서 그룹 평균나이 조회, 평균나이 20~25 사이인 그룹만 조회")
void groupByGroup() {
List<Tuple> result = factory
.select(idol.group.groupName, idol.age.avg())
.from(idol)
.groupBy(idol.group)
.having(idol.age.avg().between(20, 25))
.fetch();
assertFalse(result.isEmpty());
}
}
- Tuple 말고 DTO로 받기!
-> Projections객체를 사용
-> dto에는 생성자가 꼭 있어야 함.
-> Projections.constructor(내가 받을 타입=dto.class, 받을 것들)
@Test
@DisplayName("그룹별로 그룹화해서 그룹 평균나이 조회, 평균나이 20~25 사이인 그룹만 조회 (결과 DTO 처리)")
void groupByGroupDto() {
List<GroupAverageAgeDto> result = factory
.select(
Projections.constructor(
GroupAverageAgeDto.class,
idol.group.groupName,
idol.age.avg()
)
)
.from(idol)
.groupBy(idol.group)
.having(idol.age.avg().between(20, 25))
.fetch();
assertFalse(result.isEmpty());
for (GroupAverageAgeDto dto : result) {
String groupName = dto.getGroupName();
double averageAge =dto.getAverageAge();
}
}