강의

멘토링

커뮤니티

Querydsl 소개

소개

소리큼

디버그 : 공백

프로젝트 환경설정

프로젝트 생성

프로젝트 생성

queryDsl

이거 :

빌드 실행 세팅

Build and run using IntelliJ IDEA

롬북 어노테이션 enable

에러 페이지 확인

디버깅 : controller를 study.querydsl 밖에 넣고 한참 찾음.

세팅하다 잘 안될때

컨트롤러 생성

롬복 세팅

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());

라이브러리 살펴보기

라이브러리

공식 사이트 소개

HikariCP

추가된 내용(조금씩 바뀜)

2군데 소스 세팅
IDE 임포트

H2 데이터베이스 설치

jdbc:h2:tcp://localhost/~/querydsl

키값 없으면 권한 오류남

스프링 부트 설정 - 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: trace

implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.5.8' 

성능이 중요하면 운영 시스템에서 제거해라.
성능보다 로그면 써도된다.

예제 도메인 모델

예제 도메인 모델과 동작확인

ToString 주의점 = 무한루프

초기화 조심 : 

private List<Member> members = new ArrayList<>();

보충

문제
커밋 삭제

기본 문법

시작 - JPQL vs Querydsl

본론

너무 좋아하시네

테스트 준비

@BeforeEach

JPQL 테스트

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 활용

이미 생성됨 : 

public static final QMember member = new QMember("member1");

단축키 : Alt + Enter 

결국 JPQL이 보고 싶으시면

use_sql_commets: true

디버그 : 

use_sql_commets: true
use_sql_comments: true

구지한 이유

00:57 에보면 member1로 만들어둠

검색 조건 쿼리

검색 조건 쿼리

이것만 제공하냐.

PDF 참조

성능픽
startWith = like 'member%' 검색

잘보셔야 됩니다.
여러 조건 AND

Member findMember = queryFactory
.selectFrom(member)
.where(
member.username.eq("member1"),
member.age.between(10,30)
)
.fetchOne();

AND의 경우 05:04 메모 방식을 선호

조건은 null을 무시함

결과 조회

결과 조회

fetchResults() = 디크립트

QueryResults<Member> results = queryFactory
.selectFrom(member)
.fetchResults();

results.getTotal();
List<Member> content = results.getResults();
results.getLimit();

fetchCount() = 디크립트

성능 이슈 : 

total카운트랑 컨텐츠 카운트랑 성능이 다를때가 있다.

정렬

정렬

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);
}
}

결과값:

그냥 조인 결과값:

PDF 

같은 결과값

.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 리포지토리로 변경

잘못만들어도 인텔리제이가 알아서 잘해줌

그러나 위험함

인터페이스로 만들어야 됩니다.

기가 막히네

동적 쿼리 Dsl도 넣어야지

사용자 정의 리포지토리

조금 복잡해요

규칙 : 이름 + Impl

인터페이스는 여러개 상속가능

기기가 막히네

정리

이렇게도 많이 한다.

쿼리용 리포지 토리를 따로 만듬

핵심과 커스텀을 나눈다.

스프링 데이터 페이징 활용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 최적화

CountQuery 최적화

소리 작음

데이터가 없거나 마지막페이지(offset+사이즈)

JPAQuery<Member> countQuery = queryFactory
return PageableExecutionUtils.getPage(content, pageable, () -> countQuery.fetchCount());

조건에 따라 호출을 안함

스프링 데이터 페이징 활용3 - 컨트롤러 개발

정리

totalcount 쿼리 안날라감

정렬

자동화 하기가 어려움

파라미터를 직접 받아서 조작

스프링 데이터 JPA가 제공하는 Querydsl 기능

인터페이스 지원 - QuerydslPredicateExecutor

제약이 커서 사용하기 부적합

아주 좋아보임

한계가 명확

LEFT JOIN 조인 안되면 탈락

리포지토리 지원 - QuerydslRepositorySupport

SORT 가 안된다요?

select 로 시작이 안됨

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())
));
}