JPA(Java Persistence API)란 자바에서 사용하고 있는 ORM의 표준으로 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 일을 한다.
스프링 데이터 JPA는 메소드 이름으로 쿼리 생성을 하는 쿼리 메소드 기능을 제공하는데 오늘은 이 Query Method에 대해 알아보고자 한다.
📌 쿼리메서드는 메서드의 이름을 분석해서 JPQL쿼리를 실행한다!
📌 쿼리 메서드를 활용하면 쉽게 쿼리문을 만들어 사용할 수 있다.
Query method는 JpaRepository를 상속하는 것 만으로도 Jpa의 method들을 사용할 수 있다.
JpaRepository<> 에서 괄호에는 첫번째에는 Jpa로 사용할 entity(class), 두번째는 해당 class의 pk타입이다.
public interface UserRepository extends JpaRepository<User, Long> {}
@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; // 업데이트된 시간
}
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하면 오류가 발생한다.
List<User> findFirst1ByName(String name); // 상위 1개의 데이터 return
List<User> findTop2ByName(String name); // 상위 2개의 데이터 return
List<User> findLast1ByName(String name); // Last filter는 없다
Query문에서 and, or을 사용하고 싶은 경우 method안에 And or을 넣어준다
List<User> findByNameAndEmail(String name, String email);
List<User> findByNameOrEmail(String name, String email);
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은 이상, 이하를 의미하는 것을 헷갈리면 안된다!!!
List<User> findByIdIsNotNull(); // Id값에 Null값이 없는지?
List<User> findByAddressIsNotEmpty();
List<User> findByNameIn(List<String> name);
List<User> findByNameStartingWith(String name);
List<User> findByNameEndingWith(String name);
List<User> findByNameContains(String name);
List<User> findByNameLike(String name);
Set<User> findUserByNameIs(String name);
Set<User> findUserByName(String name);
Set<User> findUserByNameEquals(String name);
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를 사용하는게 가독성 측면에서 더 좋을 수 있다.
- 개발자는 같은 결과를 내더라도 여러 코딩 방식이 있다는 것을 인지하고 어떤 것이 가독성이 더 좋을지 생각하며 코딩해야한다.
Keyword | Sample | JPQL snippet |
---|---|---|
Distinct | findDistinctByLastnameAndFirstname | select distinct … where x.lastname = ?1 and x.firstname = ?2 |
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is, Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull, Null | findByAge(Is)Null | … where x.age is null |
IsNotNull, NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection ages) | … where x.age not in ?1 |
True | findByActiveTrue() | … where x.active = true |
False | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstname) = UPPER(?1) |