
실전! Querydsl
노트 작성자
naelonambul
프로젝트 환경설정
프로젝트 생성
Querydsl 설정과 검증
꼭 맞춰서 따라해야됨
엔티티 생성
위치 :
설정값 중요
컴파일 :
PDF 81 쪽 Gradle 설정 필요
빌드 클린 :
깃에 올리지마 = .gitignore
지금 처럼 빌드 폴더로 넣어두면 다른 세팅이 딱히 필요 없다.
Hello hello = new Hello();
em.persist(hello);
JPAQueryFactory query = new JPAQueryFactory(em);
QHello qHello = new QHello("hello");
Hello result = query
.selectFrom(qHello)
.fetchOne();
assertThat(result).isEqualTo(hello);
assertThat(result.getId()).isEqualTo(hello.getId());
스프링 부트 설정 - JPA, DB
application.yml 생성
spring:
datasource:
url: jdbc:h2:tcp://localhost/~/querydsl
username: sa
password:
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: create
properties:
hibernate:
# show_sql: true
format_sql: true
logging.level:
org.hibernate.SQL: debug
# org.hibernate.type: trace
show_sql 은 system.out으로 나와서 쓰면 안됨.
@Commit변수값 보기
# org.hibernate.type: traceimplementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.8'
성능이 중요하면 운영 시스템에서 제거해라.
성능보다 로그면 써도된다.
예제 도메인 모델
기본 문법
시작 - JPQL vs Querydsl
본론
너무 좋아하시네
테스트 준비
@BeforeEachJPQL 테스트
assertThat(findMember.getUsername()).isEqualTo("member1");
QMember 생성 = gradle
맴버 생성됨 :
@Test
public void startQuerydsl(){
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QMember m = new QMember("m");
Member findMember = queryFactory
.select(m)
.from(m)
.where(m.username.eq("member1"))
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
이미 생성된 객체가 있어서 타입 클래스를 안정해도 되는듯?
준비된 파라미터 바인딩이라서 인젝션 공격에 대비함
큰 차이점
문자 VS 함수
오타 발견 = 사용자가 실행해서 메서드가 작동하면 VS
컴파일 타임
즐겁다.
아는거 다나옴(like)
필드 레벨 사용 가능.
@Autowired
EntityManager em;
JPAQueryFactory queryFactory;
queryFactory = new JPAQueryFactory(em);
EntityManager 자체가 스레드 세이프 설계다.
기본 Q-Type 활용
검색 조건 쿼리
결과 조회
정렬
정렬
em.persist(new Member(null, 100));
em.persist(new Member("member5", 100));
em.persist(new Member("member6", 100));
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(100))
.orderBy(member.age.desc(), member.username.asc().nullsLast())
.fetch();
Member member5 = result.get(0);
Member member6 = result.get(1);
Member memberNull = result.get(2);
assertThat(member5.getUsername()).isEqualTo("member5");
assertThat(member6.getUsername()).isEqualTo("member6");
assertThat(memberNull.getUsername()).isEqualTo(null);
nullfirst, nulllast
페이징
@Test
public void paging1(){
List<Member> result = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1)
.limit(2)
.fetch();
assertThat(result.size()).isEqualTo(2);
}
@Test
public void paging1(){
QueryResults<Member> queryResults = queryFactory
.selectFrom(member)
.orderBy(member.username.desc())
.offset(1)
.limit(2)
.fetchResults();
assertThat(queryResults.getTotal()).isEqualTo(4);
assertThat(queryResults.getLimit()).isEqualTo(2);
assertThat(queryResults.getOffset()).isEqualTo(1);
assertThat(queryResults.getResults().size()).isEqualTo(2);
}
집합
집합 groupBy, having
@Test
public void aggregation(){
List<Tuple> result = queryFactory
.select(
member.count(),
member.age.sum(),
member.age.avg(),
member.age.max(),
member.age.min()
)
.from(member)
.fetch(); // fetchOne()
Tuple tuple = result.get(0);
assertThat(tuple.get(member.count())).isEqualTo(4);
assertThat(tuple.get(member.age.sum())).isEqualTo(100);
assertThat(tuple.get(member.age.avg())).isEqualTo(25);
assertThat(tuple.get(member.age.max())).isEqualTo(40);
assertThat(tuple.get(member.age.min())).isEqualTo(10);
}
사실 실무에서는 tuple을 안씀, DTO를 씀
라이브 템플릿
단축키 : 오류로 이동 F2
/*
* 팀의 이름과 각팀의 평균 연령을 구해라.
*/
@Test
public void group() throws Exception{
List<Tuple> result = queryFactory
.select(team.name, member.age.avg())
.from(member)
.join(member.team, team)
.groupBy(team.name)
// .having()
.fetch();
Tuple teamA = result.get(0);
Tuple teamB = result.get(1);
assertThat(teamA.get(team.name)).isEqualTo("teamA");
assertThat(teamA.get(member.age.avg())).isEqualTo(15); // (10+20) /2
assertThat(teamB.get(team.name)).isEqualTo("teamB");
assertThat(teamB.get(member.age.avg())).isEqualTo(35); // (30+40) /2
}
조인 - 기본 조인
/**
* 팀 A에 소속된 모든 회원
*/
@Test
public void join(){
List<Member> result = queryFactory
.selectFrom(member)
.join(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("member1", "member2");
}
.leftJoin(member.team, team)
result = [Member(id=3, username=member1, age=10), Member(id=4, username=member2, age=20)]
On도 가능
세타 조인 = 막조인
모든 팀, 모든 회원 막 조인
주의점
외부 조인을 On으로 가능
조인 - on절
조인 On 절
select 가 다른 타입이라 튜플
/**
* 예) 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
* JPQL: select m, t from Member m left join m.team t on t.name='teamA'
*/
@Test
public void join_on_filtering(){
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(member.team, team)
.on(team.name.eq("teamA"))
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
결과값:
그냥 조인 결과값:
같은 결과값
.join(member.team, team)
.where(team.name.eq("teamA"))
조건 = 조인대상을 필터링 해야된다.
근데 방법이 left join이면 On
그냥 join 이면 where 절을 써라.
요거를 더 많이 씁니다.
기존의 문법
.leftJoin(member.team, team)
/**
* 연관관계 없는 엔티티 외부 조인
* 회원의 이름이 팀 이름과 같은 대상 외부 조인
*/
@Test
public void join_on_no_relation(){
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
em.persist(new Member("teamC"));
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(team).on(member.username.eq(team.name))
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
결과값:
.join(team).on(member.username.eq(team.name))
결과값:
조인 - 페치 조인
페치 조인
페치 조인
@Test
public void fetchJoinNo(){
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1"))
.fetchOne();
}
영속성 초기화 판별 함수
@PersistenceUnit
EntityManagerFactory emf;
emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
실무에서 정말 많이 씁니다.
Member findMember = queryFactory
.selectFrom(member)
.join(member.team, team).fetchJoin()
.where(member.username.eq("member1"))
.fetchOne();
서브 쿼리
서브 쿼리
쉽지 않음.
서브쿼리니까 별명 겹치면 안됨.
/**
* 나이가 가장 많은 회원 조회
*/
@Test
public void subQuery(){
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(
JPAExpressions
.select(memberSub.age.max())
.from(memberSub)
))
.fetch();
assertThat(result)
.extracting("age")
.containsExactly(40);
}
.where(member.age.goe(
JPAExpressions
.select(memberSub.age.avg())
.from(memberSub)
))
.fetch();
.select(memberSub.age)
.from(memberSub)
.where(memberSub.age.gt(10))
@Test
public void selectSubQuery(){
QMember memberSub = new QMember("memberSub");
List<Tuple> result = queryFactory
.select(member.username,
JPAExpressions
.select(memberSub.age.avg())
.from(memberSub))
.from(member)
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
결과값:
static import 가능
.select(member.username,
select(memberSub.age.avg())
.from(memberSub))
JPA JPQL의 한계점
from 절의 서브쿼리가 안됨.
해결방안
1. 서브쿼리를 JOIN으로 변경
2. 애플리케이션에서 쿼리를 2번 분리
마지막은 nativeSQL을 사용하세요.
from 절의 서브쿼리를 쓰는 이유
= GUI 기능...
SQL AntiPatterns 책 빌카윈
Case 문
Case 문
@Test
public void basicCase(){
List<String> result = queryFactory
.select(member.age
.when(10).then("열살")
.when(20).then("스무살")
.otherwise("기타"))
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
No sources given = from 절
@Test
public void complexCase(){
List<String> result = queryFactory
.select(new CaseBuilder()
.when(member.age.between(0, 20)).then("0~20살")
.when(member.age.between(21, 30)).then("21~30살")
.otherwise("기타"))
.from(member)
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
이걸 정말 써야되?
DB는 로우데이터, 필터링,변환 까지는뭐..
근데 왠만하면 애플리케이션에서 해
상수, 문자 더하기
상수, 문자
@Test
public void constant(){
List<Tuple> result = queryFactory
.select(member.username, Expressions.constant("A"))
.from(member)
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
jpql 에서는 안나옴
Concat 필요하죠?
문자 숫자라 안됨
.concat("_").concat(member.age)
@Test
public void concat(){
List<String> result = queryFactory
.select(member.username.concat("_").concat(member.age.stringValue()))
.from(member)
.where(member.username.eq("member1"))
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
enum처리 = stringValue 쓸일 많음
중급 문법
프로젝션과 결과 반환 - 기본
프로잭션 : select대상 지정
프로젝션이 1개 일때는 쉽게 가능한데
@Test
public void tupleProjection(){
List<Tuple> result = queryFactory
.select(member.username, member.age)
.from(member)
.fetch();
for (Tuple tuple : result) {
String username = tuple.get(member.username);
Integer age = tuple.get(member.age);
System.out.println("username = " + username);
System.out.println("age = " + age);
}
}
안에서만 쓰시고 DTO 로 바꿔서 쓰세요.
프로젝션과 결과 반환 - DTO 조회
중요한 내용
딱 찍어서 2개만
JPQL:
@Test
public void findDtoByJPQL(){
List<MemberDto> resultList = em.createQuery(
"select new study.querydsl.dto.MemberDto(m.username, m.age) " +
"from Member m", MemberDto.class)
.getResultList();
for (MemberDto memberDto : resultList) {
System.out.println("memberDto = " + memberDto);
}
}
Setter
디버그 : getConstructor0(Class.java:3349) 기본 생성자
@Data 기본 생성자 없음.
@Test
public void findDtoByFeild(){
List<MemberDto> result = queryFactory
.select(Projections.fields(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
.fields 는 getter setter 없어도 필드에 값을 꽂음.
.bean은 getter setter 만들어 줘야됨.
.constructor 라도 타입을 맞춰야됨.
@Test
public void findUserDto(){
List<UserDto> result = queryFactory
.select(Projections.fields(UserDto.class,
member.username,
member.age))
.from(member)
.fetch();
for (UserDto UserDto : result) {
System.out.println("UserDto = " + UserDto);
}
}
결과값:
member.username.as("name"),
List<UserDto> result = queryFactory
.select(Projections.fields(UserDto.class,
member.username.as("name"),
ExpressionUtils.as(JPAExpressions
.select(memberSub.age.max())
.from(memberSub), "age")
))
.from(member)
.fetch();
오른쪽 컬럼은 최대 40세로
결과값:
ExpressionUtils.as(member.username, "name"),
프로젝션과 결과 반환 - @QueryProjection
궁극의 방법이나 단점도 있음.
@QueryProjection@Test
public void findDtoByQueryProjection(){
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
constructor의 문제
컴파일 오류를 못잡음
런타임에 오류가 나옴.
단축키 : ctrl+p
편리한데
@QueryProjection 넣어줘야된다.
아키텍쳐 문제 -의존성이 추가됨.
Dto 도 좀 깔끔하게 가져가자?
아니면 좀 섞자.
정리
동적 쿼리 - BooleanBuilder 사용
동적 쿼리
문서에 나오는 방법
값에 따라서 쿼리가 바뀌어야됨.
usernameCond 가 있으면 조건 추가
@Test
public void dynamicQuery_BoolenBuilder(){
String usernameParam = "member1";
Integer ageParam = 10;
List<Member> result = searchMember1(usernameParam, ageParam);
assertThat(result.size()).isEqualTo(1);
}
private List<Member> searchMember1(String usernameCond, Integer ageCond) {
BooleanBuilder builder = new BooleanBuilder();
if(usernameCond != null){
builder.and(member.username.eq(usernameCond));
}
if(ageCond != null){
builder.and(member.age.eq(ageCond));
}
return queryFactory
.selectFrom(member)
.where(builder)
.fetch();
}
이런식으로 적으시면됨.
//방어코드 + 기본값
BooleanBuilder builder = new BooleanBuilder(member.username.eq(usernameCond));
AND OR 로 조립도 됨.
동적 쿼리 - Where 다중 파라미터 사용
강사님이 실무에서 좋아하는 방법
기능은 같고 코드가 다름
여기부터 다르다.
null 방어
null 넣으면 조건이 무시됨.
private List<Member> searchMember2(String usernameCond, Integer ageCond) {
return queryFactory
.selectFrom(member)
.where(null, usernameEq(usernameCond), ageEq(ageCond))
.fetch();
}
private Predicate ageEq(Integer ageCond) {
if(ageCond != null) {
return member.age.eq(ageCond);
} else {
return null;
}
}
private Predicate usernameEq(String usernameCond) {
if(usernameCond != null) {
return member.username.eq(usernameCond);
} else {
return null;
}
}
private Predicate ageEq(Integer ageCond) {
return ageCond != null ? member.age.eq(ageCond) : null;
}
private Predicate usernameEq(String usernameCond) {
return usernameCond != null ? member.username.eq(usernameCond) : null;
}
짧은 건 3항 연산자.
장점 :
깔끔함
개발할때 위에만 봄.
조립이 가능하다.
심지어 재활용 가능
Predicate -> BooleanExpression
private List<Member> searchMember2(String usernameCond, Integer ageCond) {
return queryFactory
.selectFrom(member)
//.where(null, usernameEq(usernameCond), ageEq(ageCond))
.where(allEq(usernameCond, ageCond))
.fetch();
}
private BooleanExpression allEq(String usernameCond, Integer ageCond){
return usernameEq(usernameCond).and(ageEq(ageCond));
}
광고 상태 isValid, 광고 날짜 IN 등등 : isServicable
조립을 하면 너무 편하다.
isValid(usernameCond).Date(ageEq(ageCond));수정, 삭제 벌크 연산
수정, 삭제 배치 쿼리
@Test
@Commit
public void bulkUpdate(){
//member1 = 10 -> 비회원
//member2 = 10 -> 비회원
//member3 = 유지
//member4 = 유지
long count = queryFactory
.update(member)
.set(member.username, "비회원")
.where(member.age.lt(28))
.execute();
}
벌크 연산후 무조건 호출해라
em.flush();
em.clear();
기존 숫자 더하기 곱하기
@Test
public void bulkAdd(){
long count = queryFactory
.update(member)
.set(member.age, member.age.add(1))
.execute();
}
.set(member.age, member.age.multiply(1))
SQL function 호출하기
SQL function
@Test
public void sqlFunction(){
List<String> result = queryFactory
.select(
Expressions.stringTemplate(
"function('replace', {0},{1},{2})",
member.username, "member", "M"))
.from(member)
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
dialect에 등록이 되었이야됨.
상속받은 파일만들기 전강의에 있음
소문자
간단한거 ANSI 표준
@Test
public void sqlFunction2(){
List<String> result = queryFactory
.select(member.username)
.from(member)
.where(member.username.eq(
Expressions.stringTemplate(
"function('lower', {0})",
member.username)))
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
.where(member.username.eq(member.username.lower()))
.fetch();
실무 활용 - 순수 JPA와 Querydsl
순수 JPA 리포지토리와 Querydsl
리포지토리와 Querydsl
repository = DAO
@Repository
public class MemberJpaRepository {
private final EntityManager em;
private final JPAQueryFactory queryFactory;
public MemberJpaRepository(EntityManager em) {
this.em = em;
this.queryFactory = new JPAQueryFactory(em);
}
}
public Optional<Member> findById(Long id){
Member findMember = em.find(Member.class, id);
return Optional.ofNullable(findMember);
}
public List<Member> findAll(){
return em.createQuery(
"select m from Member m", Member.class)
.getResultList();
}
public List<Member> findByUsername(String username){
return em.createQuery(
"select m from Member m where m.username = :username", Member.class)
.setParameter("username", username)
.getResultList();
}
@SpringBootTest
@Transactional
class MemberJpaRepositoryTest {
@Autowired
EntityManager em;
@Autowired
MemberJpaRepository memberJpaRepository;
}
@Test
public void basicTest(){
Member member = new Member("member1", 10);
memberJpaRepository.save(member);
Member findMember = memberJpaRepository.findById(member.getId()).get();
assertThat(findMember).isEqualTo(member);
List<Member> result1 = memberJpaRepository.findAll();
assertThat(result1).containsExactly(member);
List<Member> result2 = memberJpaRepository.findByUsername("member1");
assertThat(result2).containsExactly(member);
}
public List<Member> findAll_querydsl(){
return queryFactory
.selectFrom(member)
.fetch();
}
public List<Member> findByUsername_Querydsl(String username){
return queryFactory
.selectFrom(member)
.where(member.username.eq(username))
.fetch();
}
요건 취향입니다.
SpringBean으로 등록해도됨.
@Bean
JPAQueryFactory jpaQueryFactory(EntityManager em){
return new JPAQueryFactory(em);
}
public MemberJpaRepository(EntityManager em, JPAQueryFactory queryFactory) {
this.em = em;
this.queryFactory = queryFactory;
}
동시성 문제
트랜잭션 단위로 작동하기 때문에 문제가 안됨.
동적 쿼리와 성능 최적화 조회 - Builder 사용
어노테이션 추가
@QueryProjection컴파일
단점
Dto가 의존성이 생김
그러나 Dto는 좀 써도됨.
검색 조건
Integer = null일수도 있어서.
MemberCond로 써도됨.
처음에 막 머리에 안떠오르고
실무하시는분은 아시겟지만
"" 이게 잘들어옴
BooleanBuilder builder = new BooleanBuilder();
if (hasText(condition.getUsername())) {
builder.and(member.username.eq(condition.getUsername()));
}
if (hasText(condition.getTeamName())) {
builder.and(team.name.eq(condition.getTeamName()));
}
if (condition.getAgeGoe() != null) {
builder.and(member.age.goe(condition.getAgeGoe()));
}
if (condition.getAgeGoe() != null) {
builder.and(member.age.loe(condition.getAgeLoe()));
}
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)
.fetch();
복붙하면 실수 많이함
.where(builder)
허접한 테스트라도 있는게 낫다.
디버깅 : (나)where 절 안넣었네
조건이 다빠지면 조심해야되요.
하루에 1000건 한달에 쿼리 한방에 3만건...
무조건 기본조건이 있는것이 좋음. 최소 리미트
동적 쿼리와 성능 최적화 조회 - Where절 파라미터 사용
where 절 파라미터
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()
.fetch();
}
.where(
userNameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe()))
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.goe(ageLoe) : null;
}
깔끔, 재사용
장점 :
수정을 거의하지 않고 재사용 가능
public List<Member> searchMember(MemberSearchCondition condition){
return queryFactory
.selectFrom(member)
ageBetween(condition.getAgeLoe(), condition.getAgeLoe())private BooleanExpression ageBetween(int ageLoe, int ageGoe){
return ageGoe(ageLoe).and(ageGoe(ageGoe));
}
서비스 하다보면 조건이 많다.
조회 API 컨트롤러 개발
조회 API 컨트롤러 개발
test에 영향이 없도록 profile을 쪼갤것
local, dev, real
@Profile("local")
@Component
@RequiredArgsConstructor
public class InitMember {
}
static class InitMemberService{
@PersistenceContext private EntityManager em;
@Transactional
public void init(){
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
for(int i=0; i<100; i++){
Team selectedTeam = i%2 == 0 ? teamA : teamB;
em.persist(new Member("member" + i , i, selectedTeam));
}
}
}
private final InitMemberService initMemberService;
@PostConstruct
public void init(){
initMemberService.init();
}
@Component
static class InitMemberService{}
그렇게 하면 동작 안함.
@PostConstruct
@Transitional
라이트 사이클 때문에 둘이 같이 적용 안됨.
디버그 : application.yml profils -> profiles
The following 1 profile is active: "local"
디버깅 : member.age.goe->loe
test 프로파일일때는 initMember가 활성화 안됨
실무 활용 - 스프링 데이터 JPA와 Querydsl
스프링 데이터 JPA 리포지토리로 변경
사용자 정의 리포지토리
스프링 데이터 페이징 활용1 - Querydsl 페이징 연동
스프링 데이터 페이징
@Override
public Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable) {
QueryResults<MemberTeamDto> results = 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()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetchResults();
List<MemberTeamDto> content = results.getResults();
long total = results.getTotal();
return new PageImpl<>(content, pageable, total);
}
Page<MemberTeamDto> searchPageSimple(MemberSearchCondition condition, Pageable pageable);
Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable);
MemberSearchCondition condition = new MemberSearchCondition();
PageRequest pageRequest = PageRequest.of(0, 3);
Page<MemberTeamDto> result = memberRepository.searchPageSimple(condition, pageRequest);
assertThat(result.getSize()).isEqualTo(3);
assertThat(result.getContent()).extracting("username").containsExactly("member1", "member2", "member3");
정리
orderBy
데이터의 내용과 전체 카운트의 별도
이득 :
내용과 카운트의 성능이 차이가 날때
or 카운트 먼저 쿼리하고 0이면 내용 쿼리를 안한다거나
데이터 없으면 그냥 fetchResults
@Override
public Page<MemberTeamDto> searchPageComplex(MemberSearchCondition condition, Pageable pageable) {
List<MemberTeamDto> content = getFetch(condition, pageable);
long total = getTotal(condition);
return new PageImpl<>(content, pageable, total);
}
스프링 데이터 페이징 활용2 - CountQuery 최적화
스프링 데이터 JPA가 제공하는 Querydsl 기능
인터페이스 지원 - QuerydslPredicateExecutor
Querydsl 지원 클래스 직접 만들기
???
라이브러리 스터디용
지금 부터 어려워짐
public Page<Member> searchPageByAppplePage(MemberSearchCondition condition, Pageable pageable){
JPAQuery<Member> query = selectFrom(member)
.where(userNameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())
);
List<Member> content = getQuerydsl().applyPagination(pageable, query).fetch();
return PageableExecutionUtils.getPage(content, pageable, query::fetchCount);
}
public Page<Member> applyPagination(MemberSearchCondition condition, Pageable pageable){
return applyPagination(pageable, query ->
query.selectFrom(member)
.where(userNameEq(condition.getUsername()),
teamNameEq(condition.getTeamName()),
ageGoe(condition.getAgeGoe()),
ageLoe(condition.getAgeLoe())
));
}




