Spring/QueryDSL

프로젝션 결과 반환 - DTO 조회

느리지만 꾸준하게 2022. 4. 21. 00:40

먼저 순수 JPA에서 DTO로 조회하는 방법을 알아보자.

// MemberDto

package study.querydsl.dto;


import lombok.Data;

@Data
public class MemberDto {


    // username이랑 age 두개만 최적화해서 가져오고 싶다해서 MemberDto를 만들어줌
    private String username;
    private int age;

    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }

}

 

Test에서 JPQL에서는 아래와 같이 짜야 한다.

// QuerydslBasicTest

@Test
    public void findDtoByJPQL() {
        // 생성자 처럼 MemberDto를 생성하는 것처럼 하고 생성자의 값들이 넘어온다. => new operation을 활용하는 방법
        // new 해서 패키지명 다 적고 Dto 명 적고 생성자를 호출하는 것처럼 생긴 이 문법이 JPQL에서 제공하는 new Operation 문법
        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);
        }
    }
/* select
        new study.querydsl.dto.MemberDto(m.username,
        m.age) 
    from
        Member m



// sql을 보면 username age 2개만 조회해주는 것을 확인할 수 있다.
select
            member0_.username as col_0_0_,
            member0_.age as col_1_0_ 
        from
            member member0_


// member1234 4개 보여주고 
memberDto = MemberDto(username=member1, age=10)
memberDto = MemberDto(username=member2, age=20)
memberDto = MemberDto(username=member3, age=30)
memberDto = MemberDto(username=member4, age=40)
  • 순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야 한다.
  • DTO의 package 이름을 다 적어줘야해서 지저분하고
  • 생성자 방식만 지원해준다. setter를 넣거나 필드에 바로 값을 넣는 것은 안되고 생성자가 꼭 있어야 한다.

 

여기서 Querydsl은 결과를 DTO 반환할 때 3가지 방법을 지원해주느데

  • 프로퍼티 접근을 지원해주고
  • 필드 직접 접근
  • 생성자 사용을 지원해준다.
// QueryBasicTest

@Test
public void findDtoBySetter() {
    List<MemberDto> result = queryFactory
            // select에 프로젝션스가 들어간다.
            .select(Projections.bean(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    // DTO 조회하는게 간편하다.
    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}

test를 돌리면 아래와 같은 에러가 나오는데

com.querydsl.core.types.ExpressionException: study.querydsl.dto.MemberDto

	at com.querydsl.core.types.QBean.newInstance(QBean.java:246)
	at com.querydsl.jpa.FactoryExpressionTransformer.transformTuple(FactoryExpressionTransformer.java:51)
	at org.hibernate.hql.internal.HolderInstantiator.instantiate(HolderInstantiator.java:85)
	at org.hibernate.loader.hql.QueryLoader.getResultList(QueryLoader.java:508)
	at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2682)
	at org.hibernate.loader.Loader.list(Loader.java:2677)
	at org.hibernate.loader.hql.QueryLoader.list(QueryLoader.java:540)
	at org.hibernate.hql.internal.ast.QueryTranslatorImpl.list(QueryTranslatorImpl.java:400)
	at org.hibernate.engine.query.spi.HQLQueryPlan.performList(HQLQueryPlan.java:219)
	at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1468)
	at org.hibernate.query.internal.AbstractProducedQuery.doList(AbstractProducedQuery.java:1649)
	at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1617)
	at org.hibernate.query.Query.getResultList(Query.java:165)
	at com.querydsl.jpa.impl.AbstractJPAQuery.getResultList(AbstractJPAQuery.java:191)
	at com.querydsl.jpa.impl.AbstractJPAQuery.fetch(AbstractJPAQuery.java:243)
	at study.querydsl.entity.QuerydslBasicTest.findDtoBySetter(QuerydslBasicTest.java:624)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:107)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: java.lang.InstantiationException: study.querydsl.dto.MemberDto
	at java.base/java.lang.Class.newInstance(Class.java:571)
	at com.querydsl.core.types.QBean.create(QBean.java:251)
	at com.querydsl.core.types.QBean.newInstance(QBean.java:222)
	... 84 more
Caused by: java.lang.NoSuchMethodException: study.querydsl.dto.MemberDto.<init>()
	at java.base/java.lang.Class.getConstructor0(Class.java:3349)
	at java.base/java.lang.Class.newInstance(Class.java:556)
	... 86 more

 

MemberDto에서 기본 생성자를 만들어준다. 두가지 방법이 있다.

querydsl이 memberdto를 만든다음에 값을 set set 해줘야 하고

만드는 과정에서 기본 생성자를 호출해야하는데 기본 생성자가 없으니까 오류가 난것이다.

@Data
public class MemberDto {


    // username이랑 age 두개만 최적화해서 가져오고 싶다해서 MemberDto를 만들어줌
    private String username;
    private int age;

    
    // 디폴트 생성자 하나를 만들어주고
    public MemberDto() {
    }

    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }

}
@Data
@NoArgsConstructor
public class MemberDto {


    // username이랑 age 두개만 최적화해서 가져오고 싶다해서 MemberDto를 만들어줌
    private String username;
    private int age;

    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }

}

수정해주고 test를 돌리면 실행 결과가 잘 나온다. (프로퍼티 접근방법 )

/* select
        member1.username,
        member1.age 
    from
        Member member1 */ select
            member0_.username as col_0_0_,
            member0_.age as col_1_0_ 
        from
            member member0_


memberDto = MemberDto(username=member1, age=10)
memberDto = MemberDto(username=member2, age=20)
memberDto = MemberDto(username=member3, age=30)
memberDto = MemberDto(username=member4, age=40)

 

이제 필드를 활용한 방법을 알아보자.

// 필드를 활용한 방법
// getter setter 없어도 된다. => 바로 필드에다가 값을 꽂아버린다.
@Test
public void findDtoByField() {
    List<MemberDto> result = queryFactory
            // select에 프로젝션스가 들어간다.
            .select(Projections.fields(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    // DTO 조회하는게 간편하다.
    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}

 

 

필드에다가 바로 값을 꽂아버린다.

// MemberDto 필드

@Data
@NoArgsConstructor
public class MemberDto {


    // username이랑 age 두개만 최적화해서 가져오고 싶다해서 MemberDto를 만들어줌
    private String username;
    private int age;

    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }

}
memberDto = MemberDto(username=member1, age=10)
memberDto = MemberDto(username=member2, age=20)
memberDto = MemberDto(username=member3, age=30)
memberDto = MemberDto(username=member4, age=40)

 

 

첫번째 beans 방식은

@Test
public void findDtoBySetter() {
    List<MemberDto> result = queryFactory
            // select에 프로젝션스가 들어간다.
            .select(Projections.bean(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    // DTO 조회하는게 간편하다.
    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}

getter setter를 통해서 값이 생성이 된 것이고

// MemberDto

@Data
@NoArgsConstructor
public class MemberDto {


    // username이랑 age 두개만 최적화해서 가져오고 싶다해서 MemberDto를 만들어줌
    private String username;
    private int age;

    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

 

 

두번째 findDtoByField방식은 값이 필드에 다 꽂힌다.

// 필드를 활용한 방법
// getter setter 없어도 된다. => 바로 필드에다가 값을 꽂아버린다.
@Test
public void findDtoByField() {
    List<MemberDto> result = queryFactory
            // select에 프로젝션스가 들어간다.
            .select(Projections.fields(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    // DTO 조회하는게 간편하다.
    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}
// MemberDto 필드

@Data
@NoArgsConstructor
public class MemberDto {


    // username이랑 age 두개만 최적화해서 가져오고 싶다해서 MemberDto를 만들어줌
    private String username;
    private int age;

    public MemberDto(String username, int age) {
        this.username = username;
        this.age = age;
    }

}

 

 

 

그 다음 방식은 생성자 접근 방법인데 아래와 같다.

@Test
public void findDtoByConstructor() {
    // member의 username과 age랑 타입을 맞춰야 한다.
    List<MemberDto> result = queryFactory
            .select(Projections.constructor(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    // DTO 조회하는게 간편하다.
    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}

 

원하는 결과값이 나오게 된다. 

 /* select
        member1.username,
        member1.age 
    from
        Member member1 */ select
            member0_.username as col_0_0_,
            member0_.age as col_1_0_ 
        from
            member member0_


// MemberDto에서 @Data를 넣어줘서 아래와 같이 나오고

memberDto = MemberDto(username=member1, age=10)
memberDto = MemberDto(username=member2, age=20)
memberDto = MemberDto(username=member3, age=30)
memberDto = MemberDto(username=member4, age=40)

 

 

이제는 임의로 UserDto를 하나 만들어보자.

package study.querydsl.dto;


import lombok.Data;

@Data
public class UserDto {

    private String name;

    private int age;
}



test에서 userdto를 만들어서 실행결과를 확인하면

@Test
public void findUserDto() {
    List<UserDto> result = queryFactory
            // member에서 userdto로 member를 조회를 하고 싶은데 이름이 안맞다.
            .select(Projections.fields(UserDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    for (UserDto userDto : result) {
        System.out.println("userDto = " + userDto);
    }
}

 

아래의 결과값이 null이 나오게 된다. 

userDto = UserDto(name=null, age=10)
userDto = UserDto(name=null, age=20)
userDto = UserDto(name=null, age=30)
userDto = UserDto(name=null, age=40)

 

그러면 .as해서 "name"을 넣어주면

@Test
public void findUserDto() {
    List<UserDto> result = queryFactory
            // member에서 userdto로 member를 조회를 하고 싶은데 이름이 안맞다.
            .select(Projections.fields(UserDto.class,
                    member.username.as("name"),
                    member.age))
            .from(member)
            .fetch();

    for (UserDto userDto : result) {
        System.out.println("userDto = " + userDto);
    }
}

 

결과가 잘 나오게 된다. 

userDto = UserDto(name=member1, age=10)
userDto = UserDto(name=member2, age=20)
userDto = UserDto(name=member3, age=30)
userDto = UserDto(name=member4, age=40)

 

 

 

즉 as해서 dto에 있는 값을 넣어주면 된다.

// UserDto

package study.querydsl.dto;


import lombok.Data;

@Data
public class UserDto {

    private String name;

    private int age;
}

 

 

 

서브쿼리를 이용해서도 작성해보자. Querydsl의 서브쿼리에 자세한 포스팅은 여기를 참고하고 학습하자.

@Test
public void findUserDto() {
    QMember memberSub = new QMember("memberSub");
    List<UserDto> result = queryFactory
            // Projections.fields에서 UserDto를 조회하는데
            .select(Projections.fields(UserDto.class,
                    // 첫번째는 name으로 되었고
                    member.username.as("name"),
                    // 두번째는 서브쿼리를 썼다.
                    ExpressionUtils.as(JPAExpressions
                            // 나이가 최대인거를 select의 서브쿼리로 씀
                            // 회원 나이의 max값으로만 서브쿼리를 해서 오른쪽에는 10 20 30 40 이 아니고 최대나이 40으로 찍는 것이다.
                            .select(memberSub.age.max())
                            // 서브쿼리의 결과가 매핑이 된다.
                            .from(memberSub), "age")
            ))
            .from(member)
            .fetch();

    for (UserDto userDto : result) {
        System.out.println("userDto = " + userDto);
    }
}

서브쿼리의 결과는 UserDto에 매칭이 된다.

package study.querydsl.dto;


import lombok.Data;

@Data
public class UserDto {

    private String name;

    private int age;
}

 

결과가 잘 나오게 된다.

userDto = UserDto(name=member1, age=40)
userDto = UserDto(name=member2, age=40)
userDto = UserDto(name=member3, age=40)
userDto = UserDto(name=member4, age=40)

 

 

이 부분도 

member.username.as("name"),

expressionutils를 써도된다. 지저분하다. 첫번째 방법을 쓰자. 서브쿼리 같은 경우는 expressionutils로 감싸야 한다.

ExpressionUtils
@Test
public void findUserDto() {
    QMember memberSub = new QMember("memberSub");
    List<UserDto> result = queryFactory
            // Projections.fields에서 UserDto를 조회하는데
            .select(Projections.fields(UserDto.class,
                    // 첫번째는 name으로 되었고

                    ExpressionUtils.as(member.username, "name"),
                    // 두번째는 서브쿼리를 썼다.
                    ExpressionUtils.as(JPAExpressions
                            // 나이가 최대인거를 select의 서브쿼리로 씀
                            // 회원 나이의 max값으로만 서브쿼리를 해서 오른쪽에는 10 20 30 40 이 아니고 최대나이 40으로 찍는 것이다.
                            .select(memberSub.age.max())
                            // 서브쿼리의 결과가 매핑이 된다.
                            .from(memberSub), "age")
            ))
            .from(member)
            .fetch();
userDto = UserDto(name=member1, age=40)
userDto = UserDto(name=member2, age=40)
userDto = UserDto(name=member3, age=40)
userDto = UserDto(name=member4, age=40)

 

 

 

생성자 constructor에서 userdto로 바꿔서 돌려보자.

@Test
public void findDtoByConstructor() {
    // member의 username과 age랑 타입을 맞춰야 한다.
    List<UserDto> result = queryFactory
            .select(Projections.constructor(UserDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    // DTO 조회하는게 간편하다.
    for (UserDto UserDto : result) {
        System.out.println("UserDto = " + UserDto);
    }
}

에러가 난다. UserDto에서 constructor를 만들어 주자. 그리고 다시 돌려주면 정상작동 한다.

// UserDto

package study.querydsl.dto;


import lombok.Data;

@Data
public class UserDto {

    private String name;
    private int age;
	
    // default 생성자도 함께 만들어준다.
    public UserDto() {
    }

    public UserDto(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

 

결과가 잘 나온다.

/* select
        member1.username,
        member1.age 
    from
        Member member1 */ select
            member0_.username as col_0_0_,
            member0_.age as col_1_0_ 
        from
            member member0_


UserDto = UserDto(name=member1, age=10)
UserDto = UserDto(name=member2, age=20)
UserDto = UserDto(name=member3, age=30)
UserDto = UserDto(name=member4, age=40)

 

 

 

정리해보자.

  • setter를 통한 방식
@Test
public void findDtoBySetter() {
    List<MemberDto> result = queryFactory
            // select에 프로젝션스가 들어간다.
            .select(Projections.bean(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    // DTO 조회하는게 간편하다.
    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}
  • 필드에 바로 injection하는 방식
// 필드를 활용한 방법
// getter setter 없어도 된다. => 바로 필드에다가 값을 꽂아버린다.
@Test
public void findDtoByField() {
    List<MemberDto> result = queryFactory
            // select에 프로젝션스가 들어간다.
            .select(Projections.fields(MemberDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    // DTO 조회하는게 간편하다.
    for (MemberDto memberDto : result) {
        System.out.println("memberDto = " + memberDto);
    }
}

 

  • 생성자 constructor로 해주는 방식
@Test
public void findDtoByConstructor() {
    // member의 username과 age랑 타입을 맞춰야 한다.
    List<UserDto> result = queryFactory
            .select(Projections.constructor(UserDto.class,
                    member.username,
                    member.age))
            .from(member)
            .fetch();

    // DTO 조회하는게 간편하다.
    for (UserDto UserDto : result) {
        System.out.println("UserDto = " + UserDto);
    }
}

 

setter나 필드같은경우 이름 매칭하는것이 굉장히 중요하기 때문에 as("name")을 썼고 

복잡할 때는 ExpressionUtils로 alias를 줄 수가 있고 매칭을 해서 값을 넣어줄 수가 있다.

@Test
public void findUserDto() {
    QMember memberSub = new QMember("memberSub");
    List<UserDto> result = queryFactory
            // Projections.fields에서 UserDto를 조회하는데
            .select(Projections.fields(UserDto.class,
                    // 첫번째는 name으로 되었고
                    member.username.as("name"),
                    // 두번째는 서브쿼리를 썼다.
                    ExpressionUtils.as(JPAExpressions
                            // 나이가 최대인거를 select의 서브쿼리로 씀
                            // 회원 나이의 max값으로만 서브쿼리를 해서 오른쪽에는 10 20 30 40 이 아니고 최대나이 40으로 찍는 것이다.
                            .select(memberSub.age.max())
                            // 서브쿼리의 결과가 매핑이 된다.
                            .from(memberSub), "age")
            ))
            .from(member)
            .fetch();

    for (UserDto userDto : result) {
        System.out.println("userDto = " + userDto);
    }
}

 

 

 

 

 

 

 

 

 

<출처 김영한: 실전! Querydsl >

https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84/dashboard

 

실전! Querydsl - 인프런 | 강의

Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, - 강의 소개 | 인프런...

www.inflearn.com

 

'Spring > QueryDSL' 카테고리의 다른 글

동적 쿼리 - BooleanBuilder  (0) 2022.04.21
프로젝션과 결과 반환 - @QueryProjection  (0) 2022.04.21
프로젝션과 결과 반환 - 기본  (0) 2022.04.20
Case 문 & 상수, 문자 더하기  (0) 2022.04.20
서브 쿼리  (0) 2022.04.20