Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
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
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

@Query 본문

JPA/Custom Query

@Query

Seung__ 2021. 7. 9. 23:25

@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를 활용해 보자.

 

 

Entity Listener : 4(실제 사용하는 방법)

좀 더 현실적으로 코드를 수정해보자. createdAt, updatedAt에 각각 @CreatedDate, @LastModifidedDate 붙여주고, @AuditingEntityListener를 활용하여 간단하게 적용할 수 있었다. 하지만 각 class의 createdAt,..

bsh-developer.tistory.com

이전에 작성해둔 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에 대한 내용은 아래 글 참고

 

@Entity 속성 2

1. SQL이란? SQL 종류 들어가기에 앖서서 SQL(Structed Query Language, 구조적 질의언어)에 대해서 간략하게 알아보자. SQL은 관계형 DB의 관리시스템의 DATA를 관리하기 위한 만들어진 특수 목적의 프로그래

bsh-developer.tistory.com

 

테스트 결과 잘 불러와졌다

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에 입력된 내용은 아래 글 참고

 

 

JPQL : 동적 parameter mapping 방법

JPQL : Java Persistence Query Language, table이 아닌 JPA Entity를 기반으로하는 query생성. 따라서 createdAt의 경우 table의 created_at이 아니라 실제 field에서 사용한 이름을 사용했음. query를 만들 때..

bsh-developer.tistory.com

 

 

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


 

bsh6463/BookManager

Contribute to bsh6463/BookManager development by creating an account on GitHub.

github.com

 

'JPA > Custom Query' 카테고리의 다른 글

@Converter  (0) 2021.07.10
Native Query  (0) 2021.07.10
Comments