본문 바로가기
Spring

[Spring] Querydsl Repository 구현

by 가드 2022. 11. 19.
728x90

Querydsl 설정은 Querydsl Gradle 설정 참고

이전 글 작성할 때 테스트했던 player table로 querydsl을 적용해보자.

Player Table

QuerydslConfiguration

@Configuration
public class QuerydslConfig {
    @PersistenceContext
    private EntityManager entityManager;
    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}

JPAQueryFactory를 서비스에서 주입받아 사용하기 위해 Configuration을 설정해야 한다.

 

Entity Class 생성

player entity 클래스를 생성

@Getter
@Setter
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Player {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String game;
    private String nickname;
    private Integer rank;
}

QuerydslRepository 생성

@Repository
public class PlayerRepositorySupport extends QuerydslRepositorySupport {
    private final JPAQueryFactory jpaQueryFactory;
    public PlayerRepositorySupport(JPAQueryFactory jpaQueryFactory) {
        super(Player.class);
        this.jpaQueryFactory =  jpaQueryFactory;
    }
    public List<Player> findAllByGame(String game) {
        return jpaQueryFactory.selectFrom(player)
            .where(player.game.eq(game))
            .fetch();
    }
}

PlayerRepositorySupport 클래스는 QuerydslRepositorySupport를 상속받고 생성자에 Bean에 등록된 JPAQueryFactory 객체를 주입받아서 사용한다.

 

PlayerRepositorySupport TestCode

@SpringBootTest
class PlayerTest {
    @Autowired
    private PlayerRepositorySupport playerRepositorySupport;
    @Test
    public void findAllByGame() {
        List<Player> playerList = playerRepositorySupport.findAllByGame("LOL");
        System.out.println("list size : " + playerList.size());
    }
}

Player List Size는 5개로 game이 "LOL"인 Entity만 잘 들고 왔다.

PlayerRepositorySupport 클래스를 다시 한번 살펴보면 QuerydslRepositorySupport를 상속받다 보니 생성자에서 슈퍼 생성자에 super(Entity.class)를 등록을 해야 하는 게 불편하다. 사실 정말 중요한 건 JPAQueryFactory인데 이 객체로만 제어할 수 있지 않을까?

상속 구조와 생성자를 없애보자.

조금 더 합리적인 QuerydslRepository 생성

@RequiredArgsConstructor
@Repository
public class PlayerQueryRepository {
    private final JPAQueryFactory jpaQueryFactory;
    public List<Player> findAllByGame(String game) {
        return jpaQueryFactory.selectFrom(QPlayer.player)
            .where(QPlayer.player.game.eq(game))
            .fetch();
    }
}

QuerydslSupportRepository에 슈퍼 생성자에 Entity 등록이 없이 바로 JPAQueryFactory를 주입해도 무관하다.

 

BooleanBuilder를 사용한 동적 쿼리

public List<Player> findAllWithBooleanBuilder(String game, String nickName, Integer rank) {
        BooleanBuilder booleanBuilder = new BooleanBuilder();
        if (game != null) {
            booleanBuilder.and(player.game.eq(game));
        }
        if (nickName != null) {
            booleanBuilder.and(player.nickname.eq(nickName));
        }
        if (rank != null) {
            booleanBuilder.and(player.rank.eq(rank));
        }
        return jpaQueryFactory.selectFrom(player)
            .where(booleanBuilder)
            .fetch();
    }

Querydsl을 사용하여 동적 쿼리로 where 절을 적용하려고 BooleanBuilder를 사용한다. 뭐 기능상 문제없이 잘 동작하지만 BooleanBuilder에 필드 조건들을 적용하려고 보니 각 필드들의 상태 값을 체크하고 조건을 적용해야 한다. 저 코드는 필드 3개의 대한 코드이지만 많은 필드 조건을 적용하려고 하면 코드 양이 늘어나고 조건이 한눈에 들어오지 않는다. 

조금 더 합리적인 동적 쿼리 생성

public List<Player> findAllWithBooleanExpression(String game, String nickName, Integer rank) {
	return jpaQueryFactory.selectFrom(player)
            .where(eqGame(game), eqName(nickName), eqRank(rank))
            .fetch();
}
private BooleanExpression eqGame(String game) {
	return game == null ? null : player.game.eq(game);
}
private BooleanExpression eqName(String nickName) {
	return nickName == null ? null : player.nickname.eq(nickName);
}
private BooleanExpression eqRank(Integer rank) {
	return rank == null ? null : player.rank.eq(rank);
}

각 필드의 조건의 메서드를 별도로 생성해서 BooleanExpression 타입으로 리턴을 해주면 where 절에서는 null 값에 대해서는 조건절에서 제거가 되기 때문에 문제가 없고 조건 메서드는 다른 쿼리 메서드에서도 같이 사용할 수 있으므로  BooleanExpression으로 동적 쿼리를 생성하는 방식이 괜찮아 보인다.

 

다음번에는 Querydsl을 이용하여 조회 쿼리 시 성능에 영향이 미치는 점에 대해서 정리해보자.

300x250

댓글