-
JPA QueryDSL 프레임워크 - 3 순수 JPA와 QueryDSLDBMS/JPA 2024. 8. 20. 04:43728x90반응형
JPA QueryDSL 프레임워크 - 3 순수 JPA와 QueryDSL
1. 순수 JPA 리포지토리
@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(); } }
2. Querydsl 사용
순수 JPA 리포지토리 - 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(); }
3. 동적 쿼리와 성능 최적화 조회
3 - 1. Builder 사용
3 - 1 - 1. MemberTeamDto - 조회 최적화용 DTO 추가
@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; } }
참고
@QueryProjection 을 사용하면 해당 DTO가 Querydsl을 의존하게 된다.
이런 의존이 싫으면, 해당 에노테이션을 제거하고,
Projection.bean(), fields(), constructor() 을 사용하면 된다.
3 - 1 - 2. 회원 검색 조건
@Data public class MemberSearchCondition { //회원명, 팀명, 나이(ageGoe, ageLoe) private String username; private String teamName; private Integer ageGoe; private Integer ageLoe; }
3 - 1 - 3. 동적쿼리 - Builder 사용
Builder를 사용한 예제
//Builder 사용 //회원명, 팀명, 나이(ageGoe, ageLoe) 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, member.username, member.age, team.id, team.name)) .from(member) .leftJoin(member.team, team) .where(builder) .fetch(); }
3 - 2. Where절 파라미터 사용
Where절에 파라미터를 사용한 예제
//회원명, 팀명, 나이(ageGoe, ageLoe) public List<MemberTeamDto> search(MemberSearchCondition condition) { return queryFactory .select(new QMemberTeamDto( member.id, member.username, member.age, team.id, team.name)) .from(member) .leftJoin(member.team, team) .where(usernameEq(condition.getUsername()), teamNameEq(condition.getTeamName()), ageGoe(condition.getAgeGoe()), ageLoe(condition.getAgeLoe())) .fetch(); } private BooleanExpression usernameEq(String username) { return isEmpty(username) ? null : member.username.eq(username); } private BooleanExpression teamNameEq(String teamName) { return isEmpty(teamName) ? null : team.name.eq(teamName); } private BooleanExpression ageGoe(Integer ageGoe) { return ageGoe == null ? null : member.age.goe(ageGoe); } private BooleanExpression ageLoe(Integer ageLoe) { return ageLoe == null ? null : member.age.loe(ageLoe); }
참고
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(); }
4. 조회 API 컨트롤러 개발
편리한 데이터 확인을 위해 샘플 데이터를 추가하자.
샘플 데이터 추가가 테스트 케이스 실행에 영향을 주지 않도록 다음과 같이 프로파일을 설정하자
4 - 1. 프로파일 설정
src/main/resources/application.yml
spring: profiles: active: local
테스트는 기존 application.yml을 복사해서 다음 경로로 복사하고, 프로파일을 test로 수정하자
src/test/resources/application.yml
spring: profiles: active: test
이렇게 분리하면 main 소스코드와 테스트 소스 코드 실행시 프로파일을 분리할 수 있다.
4 - 2. 샘플 데이터 추가
@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)); } } } }
4 - 3. 조회 컨트롤러
@RestController @RequiredArgsConstructor public class MemberController { private final MemberJpaRepository memberJpaRepository; @GetMapping("/v1/members") public List<MemberTeamDto> searchMemberV1(MemberSearchCondition condition) { return memberJpaRepository.search(condition); } }
728x90반응형'DBMS > JPA' 카테고리의 다른 글
JPA QueryDSL 프레임워크 - 4 스프링 데이터 JPA와 Querydsl (0) 2024.08.20 JPA QueryDSL 프레임워크 - 2 중급 문법 (0) 2024.08.20 JPA QueryDSL 프레임워크 - 1 소개, 기본 문법 (0) 2024.08.20 JPA 스프링 데이터 JPA (Spring Data JPA) - 3 (0) 2024.08.14 JPA 스프링 데이터 JPA (Spring Data JPA) - 2 (0) 2024.08.13