일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- springdatajpa
- 스프링
- http
- Android
- Greedy
- jpa
- JPQL
- Exception
- 그리디
- transaction
- 알고리즘
- Proxy
- Servlet
- 자바
- JDBC
- 인프런
- db
- SpringBoot
- Thymeleaf
- 김영한
- kotlin
- 스프링 핵심 원리
- Spring Boot
- AOP
- 백준
- java
- 스프링 핵심 기능
- QueryDSL
- pointcut
- spring
- Today
- Total
개발자되기 프로젝트
@Query 본문
@Query를 사용하는 이유!에 대해 알아보자
1. 이름이 길어지는 경우 가독성을 좋게 변경하기 위해
다음과 같이 이름이 굉장히 킨 query method가 있다고 해보자.
List<Book> findByCategoryIsNull
AndNameEquals
AndCreatedAtGreaterThanEqual
AndUpdatedAtGreaterThanEqual
(String name, LocalDateTime createdAt, LocalDateTime updatedAt);
category는 null이고, 주입한 이름과 동일하며, 주입한 createdAt & updatedAt보다 크거나 같은 값을 찾는 쿼리이다.
테스트도 노답이다.
@Test
void queryTest(){
System.out.println("findByCategoryIsNullAndNameEqualsANdCreatedAtGreaterThanEqualAndUpdatedAtGraeaterThanEqual : "
+ bookRepository.findByCategoryIsNullAndNameEqualsAndCreatedAtGreaterThanEqualAndUpdatedAtGreaterThanEqual(
"탐사수", LocalDateTime.now().minusDays(1L),LocalDateTime.now().minusDays(1L)));
}
---------------------------------------------------------------------------------------------------------------------------------
엥 테스트 결과 아무것도 안나왔다.
findByCategoryIsNullAndNameEqualsANdCreatedAtGreaterThanEqualAndUpdatedAtGraeaterThanEqual : []
data.sql에 다음과 같이 저장해둬서 값이 나와야 하는데..
그 이유는 data.sql은 단순히 query만 실행하지, listener가 동작하지는 않는다.
따라서 createdAt, updatedAt이 null이어서 위의 query method의 조건에 부합하지 않았기 때문이다.
간단하게 data.sql에서 값을 직접 now()를 통해 입력해 줄 수 있지만 JPA를 활용해 보자.
이전에 작성해둔 BaseEntity에서 createdAt, updatedAt에 nullable = false를 설정해 주자.
추가로 DDL시 default 값이 입력될 수 있도록 columndefinition()을 적용해주자.
@Data
@MappedSuperclass //해당 클래스 필드를 상속받는 entity의 커럶으로 포함시켜줌. 상위 엔티티의 컬럼으로 쓰겠다.
@EntityListeners(value = AuditingEntityListener.class)
public class BaseEntity implements Auditable {
@CreatedDate
@Column(nullable = false, updatable = false, columnDefinition = "datetime(6) default now(6)")
private LocalDateTime createdAt;
@LastModifiedDate
@Column(nullable = false, columnDefinition = "datetime(6) default now(6)")
private LocalDateTime updatedAt;
}
@Column에 대한 내용은 아래 글 참고
테스트 결과 잘 불러와졌다
findByCategoryIsNullAndNameEqualsANdCreatedAtGreaterThanEqualAndUpdatedAtGraeaterThanEqual :
[Book(super=BaseEntity(createdAt=2021-07-09T20:54:44.219006, updatedAt=2021-07-09T20:54:44.219006), id=2, name=삼다수, authorId=null, category=null, deleted=false)]
근데 now(6)에 6은 뭐임? : 자릿수
----------------------------------------------------------------------------------------------------------------------------------
다시 본론으로 돌아오자 ㅋㅋㅋㅋ
이름이 겁~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~나긴 query method를 실행했다.
이름 쓰다가 오타나면 한참 찾아야 하기도 하고 영 좋지 않다. 가독성도 구리다.
@Query를 통해 구체적인 where조건을 만들어 줄 수 있다.
b라는 Book entity를 선택하는데 3가지 조건이 있다.
1 : name과 일치
2 : createdAt이 주입한 값 이상
3 : updatedAt이 주입한 값 이상
@Query(value = "select b from Book b "
+ "where name = ?1 and createdAt >= ?2 and updatedAt >= ?3")
List<Book> findByNameRecently(String name, LocalDateTime createdAt, LocalDateTime updatedAt);
<Test>
@Test
void queryTest(){
System.out.println("findByNameRecently : " + bookRepository.findByNameRecently("삼다수", LocalDateTime.now().minusDays(1L), LocalDateTime.now().minusDays(1L)));
}
실제 동작한 쿼리를 보면 해당 조건들이 적용이 된 것을 확인할 수 있다.
Hibernate:
select
book0_.id as id1_1_,
book0_.created_at as created_2_1_,
book0_.updated_at as updated_3_1_,
book0_.author_id as author_i4_1_,
book0_.category as category5_1_,
book0_.deleted as deleted6_1_,
book0_.name as name7_1_,
book0_.publisher_id as publishe8_1_
from
book book0_
where
(
book0_.deleted = 0
)
and book0_.name=?
and book0_.created_at>=?
and book0_.updated_at>=?
Hibernate:
최종 결과도 의도한대로 잘 나왔다.
findByNameRecently : [Book(super=BaseEntity(createdAt=2021-07-09T21:21:42.692030, updatedAt=2021-07-09T21:21:42.692030), id=2, name=삼다수, authorId=null, category=null, deleted=false)]
참고로 @Query에 들어간 문법은 JPQL 문법이다.
@Query(value = "select b from Book b where name = ?1 and createdAt >= ?2 and updatedAt >= ?3")
@Query에 입력된 내용은 아래 글 참고
2.필요한 column만 사용이 가능
1) Tuple 사용
<BookNameAndCategory>
public class BookNameAndCategory {
private String name;
private String category;
}
<BookRepository>
Book에서 name과 cagetory를 가져와서 Tuple로 return해주자.(tuple : map이랑 비슷)
@Query(value = "select b.name as name, b.category as category from Book b")
List<Tuple> findBookNameAndCategory();
(return 을 BookNameAndCategory로 했더니 error가 발생해서 Tuple로 변경했다..)
<Test>
System.out.println("name : category");
bookRepository.findBookNameAndCategory().forEach(tuple -> System.out.println(tuple.get(0) + ":" + tuple.get(1)));
name : category
탐사수수수수:null
삼다수:null
하지만 Tuple을 사용한 방법은 이점이 많지 않다.
2) Interface활용
위에서 작성한 BookNameAndCategory를 interface로 변경
public interface BookNameAndCategory {
//
// private String name;
// private String category;
String getName();
String getCategory();
}
<BookRepository>
return을 BookNameAndCategory interface로 변경하자.
@Query(value = "select b.name as name, b.category as category from Book b")
List<BookNameAndCategory> findBookNameAndCategory();
<Test>
@Test
void queryTest(){
System.out.println("name : category");
bookRepository.findBookNameAndCategory().forEach(
b -> System.out.println(b.getName() + ":" + b.getCategory()));
}
짜잔!
name : category
Hibernate:
select
book0_.name as col_0_0_,
book0_.category as col_1_0_
from
book book0_
where
(
book0_.deleted = 0
)
탐사수수수수:null
삼다수:null
3) dto형식의 class 사용, @Query에 생성자 적용
interface를 class로 다시 변경
public class BookNameAndCategory {
private String name;
private String category;
}
<BookRepository>
@Query(value = "select new com.jpa.bookmanager.repository.dto.BookNameAndCategory(b.name, b.category) from Book b")
List<BookNameAndCategory> findBookNameAndCategory();
<Test>
@Test
void queryTest() {
bookRepository.findBookNameAndCategory().forEach(b-> {
System.out.println(b.getName() + " : " + b.getCategory());
});
}
Hibernate:
select
book0_.name as col_0_0_,
book0_.category as col_1_0_
from
book book0_
where
(
book0_.deleted = 0
)
탐사수수수수 : null
삼다수 : null
4) Paging 기능
PagingAndSortingRepository를 살펴보자.
findAll이라는 method가 있는데, Page형식으로return을 해준다.
즉 자동으로 paging 기능을 제공한다.
Page<T> findAll(Pageable pageable);
이와 마찬가지고 @Query에서도 Paging을 적용할 수 있다.
<BookRepository>
@Query(value = "select new com.jpa.bookmanager.repository.dto.BookNameAndCategory(b.name, b.category) from Book b")
List<BookNameAndCategory> findBookNameAndCategory(Pageable pageable); //message overloading
간단하게 method에 Pageable을 주입을해주면 사용이 가능하다.
아래와 같이 테스트를 할 수 있다.
bookRepository.findBookNameAndCategory((Pageable) PageRequest.of(1,1)).forEach(
bookNameAndCategory -> System.out.println(bookNameAndCategory.getName() + " : "
+ bookNameAndCategory.getCategory()));
하지만 문제가 있다.
findBookNamdAndCategory는 Pageable객체를 주입받는다.
PageRequest는 AbstractPageRequest를 상속받고, AbstractPageRequest는 Pageable의 구현체이다.
따라서 PageRequest를 입력을해도 에러가 발생하지 않아야 할 것같은데 에러가 발생한다.
Pageable로 casting을 해도 ClassCastException이 발생한다.
는...BookRepository에서 Pageable을 import를 잘못했다.
다음의 Pageable을 import해야한다.
import org.springframework.data.domain.Pageable;
위에서 발생한 에러는 java.awt.print를 import하여 발생한 문제였다 ㅋㅋㅋㅋㅋ
3. 정리
@Query는 query method의 이름이 너무 길거나 가독성이 떨어질 때 사용하면 많은 이점이 있다.
또는 column을 전부 조회하는 것이 아니라 일부만 조회하고 싶을 경우 사용할 수 있다.
이 때 return을 받는 방법은 3가지 정도 있다.
1) tuple
2) interface
3) 객체
추가로 paging기능도 제공한다.
4. GitHub : 210709 @Query
'JPA > Custom Query' 카테고리의 다른 글
@Converter (0) | 2021.07.10 |
---|---|
Native Query (0) | 2021.07.10 |