[Spring JPA] Query Method

SeongWon Oh·2021년 9월 16일
3

Spring Framework

목록 보기
14/33
post-thumbnail
post-custom-banner

Query Method란?

JPA(Java Persistence API)란 자바에서 사용하고 있는 ORM의 표준으로 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 일을 한다.
스프링 데이터 JPA는 메소드 이름으로 쿼리 생성을 하는 쿼리 메소드 기능을 제공하는데 오늘은 이 Query Method에 대해 알아보고자 한다.

📌 쿼리메서드는 메서드의 이름을 분석해서 JPQL쿼리를 실행한다!
📌 쿼리 메서드를 활용하면 쉽게 쿼리문을 만들어 사용할 수 있다.


Query Method사용하기

Query method는 JpaRepository를 상속하는 것 만으로도 Jpa의 method들을 사용할 수 있다.
JpaRepository<> 에서 괄호에는 첫번째에는 Jpa로 사용할 entity(class), 두번째는 해당 class의 pk타입이다.

public interface UserRepository extends JpaRepository<User, Long> {}

코드 예제에 사용될 Entity(class)

@Entity // entity는 primary key가 꼭 필요하다. (@Id로 지정)
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@Builder
public class User {
    @Id // PK지정
    @GeneratedValue // entity를 만들때 자동으로 순차적으로 생성해줌
    private Long id;

    @NonNull
    private String name;

    @NonNull
    private String email;

    private LocalDateTime createdAt; // 생성된 시간
    private LocalDateTime updatedAt; // 업데이트된 시간
}

Select Query

  • 아래의 method들은 모두 select query의 일을 하는 method들이다.
  • 이름은 find(get,ready,,,,) By가 핵심이며 두 문자 뒤에 구분할 수 있는 변수명을 넣어 구분하며 만들어주면 된다. 그러면 자동으로 Select Qeury Method가 만들어진다.
  • 아래의 findBy, getBy, readBy, queryBy, searchBy, streamBy, find (Entity 명) By 는 모두 Select의 일을 하는 keyword이며 개발자는 이중에서 자신이 가독성이 높다고 생각되는 것을 이용하면 된다.
    User findByEmail(String email);
    User getByEmail(String email);
    User readByEmail(String email);
    User queryByEmail(String email);
    User searchByEmail(String email);
    User streamByEmail(String email);
    User findUserByEmail(String email);

Return type은 List, Set, Object등의 여러 타입으로 할 수 있으며 JPA가 데이터를 읽어오고 return type에 맞춰서 데이터를 return해준다.
단 데이터가 여러개인데 User와 같이 단일 객체로 return하면 오류가 발생한다.


First, Top

  • First, Top은 둘 다 데이터에서 출력할 데이터의 수를 정해서 리턴하게 하는 keyword이다.
  • 기본적으로 keyword뒤에 숫자를 붙이지 않으면 가장 상위 데이터 하나가 리턴되며(Default = 1) 이름 뒤에 출력할 데이터의 수를 붙이면 해당 수만큼 리턴한다.
    List<User> findFirst1ByName(String name);	// 상위 1개의 데이터 return
    List<User> findTop2ByName(String name);   // 상위 2개의 데이터 return
    List<User> findLast1ByName(String name); // Last filter는 없다

And, Or

Query문에서 and, or을 사용하고 싶은 경우 method안에 And or을 넣어준다

    List<User> findByNameAndEmail(String name, String email);
    List<User> findByNameOrEmail(String name, String email);

After, Before, GreaterThan, LessThan, Between

  • After, Before, GreaterThan, LessThan은 값 비교를 해주는 keyword이다.
    After, GreaterThan은 특정 날짜/값 이후(또는 큰것)에 발생한 것을 조회하게 해주는 keyword이다.
    Before, LessThan은 특정 날짜/값 이전(또는 작은것)을 조회하게 해주는 keyword이다.
  • After, GreaterThan과 Before, LessThan은 서로 같은 기능을 하는 필터지만 가독성을 위해 After, Before은 날짜에만 사용하는 것을 추천한다.
  • 또한 조건을 걸다보면 초과, 미만 뿐만 아니라 이상, 이하의 조건도 필요할 것이다. 그럴 경우에는 GreaterThanEqual과 같이 Equal을 붙여주면 된다.
  • ~이상 ~이하의 의미를 갖는 Between keyword도 존재한다. Between은 parameter를 2개를 받으며 Between은 위의 After, Before, GreaterThan, LessThan과 다르게 해당 값들도 포함한다.(이상, 이하를 의미한다.)
   List<User> findByCreatedAtAfter (LocalDateTime lastDay);  
   // CreatedAt이 lastDay이후인 데이터들 return (yesterDay미포함)
   List<User> findByIdAfter(Long id);  
   // input id보다 큰 id를 가진 데이터들을 return (id 미포함)
    
    List<User> findByCreatedAtGreaterThan (LocalDateTime yesterday);  
    // CreatedAt이 lastDay이후인 데이터들 return (yesterDay미포함)
    List<User> findByCreatedAtGreaterThanEqual (LocalDateTime yesterday); 
    // CreatedAt이 lastDay이후인 데이터들 return (yesterDay포함)


    List<User> findByCreatedAtBetween(LocalDateTime yesterday, LocalDateTime tomorrow); 
    // CreatedAt이 lastDay와 tomorrow사이 값인 데이터들 return (yesterDay, tomorrow포함)
    List<User> findByIdBetween(Long id1, Long id2); 
    // id가 id1이상, id2이하인 데이터들 return 

After, Before, GreaterThan, LessThan은 초과 미만을 의미하며 Between은 이상, 이하를 의미하는 것을 헷갈리면 안된다!!!


is(Not)Empty, is(Not)Null

  • isNull은 해당 값에 Null값이 있는지 체크하는 keyword이다.
  • NotEmpty는 String과 같은 문자열이 비어있는지 체크가 아닌 Collection type의 변수가 not empty(비어있는지)를 체크한다.
    List<User> findByIdIsNotNull();  // Id값에 Null값이 없는지?
    List<User> findByAddressIsNotEmpty();

In

  • In절은 query문에서 in절이기 때문에 parameter로 iterater type인 list가 들어가게 된다. generic type이 들어가야하는 <>에는 검색하는 column의 data type을 넣는다.
  • 일반적으로 in절은 다른 query의 결과값을 다시 query에 넣어야 할 때 사용한다!
  • in절을 사용할 때는 과부하 걸리는 것을 예방하기 위해서 다른 query문의 결과로 얼마나 많은 데이터들이 나올지 사전에 검토를 하고 사용하는게 좋다.
 List<User> findByNameIn(List<String> name);

StartingWith/EndingWith/Contains

  • 해당 keyword들은 문자열에 사용하며 해당 문자열로 시작하는지, 끝나는지, 포함하는지를 filtering할 때 사용한다.
  • contains("rti")와 like("%rti%")는 같은 것이다
    List<User> findByNameStartingWith(String name);
    List<User> findByNameEndingWith(String name);
    List<User> findByNameContains(String name);
    List<User> findByNameLike(String name);

Is, Equals

  • Is는 해당 값을 가진 data를 찾는 keyword로 Is, Equals, 또는 아무런 키워드를 넣지 않으면 Is로 보게된다.
  • 아래 3개의 method는 모두 parameter의 name과 동등한 이름의 데이터들을 출력하는 method이다.
    Set<User> findUserByNameIs(String name);
    Set<User> findUserByName(String name);
    Set<User> findUserByNameEquals(String name);

Sorting

  • Sorting은 조건에 따라 데이터의 정렬을 해주는 Keyword이다.
  • Desc/ Asc로 정렬한다.
  • 여러개의 조건으로 find하는 경우는 And를 사용하였으나 정렬 조건으로 여러개의 값을 사용하는 경우는 And를 사용하지 않고 조건을 이어서 붙인다.
  • Sort method를 사용하여 정렬시킬 수도 있다.
    List<User> findTop1ByNameOrderByIdDesc(String name); 
    // Id로 내림차순으로 정렬 후 입력 name과 같은 것의 맨 위의 있는 값을 뽑아온다.
    
    List<User> findFirst2ByNameOrderByIdDescEmailAsc(String name);
    // 여러개의 조건으로 find하는 경우는 And를 사용하였으나 정렬 조건으로 여러개의 값을 사용하는 경우는 And를 사용하지 않고 조건을 이어서 붙인다.

    List<User> findFirstByName(String name, Sort sort);

Test Example

        System.out.println("findTop1ByName : "+ userRepository.findTop1ByName("martin"));

        System.out.println("findTop1ByNameOrderByIdDesc : "+ userRepository.findTop1ByNameOrderByIdDesc("martin")); // LastBy를 구현한 method(가장 하위의 데이터 출력)
        System.out.println("findFirstByNameOrderByIdDescEmailAsc : "+ userRepository.findFirst2ByNameOrderByIdDescEmailAsc("martin"));


        // Sort Parameter를 사용하여 정렬
        // 한가지 조건을 사용하는 경우
        System.out.println("findFirstByName with Sort parameter : "+ userRepository.findFirstByName("martin", Sort.by(Sort.Order.desc("id"))));
        // 두가지 조건을 사용하는 경우
        System.out.println("findFirstByName with Sort parameter : "+ userRepository.findFirstByName("martin", Sort.by(Sort.Order.desc("id"), Sort.Order.asc("email"))));
        // 코드의 가독성을 높이기 위해서는 아래와 같이 sort method를 만들고 호출을 하며 사용할 수도 있다.
        System.out.println("findFirstByName with Sort parameter : "+ userRepository.findFirstByName("martin", getSort()));
                
        .....       
        
   private Sort getSort() {
        return Sort.by(
                Sort.Order.desc("id"),
                Sort.Order.asc("email"),
                Sort.Order.desc("createdAt"),
                Sort.Order.desc("updatedAt")
        );
    }

Sort Parameter를 사용하여 정렬하는 경우

  • Sort Parameter를 사용하지 않는 방법으로 Sorting을 하면 조건이 많아질수록 메소드의 이름 길이도 길어지며 코드의 가독성에도 그리 좋지 않다.
  • 또한 조건에 따라 여러 method를 만들어야하는데 Sort method를 사용할 경우 하나의 method로 여러 sort조건을 줘서 사용할 수 있다. 그래서 아래와 같이 Sorting Method를 사용하는 것을 추천한다.
  • 하지만 반대로 같은 Sorting방식으로 사용되는 경우가 많은 경우 위의 Keyword를 사용한 method를 사용하는게 가독성 측면에서 더 좋을 수 있다.
  • 개발자는 같은 결과를 내더라도 여러 코딩 방식이 있다는 것을 인지하고 어떤 것이 가독성이 더 좋을지 생각하며 코딩해야한다.


쿼리 메서드 필터 조건

KeywordSampleJPQL snippet
DistinctfindDistinctByLastnameAndFirstnameselect distinct …​ where x.lastname = ?1 and x.firstname = ?2
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is, EqualsfindByFirstname,findByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNull, NullfindByAge(Is)Null… where x.age is null
IsNotNull, NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1 (parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection ages)… where x.age not in ?1
TruefindByActiveTrue()… where x.active = true
FalsefindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstname) = UPPER(?1)

Reference

profile
블로그 이전했습니다. -> https://seongwon.dev/
post-custom-banner

0개의 댓글