타임라인 querydsl로 구현


타임라인을 구현하며 작성한 게시글


querydsl을 사용하기로한 이유

처음에 내가 팔로우한 사용자들이 작성한 게시글을 조회(타임라인 조회) 기능을 구현할 때

다음과 같이 JPQL을 직접 작성했었다.

@Query(value = "SELECT p"
    + " FROM Post p"
    + " JOIN Follow f ON p.user.id = f.following.id"
    + " WHERE f.follower.id = :userId")
List<Post> findByJoinFollow(@Param("userId") Long userId, Pageable pageable);

@Query(value = "SELECT p"
    + " FROM Post p"
    + " JOIN Follow f ON p.user.id = f.following.id"
    + " WHERE f.follower.id = :userId"
    + " AND p.id < :lastPostId")
List<Post> findByJoinFollowAndLastIdLessThan(@Param("userId") Long userId, 
@Param("lastPostId") Long lastPostId, Pageable pageable);


이미 완성한 쿼리고 테스트를 거치며 조회가 되는지 확인했기 때문에

빠르게 이해할 수 있는 코드지만, JPQL을 완성하기 전에

N+1 문제, 불필요한 조회 등 많은 문제를 만났었다.


아직도 문제가 남아있다.


querydsl을 사용하면 위 문제들이 해결된다.



querydsl 사용

@RequiredArgsConstructor
@Repository
public class TimelineRepository {

	private final JPAQueryFactory queryFactory;

	public List<Post> findByJoinFollow(Long userId, Long lastPostId, Pageable pageable) {
		QueryResults<Post> result = queryFactory
			.selectFrom(post)
			.join(follow)
			.on(eqFollowing(follow.following))
			.where(post.id.lt(lastPostId).and(eqFollower(userId)))
			.limit(pageable.getPageSize())
			.orderBy(post.id.desc())
			.fetchResults();
		return result.getResults();
	}

	private BooleanExpression eqFollowing(QUser user) {
		if (ObjectUtils.isEmpty(user)) {
			return null;
		}
		return post.user.eq(user);
	}

	private BooleanExpression eqFollower(Long id) {
		if (ObjectUtils.isEmpty(id)) {
			return null;
		}
		return follow.follower.id.eq(id);
	}

}


위 코드를 보면 querydsl의 Custom Repository를 사용하지 않았다.

보통 Querydsl을 사용할 때 다음 파일들을 작성한다.


whwhfen

자세한 설명은 jojoldu - Spring Boot Data Jpa 프로젝트에 Querydsl 적용하기 에서 확인할 수 있다.


2020년 우아한테크콘서트에서 JPAQueryFactory만 있다면 Querydsl을 사용할 수 있다는 것을 발표했고

타임라인 구현에 적합한 방법인 것이라 판단하여 Custom Repository를 만들지 않고 JPAQueryFactory만 주입받아 구현했다.



Custom Repository를 제거해도 될까?

이 방법의 단점은 상속으로 얻는 이점이 사라진다는 것이다.

기본 repository와 Custom repository의 메소드를 하나의 인터페이스로 참조하며 사용할 수 없게 된다.

1개의 엔티티에 접근하기 위해 다수의 repository 인스턴스를 만들게 되는 것이다.


따라서 특정 상황에서만 이 상속 구조를 제거해야 한다.

어떤 기능을 구현하기 위해 다양한 엔티티를 Join하여 함께 참조해야 하는데,

이걸 A엔티티 Repository의 역할로 봐야할지, B엔티티 Repository의 역할로 봐야할지 애매모호한 상황이 발생하는데

이런 경우 특정 엔티티를 메인으로 하지 않는 기능이기 때문에, 위처럼 JPAQueryFactory만 주입받아 사용하는 Repository를 사용하면 좋다.