-
Spring Data JPA의 CrudRepository만을 이용해 단순 CRUD 작업이 가능하다.
-
하지만 좀 더 다양한 기능을 마음대로 사용하기 위해선 추가적인 학습이 필요하다.
-
다양한 조건을 실행하는 쿼리를 작성하는 방법에 대해 알아보자.
쿼리 연습을 위한 준비
-
JPA에서는 각 DB에 맞는 Dialect가 별도의 SQL에 대한 처리를 자동으로 처리해준다.
-
하지만 복잡한 쿼리를 작성하기 위해서는 DB를 대상으로 하는 SQL이 아니라
-
JPA에서 사용하는 Named Query, JPQL(Java Persistence Query Language), Query dsl이라는 것을 학습해야 한다.
-
Spring Data JPA는 이러한 번거로운 과정을 조금이라도 줄여줄 수 있다.
-
그 방법 중 하나가 쿼리 메소드라는 기능이다.
-
쿼리 메소드는 메소드의 이름만으로 필요한 쿼리를 만들어 내는 기능이다.
쿼리 메소드 이용하기
-
Spring Data JPA는 메소드의 이름만으로 원하는 질의(query)를 실행할 수 있는 방법을 제공한다.
-
이때 쿼리라는 용어는 select 에만 해당한다는 점을 주의하자.
-
예를 들어 find…By 로 쿼리 메소드를 작성한다면 ‘find’ 뒤에 엔티티 타입을 지정한다.
-
그리고 리턴 타입으로는 Collection< T >으로 설계하면 된다.
-
또한 ‘By’의 뒤쪽에는 컬럼명을 이용한다.
-
쿼리 메소드의 리턴 타입은 Page< T >, Slice< T >, List< T >와 같은 Collection< T > 형태가 된다.
-
쿼리 메소드를 작성하는 자세한 방법은 Spring Data JPA 문서를 참고하자.
- 빈도가 높은 쿼리 메소드에 대해 알아보자.
Collection< T > findBy + 속성 이름(속성 타입)
- 만약 게시물에서 특정 글쓴이가 작성한 글을 찾고자 한다면 다음과 같이 쿼리를 작성하면 된다.
public Collection<Board> findByWriter(String writer);
LIKE 구문
- LIKE에 대한 처리는 4가지 형태를 사용한다.
-
단순 like : Like
-
키워드 + ‘%’ : StartingWith
-
’%’ + 키워드 : EndingWith
-
’%’ + 키워드 + ‘%’ : Containing
-- 작성자 이름에 "text"라는 문자가 들어있는 게시글을 검색하고 싶은 경우
public Collection<Board> findByWriterLike(String text);
public Collection<Board> findByWriterStartingWith(String text);
public Collection<Board> findByWriterEndingWith(String text);
public Collection<Board> findByWriterContaining(String text);
조건 : and 혹은 or
-
예를 들어 게시글의 title과 content 속성에 특정한 문자열이 들어있는 게시물을 검색하려면
-
‘findBy’ + ‘TitleContaining’ + ‘Or’ + ‘ContentContaining’과 같은 형태가 된다.
public Collection<Board> findBy TitleContainingOrContentContaining(String title, String content);
부등호
-
> : GreaterThan
-
< : LessThan
-
예를 들어 게시물의 title에 특정 문자가 포함되어 있고(=And)
-
bno가 특정 숫자 초과(=GreaterThan)인 데이터를 조회한다면 다음과 같다.
public Collection<Board> findByTitleContainingAndBnoGreaterThan(String keywoard, Long num);
order by
-
‘OrderBy’ + 속성 + ‘Asc or Desc’
-
예를 들어 게시물의 bno가 특정 번호보다 큰 게시물을 bno 값의 역순(=Desc)으로 조회하고 싶다면 다음과 같다.
public Collection<Board> findByBnoGreaterThanOrderByBnoDesc(Long bno);
페이징 처리와 정렬
-
쿼리 메소드들에는 마지막 파라미터로 페이지 처리를 할 수 있는 Pageable 인터페이스와 정렬을 처리하는 Sort 인터페이스를 사용할 수 있다.
-
Pageable 인터페이스는 여러 메소드가 존재하기 때문에 이를 구현하기보다는 PageRequest 클래스를 이용하는 것이 편리하다.
of() | 설명 |
---|---|
PageRequest.of(int page, int size) | 페이지 번호(0부터 시작), 페이지당 데이터의 수 |
PageRequest.of(int page, int size, Sort.Direction direction, String… props) |
페이지 번호, 페이지당 데이터의 수, 정렬 방향, 속성(컬럼)들 |
PageRequest.of(int page, int size, Sort sort) | 페이지 번호, 페이지당 데이터의 수, 정렬 방향 |
페이징 처리
-
Pageable 인터페이스는 말 그대로 페이징 처리에 필요한 정보를 제공한다.
-
예를 들어 findByBnoGreaterThanOrderByBnoDesc 메소드에 Pageable을 적용하면 다음과 같다.
public List<Board> findByBnoGreaterThanOrderByBnoDesc(Long bno, Pageable paging);
-
코드는 기존과 동일하지만 파라미터에 Pageable이 적용되어 있고
-
리턴 타입으로 Collection< > 대신 List< >를 적용한 것이 달라졌다.
-
이에 대한 테스트 코드는 다음과 같다.
@Test
public void testBnoOrderByPaging() {
//Pageable paging = new PageRequest(0, 10);
//spring boot 2.0.0
Pageable paging = PageRequest.of(0, 10);
Collection<Board> results = repo.findByBnoGreaterThanOrderByBnoDesc(0L, paging);
results.forEach(board -> System.out.println(board));
}
정렬
-
정렬은 쿼리 메소드에서 OrderBy 로 처리해도 되지만
-
Sort를 이용하면 원하는 방향을 파라미터로 결정할 수 있다는 장점이 있다.
-
쿼리 메소드에 정렬 부분을 지정하지 않은 메소드를 정렬해보자.
public List<Board> findByBnoGreaterThan(Long bno, Pageable paging);
@Test
public void testBnoPagingSort() {
Pageable paging = new PageRequest(0, 10, Sort.Direction.ASC, "bno");
Collection<Board> results = repo.findByBnoGreaterThan(0L, paging);
results.forEach(board -> System.out.println(board));
}
Page< T > 타입
- Page< T > 타입을 이용하면 Sprig MVC와 연동할 때 편리함을 제공한다.
public Page<Board> findByBnoGreaterThan(Long bno, Pageable paging);
@Test
public void testBnoPagingSort() {
//Pageable paging = new PageRequest(0, 10, Sort.Direction.ASC, "bno");
//spring boot 2.0.0
Pageable paging = PageRequest.of(0, 10, Sort.Direction.ASC, "bno");
Page<Board> result = repo.findByBnoGreaterThan(0L, paging);
System.out.println("PAGE SIZE: " + result.getSize());
System.out.println("TOTAL PAGES: " + result.getTotalPages());
System.out.println("TOTAL COUNT: " + result.getTotalElements());
System.out.println("NEXT: " + result.nextPageable());
List<Board> list = result.getContent();
list.forEach(board -> System.out.println(board));
}
-
Page< Board >는 단순 데이터만을 추출하는 용도가 아니라
-
웹에서 필요한 데이터들을 추가적으로 처리해준다.
Hibernate: select board0_.bno as bno1_0_, board0_.content as content2_0_, board0_.regdate as regdate3_0_, board0_.title as title4_0_, board0_.updatedate as updateda5_0_, board0_.writer as writer6_0_ from tbl_boards board0_ where board0_.bno>? order by board0_.bno asc limit ?
Hibernate: select count(board0_.bno) as col_0_0_ from tbl_boards board0_ where board0_.bno>?
-
주목할점은 테스트 코드를 실행하면 실제로 SQL문이 2번 실행된다.
-
첫번째 SQL : 데이터를 추출
-
두번째 SQL : 데이터의 개수를 파악하기 위해 SELECT COUNT(…) 실행
-
정리하자면 리턴 타입을 Page< T >로 하게 되면 웹 페이징에서 필요한 데이터를 한 번에 처리할 수 있기 때문에
-
데이터를 위한 SQL과 개수를 파악하기 위한 SQL을 매번 작성하는 불편함을 줄일 수 있다.