쿼리를 문자가 아닌 코드로 작성할 수 있으며, 쉽고 간결하며 그 모양도 쿼리와 비슷하게 개발할 수 있음
www.querydsl.com/static/querydsl/3.6.3/reference/ko-KR/html_single/
1. QueryDSL 설정
필요 라이브러리
<!-- QueryDSL -->
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>3.6.3</version>
</dependency>
<dependency>
<groupId>com.mysema.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>3.6.3</version>
</dependency>
- querydsl-jpa : QueryDSL JPA 라이브러리
- querydsl-apt : 쿼리 타입(Q)을 생성할 때 필요한 라이브러리
환경설정
<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.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
maven plugin설정이 필요하다.
QueryDSL을 사용하려면 엔티티를 기반으로 쿼리 타입이라는 쿼리용 클래스를 생성해야 한다.
→ 쿼리 타입을 생성해주는 플러그인을 설정해줘야 함
mvn compile 실행시, ouputDirectory에 저장한 target/generated-sources 위치에 Q로 시작하는 쿼리 타입이 생긴다. Member.java ⇒ QMember.java
2. 시작
QueryDSL 기본 사용하기
@Component
public class MemberRunner implements ApplicationRunner {
@Autowired
EntityManager em;
@Override
public void run(ApplicationArguments args) throws Exception {
JPAQuery query = new JPAQuery(em);
QMember qMember = new QMember("m"); // 생성되는 JPQL 별칭
List<Member> memberList = query.from(qMember)
.where(qMember.name.eq("이름1"))
.orderBy(qMember.name.desc())
.list(qMember);
}
}
QueryDSL 사용 순서
com.mysema.query.jpa.impl.JPAQuery 객체를 생성 (엔티티매니저를 넘김)
쿼리 타입(Q)을 생성, 별칭 주기
Hibernate: select member0_.id as id1_0_, member0_.age as age2_0_, member0_.name as name3_0_ from member member0_ where member0_.name=? order by member0_.name desc
3. 검색 조건 쿼리
JPAQuery query = new JPAQuery(em);
QMember member = QMember.member;
List<Member> members = query.from(member)
.where(member.name.eq("이적").and(member.age.gt(30)))
.where(member.name.eq("이적"), member.age.gt(30)) // 여러 파라미터를 넘겨도된다. (and 연산)
.list(member);
// member.age.between(30, 50); // 나이가 30~50 사이
// member.name.contains("이적"); // '%이적%' 검색
// member.name.startsWith("이적"); // '이적%' 검색
4. 결과 조회
결과 조회 API는 com.mysema.query.Projectable에 정의되어 있다.
- uniqueResult() : 조회 결과가 한 건일 경우 사용. 결과가 없을시 null 반환, 하나 이상이면 com.mysema.query.NonUniqueResultException 예외가 발생
- singleResult() : uniqueResult()와 같지만 결과가 하나 이상이면 처음 데이터를 반환
- list() : 결과가 하나 이상일 때 사용. 결과가 없을시 빈 컬렉션 반환
5. 페이징과 정렬
5-1. limit와 offset 사용하기
JPAQuery query = new JPAQuery(em);
QMember member = QMember.member;
query.from(member)
.where(member.age.gt(20))
.orderBy(member.name.desc(), member.age.asc())
.offset(10).limit(10)
.list(member);
// 페이징은 offset, limit를 적절히 조합해서 사용한다.
- limit : 최대 결과 수
- offset : 결과 시작행
5-2. restrict() 와 QueryModifiers 사용하기
QueryModifiers queryModifiers = new QueryModifiers(20L, 10L); // limit, offset
query.from(member)
.restrict(queryModifiers)
.list(member);
5-3. listResults()사용하기
실제 페이징 처리를 하려면 검색된 전체 데이터 수를 알아야하는데 list()대신 listResults()를 사용한다.
// listResults() 사용
SearchResults<Member> result =
query.from(member)
.where(member.age.gt(20))
.offset(0).limit(10)
.listResults(member);
long total = result.getTotal();
long limit = result.getLimit();
long offset = result.getOffset();
List<Member> resultList = result.getResults(); // 조회된 데이터
6. 조인
inner join, leftJoin, rightJoin, fullJoin을 사용할 수 있다. 추가로 JPQL의 on 성능 최적화를 위한 fetch조인도 사용할 수 있다.
join(조인 대상, 별칭으로 사용할 쿼리 타입)
JPAQuery query = new JPAQuery(em);
QMember member = QMember.member;
QTeam team = QTeam.team;
// 1.
query.from(member)
.join(member.team, team)
.list(member);
// 2.
query.from(member)
.join(member.team, team)
.on(member.age.gt(20)) // on 사용(join 조건)
.list(member);
// 3.
query.from(member)
.join(member.team, team).fetch()
.list(member);
// 1.
Hibernate:
select
member0_.id as id1_0_,
member0_.age as age2_0_,
member0_.name as name3_0_,
member0_.team_id as team_id4_0_
from
member member0_
inner join
team team1_
on member0_.team_id=team1_.id
// 2.
Hibernate:
select
member0_.id as id1_0_,
member0_.age as age2_0_,
member0_.name as name3_0_,
member0_.team_id as team_id4_0_
from
member member0_
inner join
team team1_
on member0_.team_id=team1_.id
and (
member0_.age>?
)
// 3.
Hibernate:
select
member0_.id as id1_0_0_,
team1_.id as id1_1_1_,
member0_.age as age2_0_0_,
member0_.name as name3_0_0_,
member0_.team_id as team_id4_0_0_,
team1_.team_name as team_nam2_1_1_
from
member member0_
inner join
team team1_
on member0_.team_id=team1_.id
7. 서브 쿼리
com.mysema.query.jpa.JPASubQuery를 생성해서 사용
unique() : 결과 한건일 때 사용
list() : 여러건일때 사용
QItem item = QItem.item;
QItem itemSub = new Qitem("itemSub");
query.from(item)
.where(item.price.eq(
new JPASubQuery().from(itemSub).unique(itemSub.price.max())
))
.list(item);
QItem item = QItem.item;
QItem itemSub = new Qitem("itemSub");
query.from(item)
.where(item.in(
new JPASubQuery().from(itemSub).where(item.name.eq(itemSub.name)).list(itemSub)
))
.list(item);
# 8\. 프로젝션과 결과 반환
프로젝션 : select 절에 조회 대상을 지정하는 것
프로젝션 대상이 하나
JPAQuery query = new JPAQuery(em);
QMember member = QMember.member;
List<String> resultList = query.from(member).list(member.name);
resultList.stream().forEach(System.out::println);
Hibernate:
select
member0_.name as col_0_0_
from
member member0_
여러 컬럼 반환과 튜플
프로젝션 대상으로 여러 필드를 선택하면 QueryDSL은 기본적으로 com.mysema.query.Tuple이라는 Map과 비슷한 내부 타입을 사용한다.
List<Tuple> result = query.from(member).list(member.name, member.age);
result.stream().forEach(tuple -> {
System.out.println("name : " + tuple.get(member.name));
System.out.println("age : " + tuple.get(member.age));
});
Hibernate:
select
member0_.name as col_0_0_,
member0_.age as col_1_0_
from
member member0_
빈 생성
쿼리 결과를 엔티티가 아닌 특정 객체로 받고싶을때 사용(mybatis resultMap과 비슷)
프로퍼티 접근
필드 직접 접근
생성자 사용
@Getter
@Setter
@AllArgsConstructor
public class MemberDTO {
private String memberName;
private int age;
}
List<MemberDTO> resultList =
query.from(member).list(
Projections.bean(MemberDTO.class, member.name.as("memberName"), member.age) // 프로퍼티 setter 접근
// Projections.fields(MemberDTO.class, member.name.as("memberName"), member.age) // 필드 직접 접근
// Projections.constructor(MemberDTO.class, member.name, member.age) // 생성자 사용
);
쿼리 결과는 name인데 MemberDTO는 memberName프로퍼티를 가지고 있다.
쿼리 결과와 매핑할 프로퍼티 이름이 다르면 as를 사용해서 별칭을 주면 된다.
9. 동적 쿼리
com.mysema.query.BooleanBuilder를 사용하면 특정 조건에 따른 동적 쿼리를 편리하게 생성할 수 있다.
JPAQuery query = new JPAQuery(em);
Member condition = new Member();
condition.setName("corn");
condition.setAge(30);
QMember member = QMember.member;
BooleanBuilder builder = new BooleanBuilder();
if(!StringUtils.isEmpty(condition.getName())){
builder.and(member.name.contains(condition.getName()));
}
if(condition.getAge() != null){
builder.and(member.age.gt(condition.getAge()));
}
if(!StringUtils.isEmpty(condition.getId())){
builder.and(member.id.eq(condition.getId()));
}
List<Member> resutList = query.from(member)
.where(builder)
.list(member);
Hibernate:
select
member0_.id as id1_0_,
member0_.age as age2_0_,
member0_.name as name3_0_,
member0_.team_id as team_id4_0_
from
member member0_
where
(
member0_.name like ? escape '!'
)
and member0_.age>?
'프로그래밍 노트 > JPA' 카테고리의 다른 글
[JPA] 영속성 관리_1 (EntityManager, EntityManagerFactory, PersistContext) (0) | 2019.09.03 |
---|---|
[JPA] 스프링 데이터 JPA 소개 (0) | 2019.09.02 |
[JPA] JPQL 문법, 내용정리 (0) | 2019.08.21 |
[JPA] JPQL이란? (0) | 2019.08.18 |
JPA, Hibernate, Spring Data JPA 구분하기 (차이점) (0) | 2019.07.23 |