중급자를 위해 준비한
[백엔드, 웹 개발] 강의입니다.
Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!
✍️
이런 걸
배워요!
Querydsl을 기초부터 실무활용까지 한번에 배울 수 있습니다.
단순한 기능 설명을 넘어 실무활용 노하우를 배울 수 있습니다.
JPA를 사용할 때 동적 쿼리와 복잡한 쿼리 문제를 해결할 수 있습니다.
복잡한 쿼리, 동적 쿼리는 이제 안녕!
Querydsl로 자바 백엔드 기술을 단단하게.
???? 본 강의는 로드맵 과정입니다.
- 본 강의는 자바 백엔드 개발의 실전 코스를 완성하는 마지막 강의입니다. 스프링 부트와 JPA 실무 완전 정복 로드맵을 우선 확인해주세요. (링크)
강좌
https://www.inflearn.com/course/querydsl-실전#
강의자료
https://github.com/braverokmc79/jpa-basic-lecture-file2
소스 :
https://github.com/braverokmc79/jpa-querydsl
[5] 중급 문법
22.프로젝션과 결과 반환 - 기본
강의 :
https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30136&tab=curriculum
프로젝션:
select 대상 지정 프로젝션 대상이 하나
@Test public void simpleProjection(){ List<String> result = queryFactory .select(member.username) .from(member) .fetch(); for (String s : result) { System.out.println("s = " + s); } }
쿼리출력
/* select member1.username from Member member1 */ select member0_.username as col_0_0_ from member member0_
프로젝션 대상이 하나면 타입을 명확하게 지정할 수 있음
프로젝션 대상이 둘 이상이면 튜플이나 DTO로 조회
튜플 조회
프로젝션 대상이 둘 이상일 때 사용
com.querydsl.core.Tuple
@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); } }
쿼리출력
/* select member1.username, member1.age from Member member1 */ select member0_.username as col_0_0_, member0_.age as col_1_0_ from member member0_
=>
username = member1 age = 10 username = member2 age = 20 username = member3 age = 30 username = member4 age = 40
★23.프로젝션과 결과 반환 - DTO 조회★
강의 :
https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30137&tab=curriculum
1) 순수 JPA에서 DTO 조회
MemberDto
package study.querydsl.dto; import lombok.Data; @Data @NoArgsConstructor public class MemberDto { private String username; private int age; public MemberDto(String username, int age){ this.username =username; this.age=age; } }
@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); } }
쿼리 출력
/* select new study.querydsl.dto.MemberDto(m.username, m.age) from Member m */ select member0_.username as col_0_0_, member0_.age as col_1_0_ from member member0_
순수 JPA에서 DTO를 조회할 때는 new 명령어를 사용해야함
DTO의 package이름을 다 적어줘야해서 지저분함
생성자 방식만 지원함
2) Querydsl 빈 생성(Bean population)
결과를 DTO 반환할 때 사용
다음 4가지 방법 지원
프로퍼티 접근
필드 직접 접근
생성자 사용
1. 프로퍼티 접근 - Setter
@Test public void findDtoBySetter(){ 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); } }
memberDto = MemberDto(username=member1, age=10) memberDto = MemberDto(username=member2, age=20) memberDto = MemberDto(username=member3, age=30) memberDto = MemberDto(username=member4, age=40)
2. 필드 직접 접근
@Test public void findDtoByField(){ 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); } }
3. 생성자 방식
/** * 생성자 방식 * **/ @Test public void findDtoByConstructor(){ 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); } }
4. 별칭이 다를 때
@Test public void findUserDto(){ QMember memberSub =new QMember("memberSub"); List<UserDto> fetch = 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 userDto : fetch) { System.out.println("userDto = " + userDto); } }
프로퍼티나, 필드 접근 생성 방식에서 이름이 다를 때 해결 방안
ExpressionUtils.as(source,alias) : 필드나, 서브 쿼리에 별칭 적용
username.as("memberName") : 필드에 별칭 적용
★★★24.프로젝션과 결과 반환 - @QueryProjection
(대부분 이 방식을 사용한다 )
강의 :
https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30138&tab=curriculum
1) 생성자 + @QueryProjection
package study.querydsl.dto; import com.querydsl.core.annotations.QueryProjection; import lombok.AccessLevel; import lombok.Data; import lombok.NoArgsConstructor; @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 생성 확인
2) @QueryProjection 활용
@Test public void findDtoByQueryProjection(){ List<MemberDto> fetch = queryFactory .select(new QMemberDto(member.username, member.age)) .from(member) .fetch(); for (MemberDto memberDto : fetch) { System.out.println("memberDto = " + memberDto); } }
이 방법은 컴파일러로 타입을 체크할 수 있으므로 가장 안전한 방법이다. 다만 DTO에 QueryDSL
어노테이션을 유지해야 하는 점과 DTO까지 Q 파일을 생성해야 하는 단점이 있다.
distinct
List<String> result = queryFactory .select(member.username).distinct() .from(member) .fetch();
참고: distinct는 JPQL의 distinct와 같다.
distinct 에 대해서는 다음 페이지를 참조
자바 ORM 표준 JPA 프로그래밍 - 10. 객체지향 쿼리 언어2 - 중급 문법 ★ 페치 조인(Fetch Join)
https://macaronics.net/index.php/m01/spring/view/2054
★★★25.동적 쿼리 - BooleanBuilder 사용
강의 :
https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30139&tab=curriculum
동적 쿼리를 해결하는 두가지 방식
1) BooleanBuilder
2 ) Where 다중 파라미터 사용
1) BooleanBuilder
@Test public void dynamicQuery_BooleanBuilder(){ String usernameParam ="member1"; Integer ageParam =null; List<Member> result=searchMember1(usernameParam, ageParam); Assertions.assertThat(result.size()).isEqualTo(1); } private List<Member> searchMember1(String usernameCond, Integer ageCond) { /** username 이 필수값이아여야 할경우 다음과 같이 BooleanBuilder 생성자 설정 BooleanBuilder builder =new BooleanBuilder(member.username.eq(usernameCond)); */ BooleanBuilder builder =new BooleanBuilder(member.username.eq(usernameCond)); 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(); }
★★★★★
26.동적 쿼리 - Where 다중 파라미터 사용
강의 :
https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30140&tab=curriculum
코드가 깔끔하게 이해하기 가 쉽다.
@Test public void dynamicQuery_WhereParam(){ String usernameParam ="member1"; Integer ageParam =null; List<Member> result=searchMember2(usernameParam, ageParam); Assertions.assertThat(result.size()).isEqualTo(1); } private List<Member> searchMember2(String usernameCond, Integer ageCond){ //** where 조건에서 null 이면 무시한다. return queryFactory .selectFrom(member) // .where(usernameEq(usernameCond), ageEq(ageCond)) .where(allEq(usernameCond, 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; } /** * 다음과 같이 조합할 수 있다. */ private BooleanExpression allEq(String usernameCond, Integer ageCond){ return usernameEq(usernameCond).and(ageEq(ageCond)); }
where 조건에 null 값은 무시된다.
메서드를 다른 쿼리에서도 재활용 할 수 있다.
쿼리 자체의 가독성이 높아진다.
조합 가능
private BooleanExpression allEq(String usernameCond, Integer ageCond) { return usernameEq(usernameCond).and(ageEq(ageCond)); }
null 체크는 주의해서 처리해야함
27.수정, 삭제 벌크 연산
강의 :
https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30141&tab=curriculum
쿼리 한번으로 대량 데이터 수정
@Test @Commit public void bulkUpdate(){ //member1 =10 -> DB member1 //member2 =20 -> DB member2 //member3 =30 -> DB member3 //member4 =40 -> DB member4 long count = queryFactory .update(member) .set(member.username, "비회원") .where(member.age.lt(28)) .execute(); em.flush(); em.clear(); //member1 =10 -> DB 비회원 //member2 =20 -> DB 비회원 //member3 =30 -> DB member3 //member4 =40 -> DB member4 List<Member> result = queryFactory.selectFrom(member).fetch(); for (Member member1 : result) { System.out.println("member1 = " + member1); } }
/** * 벌크연산 빼기 - 로 */ @Test public void bulkMinus(){ long count =queryFactory .update(member) .set(member.age, member.age.add(-1)) .execute(); } /** * 벌크 연산 곱하기 */ @Test public void bulkMultiple(){ long count =queryFactory .update(member) .set(member.age, member.age.multiply(2)) .execute(); } /** * 벌크 연산 삭제 */ @Test public void bulkDelete(){ queryFactory .delete(member) .where(member.age.gt(18)) .execute(); }
주의:
JPQL 배치와 마찬가지로, 영속성 컨텍스트에 있는 엔티티를 무시하고 실행되기 때문에 배치 쿼리를
실행하고 나면 영속성 컨텍스트를 초기화 하는 것이 안전하다.
29.SQL function 호출하기
강의 :
https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30142&tab=curriculum
member M으로 변경하는 replace 함수 사용
@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); } }
소문자로 변경해서 비교해라
@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); } }
lower 같은 ansi 표준 함수들은 querydsl이 상당부분 내장하고 있다. 따라서 다음과 같이 처리해도 결과는 같다.
.where(member.username.eq(member.username.lower()))
댓글 ( 4)
댓글 남기기