중급자를 위해 준비한
[백엔드, 웹 개발] 강의입니다.
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
[6] 실무 활용 - 순수 JPA와 Querydsl
30.순수 JPA 리포지토리와 Querydsl
강의 :
https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30144&tab=curriculum
1) 순수 JPA 리포지토리와 Querydsl
2) 동적쿼리 Builder 적용
3) 동적쿼리 Where 적용
4) 조회 API 컨트롤러 개발
순수 JPA 리포지토리
package study.querydsl.repository; import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQueryFactory; import org.springframework.stereotype.Repository; import study.querydsl.dto.MemberSearchCondition; import study.querydsl.dto.MemberTeamDto; import study.querydsl.dto.QMemberTeamDto; import study.querydsl.entity.Member; import javax.persistence.EntityManager; import java.util.List; import java.util.Optional; import static org.springframework.util.StringUtils.hasText; import static org.springframework.util.StringUtils.isEmpty; import static study.querydsl.entity.QMember.member; import static study.querydsl.entity.QTeam.team; @Repository public class MemberJpaRepository { private final EntityManager em; private final JPAQueryFactory queryFactory; public MemberJpaRepository(EntityManager em) { this.em = em; this.queryFactory = new JPAQueryFactory(em); } public void save(Member member) { em.persist(member); } public Optional<Member> findById(Long id) { Member findMember = em.find(Member.class, id); return Optional.ofNullable(findMember); } public List<Member> findAll() { return em.createQuery("select m from Member m", Member.class) .getResultList(); } public List<Member> findByUsername(String username) { return em.createQuery("select m from Member m where m.username = :username", Member.class) .setParameter("username", username) .getResultList(); } }
순수 JPA 리포지토리 테스트
ingframework.transaction.annotation.Transactional; import study.querydsl.dto.MemberSearchCondition; import study.querydsl.dto.MemberTeamDto; import study.querydsl.entity.Member; import study.querydsl.entity.Team; import javax.persistence.EntityManager; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest @Transactional class MemberJpaRepositoryTest { @Autowired EntityManager em; @Autowired MemberJpaRepository memberJpaRepository; @Test public void basicTest() { Member member = new Member("member1", 10); memberJpaRepository.save(member); Member findMember = memberJpaRepository.findById(member.getId()).get(); assertThat(findMember).isEqualTo(member); List <Member> result1 = memberJpaRepository.findAll(); assertThat(result1).containsExactly(member); List <Member> result2 = memberJpaRepository.findByUsername("member1"); assertThat(result2).containsExactly(member); }
Querydsl 사용
public List <Member> findAll_Querydsl() { return queryFactory .selectFrom(member).fetch(); } public List <Member> findByUsername_Querydsl(String username) { return queryFactory .selectFrom(member) .where(member.username.eq(username)) .fetch(); }
JPAQueryFactory 스프링 빈 등록 다음과 같이
JPAQueryFactory 를 스프링 빈으로 등록해서 주입받아 사용해도 된다.
@Bean public JPAQueryFactory queryFactory(EntityManager em) { return new JPAQueryFactory(em); }
또는
package study.querydsl; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.persistence.EntityManager; @Configuration @RequiredArgsConstructor public class AppConfig { private final EntityManager em; @Bean public JPAQueryFactory queryFactory() { return new JPAQueryFactory(em); } }
참고: 동시성 문제는 걱정하지 않아도 된다. 왜냐하면 여기서 스프링이 주입해주는 엔티티 매니저는 실제
동작 시점에 진짜 엔티티 매니저를 찾아주는 프록시용 가짜 엔티티 매니저이다. 이 가짜 엔티티 매니저는
실제 사용 시점에 트랜잭션 단위로 실제 엔티티 매니저(영속성 컨텍스트)를 할당해준다.
> 더 자세한 내용은 자바 ORM 표준 JPA 책 13.1 트랜잭션 범위의 영속성 컨텍스트를 참고하자.
31.동적 쿼리와 성능 최적화 조회 - Builder 사용
강의 :
https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30145&tab=curriculum
Builder를 사용한 예제
MemberTeamDto
package study.querydsl.dto; import com.querydsl.core.annotations.QueryProjection; import lombok.Data; @Data public class MemberTeamDto { private Long memberId; private String username; private int age; private Long teamId; private String teamName; @QueryProjection public MemberTeamDto(Long memberId, String username, int age, Long teamId, String teamName) { this.memberId = memberId; this.username = username; this.age = age; this.teamId = teamId; this.teamName = teamName; } }
MemberSearchCondition
package study.querydsl.dto; import lombok.Data; @Data public class MemberSearchCondition { //회원명, 팀명, 나이(ageGoe, ageLoe) private String username; private String teamName; private Integer ageGoe; private Integer ageLoe; }
1) Where절에 파라미터를 사용한 예제
public List<MemberTeamDto> searchByBuilder(MemberSearchCondition condition){ BooleanBuilder builder =new BooleanBuilder(); if (hasText(condition.getUsername())) { builder.and(member.username.eq(condition.getUsername())); } if (hasText(condition.getTeamName())) { builder.and(team.name.eq(condition.getTeamName())); } if(condition.getAgeGoe() !=null){ builder.and(member.age.goe(condition.getAgeGoe())); } if(condition.getAgeLoe() !=null){ builder.and(member.age.loe(condition.getAgeLoe())); } return queryFactory .select(new QMemberTeamDto( member.id.as("memberId"), member.username, member.age, team.id.as("teamId"), team.name.as("teamName") )) .from(member) .leftJoin(member.team, team) .where(builder) .fetch(); }
오류 정정
> 강의 영상에서는 member.id.as("memberId") 라고 적었는데, QMemberTeamDto 는 생성자를 사용하기
때문에 필드 이름을 맞추지 않아도 된다. 따라서 member.id 만 적으면 된다.
조회 예제 테스트
@Test public void searchTest(){ Team teamA = new Team("teamA"); Team teamB=new Team("teamB"); em.persist(teamA); em.persist(teamB); Member member1=new Member("member1", 10, teamA); Member member2=new Member("member2", 20, teamA); Member member3=new Member("member3", 30, teamB); Member member4=new Member("member4", 40, teamB); em.persist(member1); em.persist(member2); em.persist(member3); em.persist(member3); em.persist(member4); MemberSearchCondition condition=new MemberSearchCondition(); condition.setAgeGoe(35); condition.setAgeLoe(40); condition.setTeamName("teamB"); List<MemberTeamDto> result = memberJpaRepository.searchByBuilder(condition); Assertions.assertThat(result).extracting("username").containsExactly("member4"); }
출력 쿼리
/* select member1.id as memberId, member1.username, member1.age, team.id as teamId, team.name as teamName from Member member1 left join member1.team as team where team.name = ?1 and member1.age >= ?2 and member1.age <= ?3 */ select member0_.member_id as col_0_0_, member0_.username as col_1_0_, member0_.age as col_2_0_, team1_.team_id as col_3_0_, team1_.name as col_4_0_ from member member0_ left outer join team team1_ on member0_.team_id=team1_.team_id where team1_.name=? and member0_.age>=? and member0_.age<=?
백기선 강좌 반드시 참조 : ★★★ 38.스프링 데이터 JPA: Projection ★★★
package com.example.demojap3.comment; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Data; /** * 클래스방식 커스텀 */ @Data @AllArgsConstructor(access = AccessLevel.PROTECTED) public class CommentSummary2 { private String comment; private int up; private int down; public String getVotes(){ return getUp() + " : " +getDown(); } }
package com.example.demojap3.comment; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; import java.util.Optional; public interface CommentRepository extends JpaRepository<Comment, Long> { /** * ==> 다음과 같이 제네릭으로 변경 */ <T>List<T> findByPost_Id(Long id, Class<T> type); }
★32.동적 쿼리와 성능 최적화 조회 - Where절 파라미터 사용
강의 :
https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30146&tab=curriculum
public List<MemberTeamDto> search(MemberSearchCondition condition){ return queryFactory .select(new QMemberTeamDto( member.id.as("memberId"), member.username, member.age, team.id.as("teamId"), team.name.as("teamName") )) .from(member) .leftJoin(member.team, team) .where( usernameEq(condition.getUsername()), teamNameEq(condition.getTeamName()), ageGoeEq(condition.getAgeGoe()), ageLoeEq(condition.getAgeLoe()) ) .fetch(); } private BooleanExpression usernameEq(String username) { return hasText(username) ? member.username.eq(username) :null; } private BooleanExpression teamNameEq(String teamName) { return hasText(teamName) ? team.name.eq(teamName) :null; } private BooleanExpression ageGoeEq(Integer ageGoe) { return ageGoe!=null? member.age.goe(ageGoe) : null; } private BooleanExpression ageLoeEq(Integer ageLoe) { return ageLoe!=null? member.age.loe(ageLoe) : null; }
참고: where 절에 파라미터 방식을 사용하면 조건 재사용 가능
//where 파라미터 방식은 이런식으로 재사용이 가능하다. public List < Member > findMember(MemberSearchCondition condition) { return queryFactory .selectFrom(member) .leftJoin(member.team, team) .where(usernameEq(condition.getUsername()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe())) .fetch(); }
33.조회 API 컨트롤러 개발
강의 :
https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30147&tab=curriculum
편리한 데이터 확인을 위해 샘플 데이터를 추가하자.
샘플 데이터 추가가 테스트 케이스 실행에 영향을 주지 않도록 다음과 같이 프로파일을 설정하자
src/main/resources/application.yml
spring: profiles: active: local
spring.profiles.active=local
테스트는 기존 application.yml을 복사해서 다음 경로로 복사하고, 프로파일을 test로 수정하자
src/test/resources/application.yml
spring: profiles: active: test
이렇게 분리하면 main 소스코드와 테스트 소스 코드 실행시 프로파일을 분리할 수 있다.
샘플 데이터 추가
package study.querydsl; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import study.querydsl.entity.Member; import study.querydsl.entity.Team; import javax.annotation.PostConstruct; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @Profile("local") @Component @RequiredArgsConstructor public class InitMember { private final InitMemberService initMemberService; @PostConstruct public void init() { initMemberService.init(); } @Component static class InitMemberService { @PersistenceContext EntityManager em; @Transactional public void init() { Team teamA = new Team("teamA"); Team teamB = new Team("teamB"); em.persist(teamA); em.persist(teamB); for (int i = 0; i < 100; i++) { Team selectedTeam = i % 2 == 0 ? teamA : teamB; em.persist(new Member("member" + i, i, selectedTeam)); } } } }
조회 컨트롤러
package study.querydsl; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import study.querydsl.entity.Member; import study.querydsl.entity.Team; import javax.annotation.PostConstruct; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @Profile("local") @Component @RequiredArgsConstructor public class InitMember { private final InitMemberService initMemberService; @PostConstruct public void init() { initMemberService.init(); } @Component static class InitMemberService { @PersistenceContext EntityManager em; @Transactional public void init() { Team teamA = new Team("teamA"); Team teamB = new Team("teamB"); em.persist(teamA); em.persist(teamB); for (int i = 0; i < 100; i++) { Team selectedTeam = i % 2 == 0 ? teamA : teamB; em.persist(new Member("member" + i, i, selectedTeam)); } } } }
예제 실행(postman)
http://localhost:8080/v1/members?teamName=teamB&ageGoe=31&ageLoe=35
댓글 ( 4)
댓글 남기기