실전! 코틀린과 스프링 부트로 도서관리 애플리케이션 개발하기

Ch04. 요구사항 추가(책 통계, QueryDSL) - QueryDSL 사용하기

webmaster 2022. 11. 8. 00:56
728x90

UserRepositoryCustomImpl 작성하기(방식 1)

이구조로 만들어야한다.

Config 파일

@Configuration
class QuerydslConfig(
    private val em: EntityManager
) {
    @Bean
    fun querydsl(): JPAQueryFactory{
        return JPAQueryFactory(em)
    }
}
  • JPAQueryFactory는 entityManager를 파라미터로 받아 사용한다.
  • 해당 빈을 등록해두어 QueryDsl을 사용하는 곳에서 주입받아서 사용하면 된다.

UserRepositroy

interface UserRepository : JpaRepository<User, Long>, UserRepositoryCustom{
    fun findByName(name: String): User?

}
  • 더 이상 @Query 만드는 코드가 없다.
  • UserRepositoryCustom을 상속받는다

UserRepositoryCustom

interface UserRepositoryCustom {
    fun findAllWithHistories():List<User>
}
  • 추가로 구현해줄 메서드를 인터페이스로 작성한다

UserRepositoryCustomImpl

class UserRepositoryCustomImpl(
    private val queryFactory: JPAQueryFactory
) : UserRepositoryCustom {
    override fun findAllWithHistories(): List<User> {
        return queryFactory.select(user).distinct().from(user)
            //.leftJoin(user.userLoanHistories) //방법 1.
            .leftJoin(userLoanHistory).on(userLoanHistory.user.id.eq(user.id)).fetchJoin().fetch() //방법2
    }
}
  • 빈으로 JPAQueryFactory를 주입받는다.
  • UserRepositoryCustom을 상속받아 구현한다.
  • Q파일을 사용할 수 있으며 static import를 사용하여 객체처럼 사용한다
  • join방법은 2가지가 있다.
    • leftJoin(조인 필드) 
    • leftJoin(조인할 Q파일).on(조인 컬럼.eq(대상 컬럼))

장점은 서비스 단에서 UserRepository를 하나만 사용할 수 있다.

단점은 인터페이스와 클래스를 항상 같이 만들어주어야 하는 것이 부담이고, 번거롭다

BookQuerydslRepository 작성하기(방식 2)

@Component
class BookQuerydslRepository(
    private val queryFactory: JPAQueryFactory,
) {
    fun getStats(): List<BookStatResponse> {
        return queryFactory.select(
            Projections.constructor(
                BookStatResponse::class.java,
                book.type,
                book.id.count()
            )
        )
            .from(book)
            .groupBy(book.type)
            .fetch()
    }
}
  • 패키지 위치는 domain 하위가 아닌 가장 상위 패키지에 repository를 만들어서 해당 파일을 만든다.
  • 해당 파일을 스프링이 스캔할 수 있도록 @Component 애노테이션을 붙인다.(@Repository를 붙여도 무방)
  • Projections.constructor()를 사용해 첫 번째 파라미터로 받은 클래스의 생성자를 통해 새로운 객체를 생성해 넣어준다.

BookService

@Service
class BookService(
    private val bookRepository: BookRepository,
    private val bookQuerydslRepository: BookQuerydslRepository, //추가된 주입된 클래스
    private val userRepository: UserRepository,
    private val userLoanHistoryRepository: UserLoanHistoryRepository,
) {
    //...
    @Transactional(readOnly = true)
    fun getBookStatistics(): List<BookStatResponse> {
        return bookQuerydslRepository.getStats()
    }
}
  • 더 이상 BookRepository 기능이 아닌 새로 만든 Repository 기능을 사용한다.

장점은 클래스만 바로 만들면 되어 간결하다.

단점은 서비스단에서 필요에 따라 두 Repository를 모두 사용해주어야 한다.

 

두 방식 모두 장단이 있지만, 강사는 두 번째 방법을 선호 -> 멀티 모듈을 사용할 경우, 모듈별로 각기 다른 Repository를 사용하는 경우가 많아 단점이 상쇄되고 장점이 극대화되기 때문이다

728x90