@Test
public void simpleProjection() throws Exception {
List<String> result = queryFactory
.select(member.username)
.from(member)
.fetch();
for (String s : result) {
System.out.println("s = " + s);
}
}
List<String>
)com.querydsl.core.Tuple
@Test
public void tupleProjection() throws Exception {
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("tuple ="+ tuple); // tuple = [member1, 10]
System.out.println("age = " + age); //10
System.out.println("username = " + username); //member1
}
}
Tuple
은 Repository 계층 안에서 쓰는 정도는 괜찮지만 그 밖의 계층에서는 DTO로 변환해서 쓰는 것이 좋다.Tuple
도 결국 Querydsl에 종속적이기 때문이다.@Test
public void findDtoJPQL() throws Exception {
List<MemberDto> result = em.createQuery("select new study.querydsl.dto.MemberDto(m.username,m.age) from Member m", MemberDto.class)
.getResultList();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
@Test
public void findDtoBySetter() throws Exception {
List<MemberDto> result = queryFactory
.select(Projections.bean(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
@Test
public void findDtoByField() throws Exception {
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);
}
}
@Test
public void findUserDtoByField() throws Exception {
QMember memberSub = new QMember("memberSub");
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();
for (UserDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
as('name')
을 이용해서 해결한다.ExpressionUtils.as(source, alias)
: 필드나 서브 쿼리에 별칭 적용username.as("memberName")
: 필드에 별칭 적용@Test
public void findDtoByConstructor() throws Exception {
List<MemberDto> result = queryFactory
.select(Projections.constructor(MemberDto.class,
member.username,
member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
@QueryProjection
을 생성자에 붙혀준 뒤 gradle → tasks → other → compileQuerydsl을 통해 DTO도 Q 타입으로 생성해 준다. @Data
@NoArgsConstructor
public class MemberDto {
private String username;
private int age;
@QueryProjection
public MemberDto(String username, int age) {
this.username = username;
this.age = age;
}
}
./gradlew compileQuerydsl
QMemberDto
생성 확인@Test
public void findDtoByQueryProjection() throws Exception{
List<MemberDto> result = queryFactory
.select(new QMemberDto(member.username, member.age))
.from(member)
.fetch();
for (MemberDto memberDto : result) {
System.out.println("memberDto = " + memberDto);
}
}
@Test
public void distinct_test() throws Exception{
List<String> result = queryFactory
.select(member.username).distinct()
.from(member)
.fetch();
}
예전 순수 JPA에서 동적 쿼리를 해결하는 방법으로 3가지가 있었다.
위에서 queryDSL을 사용해서 동적 쿼리를 해결하는 방식은 자세히 들어가면 또 2가지 방식이 있다.
@Test
public void dynamicQuery_BooleanBuilder() throws Exception{
String usernameParam = "member1";
Integer ageParam = null;
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();
}
if
문을 통해 usernameCond, ageCond를 builder.and() 메서드를 통해 조건을 넣어주고 있다. @Test
public void dynamicQuery_whereParam() throws Exception{
String usernameParam = null;
Integer ageParam = null;
List<Member> result = searchMember2(usernameParam, ageParam);
assertThat(result.size()).isEqualTo(1);
}
private List<Member> searchMember2(String usernameCond, Integer ageCond) {
return queryFactory
.selectFrom(member)
.where(usernameEq(usernameCond), ageEq(ageCond))
.fetch();
}
private BooleanExpression usernameEq(String usernameCond) {
return usernameCond != null ? member.username.eq(usernameCond):null;
}
private BooleanExpression ageEq(Integer ageCond) {
return ageCond != null ? member.age.eq(ageCond):null;
}
where
조건에 null
값은 무시된다.usernameEq()
과 같은 메서드는 다른 쿼리에서 재활용할 수도 있다.private Predicate allEq(String usernameCond, Integer ageCond){
return usernameEq(usernameCond).and(ageEq(ageCond));
}
method chaining
이 가능하다.null
체크는 주의해서 처리해야 한다.@Test
public void bulkUpdate() throws Exception{
// member1(10), member2(20) -> 비회원
// member3(30), member4(40) -> member3, member4
long count = queryFactory
.update(member)
.set(member.username, "비회원")
.where(member.age.lt(28))
.execute();
em.flush();
em.clear();
List<Member> result = queryFactory
.selectFrom(member)
.fetch();
for (Member member1 : result) {
System.out.println("member1 = " + member1);
}
}
@Test
public void bulkAdd() throws Exception{
long count = queryFactory
.update(member)
.set(member.age, member.age.add(1))
.execute();
}
member.age.multiply(x)
를 사용.member.age.add(-1)
를 사용.@Test
public void buldDelete() throws Exception{
long count = queryFactory
.delete(member)
.where(member.age.lt(18))
.execute();
}
✅ JPQL 배치와 마찬가지로, 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 배치 쿼리를
실행하고 나면 영속성 컨텍스트를 초기화하는 것이 안전하다.
✅ SQL function은 JPA와 같이 Dialect에 등록된 내용만 호출할 수 있다.
String result = queryFactory
.select(Expressions.stringTemplate("function('replace', {0}, {1},
{2})", member.username, "member", "M"))
.from(member)
.fetchFirst();
String result = queryFactory
.select(member.username)
.from(member)
.where(member.username.eq(Expressions.stringTemplate("function('lower', {0})",
member.username)))
lower 같은 ansi 표준 함수들은 querydsl이 상당 부분 내장하고 있다. 따라서 다음과 같이 처리해도
결과는 같다.
.where(member.username.eq(member.username.lower()))