Tools & Libraries/Querydsl

[Querydsl] 순수 JPA와 Querydsl 1. (순수 JPA 리포지토리에서 querydsl 사용해보기)

대기업 가고 싶은 공돌이 2024. 10. 12. 03:43

순수 JPA 리포지토리와 Querydsl

이제 순수 JPA 리포지토리에서 Querydsl을 활용해보자

@Repository
public class MemberJPARepository {

    private final EntityManager em;
    private final JPAQueryFactory queryFactory;

    public MemberJPARepository(EntityManager em) {
        this.em = em;
        this.queryFactory = new JPAQueryFactory(em);
    }

우선 위와 같이 순수 JPA 리포지토리를 만들고

여러 기본 메소드들을 만들어주자

 

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();
}

 

이제 테스트 코드를 작성해 기능이 제대로 작동하는지 확인해 보자

 

@Test
public void basicTest(){
    Member member = new Member("member1", 10);
    memberJPARepository.save(member);

    Member member1 = memberJPARepository.findById(member.getId()).get();
    Assertions.assertThat(member1).isEqualTo(member);

    List<Member> all = memberJPARepository.findAll();
    Assertions.assertThat(all).containsExactly(member);
}

 

잘 통과를 했고 이제 메소드들을 하나씩 querydsl로 바꿔보자

 

Querydsl로 바꾸기

findAll 메소드

public List<Member> findAll_Querydsl() {
    return queryFactory
            .selectFrom(member)
            .fetch();
}

find All 메소드를 이렇게 간단하게 바꿀 수 있다.

 

findByUsername 메소드

public List<Member> findByUsername(String username){
    return em.createQuery("select m from Member m where m.username = :username", Member.class)
            .setParameter("username",username)
            .getResultList();
}

 

findByUsername 메소드도 바꿔보자

public List<Member> findByUsername_Querydsl(String username){
    return queryFactory
            .selectFrom(member)
            .where(member.username.eq(username))
            .fetch();
}

 

확실히 코드를 알아보기 편하고

자바 코드로 작성하니 오타로 인한 에러를 컴파일 시점에 잡을 수 있다.

 

이제 테스트 코드를 다시 작성해보자

 

Querydsl 테스트 코드

@Test
public void basicTest(){
    Member member = new Member("member1", 10);
    memberJPARepository.save(member);

    Member member1 = memberJPARepository.findById(member.getId()).get();
    Assertions.assertThat(member1).isEqualTo(member);

    List<Member> all = memberJPARepository.findAll_Querydsl();
    Assertions.assertThat(all).containsExactly(member);

    List<Member> result = memberJPARepository.findByUsername_Querydsl("member1");
    Assertions.assertThat(all).containsExactly(member);
}

이렇게 메소드 명만 바꿔주고 테스트를 실행해보자

잘 통과했다.

 

사소한 팁

public MemberJPARepository(EntityManager em) {
    this.em = em;
    this.queryFactory = new JPAQueryFactory(em);
}

쿼리 팩토리를 주입할 때 생성자로 넣어도 되지만 bean으로 등록해서 그냥 주입해도 편리하다.

 

@SpringBootApplication
public class QuerydslApplication {

    public static void main(String[] args) {
       SpringApplication.run(QuerydslApplication.class, args);
    }
    
    @Bean
    JPAQueryFactory jpaQueryFactory(EntityManager em){
       return new JPAQueryFactory(em);
    }
}

이런 식으로 말이다. 물론 빈은 다른 곳에서 등록시켜도 된다.

 

public MemberJPARepository(EntityManager em, JPAQueryFactory queryFactory) {
    this.em = em;
    this.queryFactory = queryFactory;
}

 

이제 이렇게 변경 가능하다.

 

스프링이 bean으로 em과 queryfactory를 다 관리해주니

생성자를 생략하고 RequiredArgsConstructor를 사용할 수도 있다.

 

 

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;
    }
}

 

우선 위와 같이 dto를 만들어 주자,

Qtype을 만들기 위해 @QueryProjection을 작성하자.

 

메소드 작성

public List<MemberTeamDto> searchByBuilder(MemberSearchCondition condition){

    BooleanBuilder builder = new BooleanBuilder();
    if(StringUtils.hasText(condition.getUsername())){
        builder.and(member.username.eq(condition.getUsername()));
    }
    if(StringUtils.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();
}

 

불리언 빌더를 사용하여 조건을 적용한 걸 먼저 보자

BooleanBuilder builder = new BooleanBuilder();
if(StringUtils.hasText(condition.getUsername())){
    builder.and(member.username.eq(condition.getUsername()));
}
if(StringUtils.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()));
}

위와 같이 특정 조건에 부합하면 빌더에 조건을 추가하여 동적으로 조건을 추가할 수 있다.

 

StirngUtils.hasText는 말 그대로 null과 "" 인 값을 걸러주는 기능이다.

 

.select(new QMemberTeamDto(
        member.id.as("memberId"),
        member.username,
        member.age,
        team.id.as("teamId"),
        team.name.as("teamName")
))

 

셀렉트 절에 dto를 삽입할 때 반드시 컬럼명을 일치시켜줘야 한다.

member 엔티티는 id 이기 때문에 as 를 사용하여 dto의 이름과 동일하게 변경해주었다.

 

밑에 team id와 team name도 마찬가지다. 

 

주의할 점

만약 빌더에 아무런 조건이 들어있지 않다면, 데이터베이스에 있는 모든 데이터를 다 가져오기 때문에

페이징 처리를 해주면 좋다.