[JPA] @Query 활용하기

jpa, spring

@Query 활용하기 #

BookRepository class에 극단적인 메소드를 만들어보자

List<Book> findByCategoryIsNullAndNameEqualsAndCreatedAtGreaterThanEqualAndUpdatedAtGreaterThanEqual(String name, LocalDateTime createdAt, LocalDateTime updatedAt);

BookRepository class에 아래와 같은 메서드를 추가해보자

@Query(value = "select b from Book b " // 공백에 유의하자
    + "where name = ?1 and createdAt >= ?2 and updatedAt >= ?3 and category is null")
List<Book> findByNameRecently(String name, LocalDateTime createdAt, LocalDateTime updatedAt);

위의 극단적으로 긴 메소드와 같은 역할을 하지만 JPQL 이라는 문법을 통하여, Query annotation을 이용하면 좀더 가독성있는 코드를 짤수 있다.


@Param #

@Query(value = "select b from Book b "
    + "where name = :name and createdAt >= :createdAt and updatedAt >= :updatedAt and category is null")
List<Book> findByNameRecently(
    @Param("name") String name, 
    @Param("createdAt") LocalDateTime createdAt, 
    @Param("updatedAt") LocalDateTime updatedAt
);

이렇게 하면 파라미터의 순서에 영향을 받지 않아지기 때문에, 로직의 변경이 자유로워지며 사이드 이펙트에서 좀더 자유로울 수 있게 된다.


@Column(columnDefinition) #

위의 코드를 찾는 data.sql문을 보면

insert into book(`id`, `name`, `publisher_id`, `deleted`) values (1, 'JPA 초격차 패키지', 1, false);

보다시피 createdAt 과 updatedAt이 빠져 있다. 이걸 sql문으로 일일히 넣는것보단 columnDefinition 을 활용한다.

public class BaseEntity implements Auditable {

    @CreatedDate
    @Column(columnDefinition = "datetime(6) default now(6) comment '생성시간'", nullable = false, updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column(columnDefinition = "datetime(6) default now(6) comment '수정시간'", nullable = false)
    private LocalDateTime updatedAt;
}

@Query 좀 더 활용하기 #

book과 category만 따로 추출해서 찾아보는 구체 클래스를 생성한다.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BookNameAndCategory {
    private String name;
    private String category;
}

BookRepository 는 아래와 같이 @Query value를 지정해준다. class 이름을 지정할때는 directory까지 지정해줘야한다.

public interface BookRepository extends JpaRepository<Book, Long>{
    ...
    @Query(value = "select new com.fastcampus.jpa.bookmanager.repository.dto.BookNameAndCategory(b.name, b.category) from Book b")
    List<BookNameAndCategory> findBookNameAndCategory();
}
@Test
void queryTest() {
    bookRepository.findBookNameAndCategory().forEach(b -> {System.out.println(b.getName() + " : " + b.getCategory());});
}

Paging 기능 #

@Query(value = "select new com.fastcampus.jpa.bookmanager.repository.dto.BookNameAndCategory(b.name, b.category) from Book b")
Page<BookNameAndCategory> findBookNameAndCategory(Pageable pageable);

↑ 위와 같이 repository의 메소드에 Pageable 인자를 추가해준다.

bookRepository.findBookNameAndCategory(PageRequest.of(1, 1)).forEach(
    BookNameAndCategory -> System.out.println(BookNameAndCategory.getName() + " : " + BookNameAndCategory.getCategory())
);

bookRepository.findBookNameAndCategory(PageRequest.of(0, 1)).forEach(
    BookNameAndCategory -> System.out.println(BookNameAndCategory.getName() + " : " + BookNameAndCategory.getCategory())
);

첫번째 테스트 메소드는 PageRequest를 활용하여 2번째 페이지에서 찾고, 두번째 테스트 메소드는 첫번째 페이지에서 가져온다.