[Spring JPA #13] 스프링 데이터 쿼리 만들기
| 스프링 데이터 쿼리 만들기
스프링 데이터에서 Repository 인터페이스 내에서 메서드명으로 쿼리를 만드는 방법은 다음과 같습니다.
- 메서드 이름을 분석해서 쿼리 만들기 (CREATE)
- 미리 정의해 둔 쿼리를 찾아 사용하기 (USE_DECLARED_QUERY)
- 미리 정의한 쿼리를 보고 없으면 만들기 (CREATE_IF_NOT_FOUND)
메서드 이름을 정의할 때 다음과 같은 규칙에 따라 메서드명을 정해야 합니다. 왜냐하면 스프링 데이터에서 그 메서드명을 해석하여 쿼리를 만들기 때문입니다.
리턴타입 {접두어}{도입부}By{프로퍼티 표현식}(조건식)[(And|Or){프로퍼티표현식}(조건식)]{정렬조건}(매개변수)
- 접두어 : find, get, query, count ...
- 도입부 : Distinct, First(N), Top(N)
- 프로퍼티 표현식 : Persion, Address, ZipCode => find(Person)ByAddress_ZipCode(...)
- 조건식 : IgnoreCase, Between, LessThan, GreaterThan, Like, Contains, ...
- 정렬 조건 : OrderBy{프로퍼티}Asc|Desc
- 리턴 타입 : E, Optional<E>, List<E>, Page<E>, Slice<E>, Stream<E>
- 매개 변수 : Pageable, Sort
또한 @Query, @NamedQuery를 이용하여 쿼리를 미리 정의해두고 사용할 수 있습니다. 그리고 유념해야할 것은 쿼리를 찾는 방식은 저장소마다 다릅니다. 따라서 찾는 방식이 어떤 매카니즘으로 이루어지는지 알아야 제대로 된 쿼리가 적용될 것을 예측할 수 있습니다.
| 스프링 데이터 쿼리 예제
프로젝트 구조
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── tutorial
│ │ │ └── springbootjpa
│ │ │ ├── Comment.java
│ │ │ ├── CommentRepository.java
│ │ │ ├── MyRepository.java
│ │ │ ├── SpringBootJpaApplication.java
│ │ └── resources
│ │ ├── application.properties
│ │ ├── static
│ │ └── templates
│ └── test
│ └── java
│ └── com
│ └── tutorial
│ └── springbootjpa
│ ├── CommentRepositoryTest.java
의존성 관리
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
소스 코드
@NoRepositoryBean
public interface MyRepository<T, ID extends Serializable> extends Repository<T, ID> {
<E extends T> E save(@NonNull E entity);
List<T> findAll();
long count();
@Nullable
<E extends T> Optional<E> findById(ID id);
}
@SpringBootApplication
public class SpringBootJpaApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootJpaApplication.class, args);
}
}
@Entity
public class Comment {
@Id
@GeneratedValue
private Long id;
private String comment;
@ManyToOne
private Post post;
private Integer likeCount = 0;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public Post getPost() {
return post;
}
public void setPost(Post post) {
this.post = post;
}
public Integer getLikeCount() {
return likeCount;
}
public void setLikeCount(Integer likeCount) {
this.likeCount = likeCount;
}
}
public interface CommentRepository extends MyRepository<Comment, Long>{
List<Comment> findByCommentContainsIgnoreCaseAndLikeCountGreaterThan(String keyword, int like);
}
- 위 메서드는 'keyword를 기준으로 대소문자 관계없이 likeCount가 like 인자보다 많은 Comment 엔티티를 찾아라'라는 의미입니다.
테스트 코드
@RunWith(SpringRunner.class)
@DataJpaTest
public class CommentRepositoryTest {
@Autowired
CommentRepository commentRepository;
@Test
public void crud() {
this.createComment(100, "spring data jpa");
this.createComment(50, "hibernate spring");
List<Comment> comments = commentRepository.findByCommentContainsIgnoreCaseOrderByLikeCountDesc("Spring");
assertThat(comments.size()).isEqualTo(2);
assertThat(comments).first().hasFieldOrPropertyWithValue("likeCount", 100);
}
private void createComment(int likeCount, String content) {
Comment comment = new Comment();
comment.setLikeCount(likeCount);
comment.setComment(content);
commentRepository.save(comment);
}
}
페이지 기준으로 슬라이싱하여 값을 찾아 페이지 객체로 반환할 수 있습니다.
소스 코드
public interface CommentRepository extends MyRepository<Comment, Long>{
List<Comment> findByCommentContainsIgnoreCaseAndLikeCountGreaterThan(String keyword, int like);
Page<Comment> findByCommentContainsIgnoreCase(String keyword, Pageable pageable);
}
- findByCommentContainsIgnoreCase 메서드는 "keyword를 기준으로 Pageable한 요청 객체를 받아 대소관계 없이 Comment 엔티티를 찾아서 Page 형태로 엔티티를 반환하라"라는 의미입니다.
테스트 코드
@RunWith(SpringRunner.class)
@DataJpaTest
public class CommentRepositoryTest {
@Autowired
CommentRepository commentRepository;
@Test
public void crud() {
this.createComment(100, "spring data jpa");
this.createComment(50, "hibernate spring");
PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "LikeCount"));
Page<Comment> comments = commentRepository.findByCommentContainsIgnoreCase("spring", pageRequest);
assertThat(comments.getNumberOfElements()).isEqualTo(2);
assertThat(comments).first().hasFieldOrPropertyWithValue("likeCount", 100);
}
private void createComment(int likeCount, String content) {
Comment comment = new Comment();
comment.setLikeCount(likeCount);
comment.setComment(content);
commentRepository.save(comment);
}
}
- PageRequest 클래스를 통해 위에서 언급한 메서드의 인자로 넣어질 객체를 만드는 모습입니다. page 베이스 인덱스는 0으로 하면서 사이즈가 10 그리고 내림차순으로 Page를 찾는 PageRequest 객체를 형성합니다.
위 코드는 다음과 같이 Stream으로 대체할 수도 있습니다.
소스 코드
public interface CommentRepository extends MyRepository<Comment, Long>{
List<Comment> findByCommentContainsIgnoreCaseAndLikeCountGreaterThan(String keyword, int like);
Stream<Comment> findByCommentContainsIgnoreCase(String keyword, Pageable pageable);
}
테스트 코드
@RunWith(SpringRunner.class)
@DataJpaTest
public class CommentRepositoryTest {
@Autowired
CommentRepository commentRepository;
@Test
public void crud() {
this.createComment(100, "spring data jpa");
this.createComment(50, "hibernate spring");
PageRequest pageRequest = PageRequest.of(0, 10, Sort.by(Sort.Direction.DESC, "LikeCount"));
try(Stream<Comment> comments = commentRepository.findByCommentContainsIgnoreCase("spring", pageRequest)){
Comment firstComment = comments.findFirst().get();
assertThat(firstComment.getLikeCount()).isEqualTo(100);
}
}
private void createComment(int likeCount, String content) {
Comment comment = new Comment();
comment.setLikeCount(likeCount);
comment.setComment(content);
commentRepository.save(comment);
}
}
'Spring Data > Spring Data JPA' 카테고리의 다른 글
[Spring JPA #17] 스프링 데이터 DomainClassConverter (0) | 2021.03.26 |
---|---|
[Spring JPA #16] 스프링 데이터 QueryDsl (0) | 2021.03.25 |
[Spring JPA #15] 스프링 데이터 도메인 이벤트 (0) | 2021.03.25 |
[Spring JPA #14] 스프링 데이터 커스텀 리포지터리 만들기 (0) | 2021.03.25 |
[Spring JPA #12] 스프링 데이터 Null 체크 (0) | 2021.03.25 |
[Spring JPA #11] 스프링 데이터 리포지터리 인터페이스 정의하기(Spring Repository Interface) (0) | 2021.03.25 |
[Spring JPA #10] 스프링 데이터 Common 리포지터리(Repository) (0) | 2021.03.25 |
[Spring JPA #9] 스프링 데이터 JPA 원리 및 스프링 데이터 구성 요소 (0) | 2021.03.25 |