SPRING/QueryDsl
[QueryDsl]
노트코드
2022. 2. 9. 13:28
Querydsl
프로젝트 셋팅
<dependencies>
....
**<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>${querydsl.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>${querydsl.version}</version>
</dependency>**
</dependencies>
<build>
<plugins>
...
**<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals>
<goal>process</goal>
</goals>
<configuration>
<outputDirectory>target/generated-sources/java</outputDirectory>
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>**
</plugins>
</build>
</project>
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 #쿼리를 이쁘게 보여줌
use_sql_comments : true #jpql 쿼리 보는법
logging.level:
org.hibernate.SQL: debug
org.hibernate.type: trace #로그에 파라미터가 어떤값이 들어갔는지 찍어줌
- quick start
@Test
public void startQuerydslCreateInstance() {
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(em);
Member findMember = jpaQueryFactory
.select(member)
.from(member)
.where(member.userName.eq("member1"))
.fetchOne();
assertThat(findMember.getUserName()).isEqualTo("member1");
}
- search
@Test
public void search() {
//1번째 타입 and사용
Member findMember = queryFactory
.selectFrom(member)
.where(member.userName.eq("member1").and(member.age.eq(10)))
.fetchOne();
//2번째 타입 , 사용
Member findMember2 = queryFactory
.selectFrom(member)
.where(member.userName.eq("member1"),
member.age.eq(10))
.fetchOne();
assertThat(findMember.getUserName()).isEqualTo("member1");
fetch() : 리스트 조회, 데이터없으면 빈 리스트 반환
fetchOne() : 단건 조회
- 결과가 없으면 null
- 결과가 둘 이상이면 NonUniqueResultException
fetchFirst() : limit(1).fetchOne()
fetchResult() : 페이징 정보 포함, total count 쿼리 추가 실행
fetchCount(): count쿼리로 변경해서 count수 조회
@Test
public void resultFetch(){
queryFactory
.selectFrom(member)
.fetch();
queryFactory
.selectFrom(member)
.where(member.userName.eq("member1"))
.fetchOne();
queryFactory
.selectFrom(member)
.fetchFirst();
QueryResults<Member> results = queryFactory
.selectFrom(member)
.fetchResults(); //조회쿼리 한번
results.getTotal(); // 쿼리 두번 나감 토탈숫자 쿼리 때문에
List<Member> contents = results.getResults();
long total = queryFactory
.selectFrom(member)
.fetchCount(); // 토탈 쿼리만 나감
}
쿼리가 복잡해지면 fetchResults를 사용시 토탈쿼리와, 데이터 쿼리가 다르게 나갈수 있다.(최적화 때문에)
이럴때는 fetchResults를 사용하지 말고, 조회쿼리와, 토탈쿼리를 따로 사용하자
- 정렬
/**
* 회원 정렬 순서
* 1. 나이 내림차순(desc)
* 2. 회원 이름 올림차순(asc)
* 단 2에서 회원 이름이 없으면 마지막에 출력(nulls last)
*/
@Test
public void sort() {
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);
System.out.println("member5 = " + member5);
Member member6 = result.get(1);
System.out.println("member6 = " + member6);
Member memberNull = result.get(2);
System.out.println("memberNull = " + memberNull);
//member5 = Member(id=8, userName=member5, age=100)
//member6 = Member(id=9, userName=member6, age=100)
//memberNull = Member(id=7, userName=null, age=100)
- 페이징
@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 paging2() {
QueryResults<Member> result = queryFactory
.selectFrom(member)
.orderBy(member.userName.desc())
.offset(1)
.limit(2)
.fetchResults();
assertThat(result.getTotal()).isEqualTo(4);
assertThat(result.getLimit()).isEqualTo(2);
assertThat(result.getOffset()).isEqualTo(1);
assertThat(result.getResults().size()).isEqualTo(2);
}
- groupby
/**
* 팀의 이름과 각 팀의 평균 연령을 구해라
*
* @throws Exception
*/
@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)
.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);
assertThat(teamB.get(team.name)).isEqualTo("teamB");
assertThat(teamB.get(member.age.avg())).isEqualTo(35);
}
- 조인
List<Tuple> result = queryFactory
.selectFrom(member)
.join(member.team, team)
.where(team.name.eq("teamA))
.fetch();
-join(), innerJoin(): 내부조인
-leftJoin(): left외부조인
-rightJoin() : right 외부조인
-JPQL의 on과 성능 최적화를 위한 fetch조인
- 세타조인
/**
* 연관 관계 없어도 조인 가능 세타 조인
* 회원의 이름이 팀 이름과 같은 회원 조회
*/
@Test
public void thetaJoin() throws Exception {
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
em.persist(new Member("teamC"));
List<Member> result = queryFactory
.select(member)
.from(member, team)
.where(member.userName.eq(team.name))
.fetch();
assertThat(result)
.extracting("userName")
.containsExactly("teamA", "teamB");
}
- 조인 on절
/**
* 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
* select m , t from Member m left join m.team t on t.name = 'teamA'
*/
@Test
public void join_on_filltering() {
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);
}
}
-on절을 활용해 조인 대상을 필터링 할때, 외부조인이 아니라 내부조인을 사용하면, where절에서 필터링 하는것과 기능이 동일하다. 따라서 on절을 활용한 조인 대상 필터링을 사용할 때, 내부조인 이면 익숙한 where절로
해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용하자.
- 연관관계 없는 엔티티 외부조인
/**
* 연관 관계 없어도 조인 가능 세타 조인
* 회원의 이름이 팀 이름과 같은 회원 조회
*/
@Test
public void join_on_no_relaction() throws Exception {
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))
// 기존 조인은 member.team,team 이런식으로 진행되지만, 이렇게 쓰면 id로 조인을 하려고함, 우리는 연관관계 없는 이름(team.name) 조인이기때문에 team만 적음
.where(member.userName.eq(team.name))
.fetch();
for (Tuple tuple : result) {
System.out.println(tuple);
}
}
on을 사용해서 서로 관계까 없는 필드로 외부 조인하는 기능이 추가되었다. 내부조인도 가능하다.
- 패치조인 미적용
@PersistenceUnit
EntityManagerFactory emf;
@Test
public void fetchJoinNo() {
em.flush();
em.clear();
Member member1 = queryFactory
.selectFrom(member)
.where(member.userName.eq("member1"))
.fetchOne();
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(member1.getTeam()); // 해당 객체가 로딩된 객체인지(프록시인지) 확인하는 메소드
assertThat(loaded).as("패치조인 미적용").isFalse();
}
- 패치조인 적용
@Test
public void fetchJoinUse() {
em.flush();
em.clear();
Member member1 = queryFactory
.selectFrom(member)
.join(member.team, team).fetchJoin()
.where(member.userName.eq("member1"))
.fetchOne();
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(member1.getTeam()); // 해당 객체가 로딩된 객체인지(프록시인지) 확인하는 메소드
assertThat(loaded).as("패치조인 적용").isTrue();
}
- 서브쿼리
@Test
public void subQuery() {
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.eq(
select(memberSub.age.max())
.from(memberSub)
))
.fetch();
assertThat(result).extracting("age")
.containsExactly(40);
}
@Test
public void subQueryGoe() {
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.goe(
select(memberSub.age.avg())
.from(memberSub)
))
.fetch();
assertThat(result).extracting("age")
.containsExactly(30, 40);
}
- 서브쿼리 여러 건 처리 in 사용
@Test
public void subQueryIn() {
QMember memberSub = new QMember("memberSub");
List<Member> result = queryFactory
.selectFrom(member)
.where(member.age.in(
select(memberSub.age)
.from(memberSub)
.where(memberSub.age.gt(10))
)
)
.fetch();
assertThat(result).extracting("age")
.containsExactly(20, 30, 40);
}
- select절에 subquery
@Test
public void selectSubQuery() {
QMember memberSub = new QMember("memberSub");
List<Tuple> result = queryFactory
.select(member.userName,
select(memberSub.age.avg())
.from(memberSub)
)
.from(member)
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}
JPA JPQL 서브쿼리의 한계점으로 from절의 서브쿼리는 지원하지 않는다.
from절의 서브쿼리 해결방안
- 서브쿼리를 join으로 변경
- 애플리케이션에 쿼리를 2번 분리해서 실행
- nativeSQL을 사용
- Case문(단순)
@Test
public void basicCase() {
List<String> result = queryFactory
.select(member.age
.when(10).then("열살")
.when(20).then("스무살")
.otherwise("기타"))
.from(member)
.fetch();
for (String s : result) {
System.out.println(s);
}
}
- Case문(복잡)
/**
* 좀더 복잡할 경우 CaseBuilder사용 굳이 사용하지 말자(DB는 조회용)
*/
@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);
}
}