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

Ch04. 요구사항 추가(책 통계, QueryDSL) - UserLoanHistoryRepository 리펙토링

webmaster 2022. 11. 8. 01:20
728x90

UserLoanHistoryRepository

interface UserLoanHistoryRepository : JpaRepository<UserLoanHistory, Long> {
    fun findByBookName(bookName: String): UserLoanHistory?
    fun findByBookNameAndStatus(bookName: String, status: UserLoanStatus): UserLoanHistory?
    fun findByStatus(status: UserLoanStatus): List<UserLoanHistory>
    fun countByStatus(status: UserLoanStatus): Long
}
  • 해당 메서드들은 @Query를 사용한 것이 아닌 SpringDataJpa를 사용해서 만든 것이다
    • 이는 리펙토링 해야 하나? -> 해야 된다(메서드 명도 짧아지고, 동적 쿼리의 편함이 있기때문)
  • 만약 findBy 뒤에 오는 조건이 동적으로 온다면?
    • 매번 새로운 메서드를 만들어야 한다(2^n으로 메서드가 많아진다)
    • 메서드 명이 길어지고, 읽기 힘들다.
  • where절에 들어오는 조건절이 동적으로 동작한다면 이를 QueryDsl을 사용하면 간단하게 작성할 수 있다.

UserLoanHistoryQuerydslRepository

@Component
class UserLoanHistoryQuerydslRepository(
    private val queryFactory: JPAQueryFactory
) {
    fun find(bookName: String, status: UserLoanStatus? = null): UserLoanHistory? {
        return queryFactory.select(userLoanHistory)
            .from(userLoanHistory)
            .where(
                userLoanHistory.bookName.eq(bookName),
                status?.let { userLoanHistory.status.eq(status) }
            )
            .limit(1)
            .fetchOne()
    }

    fun count(status: UserLoanStatus): Long {
        return queryFactory.select(userLoanHistory.id.count())
            .from(userLoanHistory)
            .where(
                userLoanHistory.status.eq(status)
            ).fetchOne() ?: 0L
    }
}
  • find()
    • 파라미터로 받는 status 값은 null로 올 수 있기 때문에 null 허용 타입으로 설정한 뒤, 디폴트 파라미터로 null값을 주었다.
    • where절에서 만약 status가 null이면 null값을 리턴하고 null이 아니면 let 함수를 실행한다.
      • let함수에서 userHistory.status.eq(status) 쿼리를 실행해준다.
      • where절에서 null 이면 and 절에 포함이 되지 않고, null이 아닌 값이 반환되면 해당 조건이 AND로 결합된다
    • limit를 사용하여 SQL을 limit을 fetchOne을 사용해 하나의 데이터만 가지고 온다
    • 함수명도 의미 있게 사용할 수 있다
  • count
    • count함수가 null을 반환할 수도 있으니 엘비스 연산자로 null을 반환하면 0을 반환하도록 한다.

BookService

@Service
class BookService(
    private val bookRepository: BookRepository,
    private val bookQuerydslRepository: BookQuerydslRepository,
    private val userRepository: UserRepository,
    private val userLoanHistoryRepository: UserLoanHistoryRepository,
    private val userLoanHistoryQuerydslRepository: UserLoanHistoryQuerydslRepository, //새로 추가된 repository
) {
   	//...

    @Transactional
    fun loanBook(request: BookLoanRequest) {
    	//...
        if (userLoanHistoryQuerydslRepository.find(
        //...
    }

    //..

    @Transactional(readOnly = true)
    fun countLoanedBook(): Int {
        return userLoanHistoryQuerydslRepository.count(UserLoanStatus.LOANED).toInt()
    }
}

 

728x90