Spring-data-jpa 를 사용할 때 조건 조회를 할 수 있는 여러 방법들에 대해 알아볼 예정입니다. 그 첫번째 가장 간단하게 조건 조회를 할 수 있는 Query methods
에 대해서 알아보겠습니다.
Query Methods 는 method 이름을 기반으로 자동으로 쿼리를 생성해주는 방식을 말합니다. method 이름을 특정 규칙에 따라 작성하면 SQL 없이 조건 조회를 구현할 수 있습니다.
Query Methods 는 JpaRepository 를 상속받은 어느 Repository 에서나 사용할 수 있습니다.
domain.member.MemberRepository
@Repository
public interface MemberRepository extends JpaRepository<Member, String> {
//이하 생략..
}
Entity 내 필드들을 조건으로 활용할 수 있으며, 연관관계에 있는 Entity 의 필드도 조건으로 처리할 수 있습니다. 그럼 이전에 작성했던 Member Entity 를 활용하여 조건 조회를 해보도록 하겠습니다.
entity.Member
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table
@Entity(name = "member")
public class Member extends BaseEntity implements Persistable<String> {
@Id
@Column(name = "member_id")
private String memberId;
@Column(name = "member_pw")
private String password;
@Column(name = "member_nm")
private String memberName;
@Column(name = "use_yn")
@Enumerated(EnumType.STRING)
private UseYn useYn;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "authority_cd")
private Authority authority;
//이하 생략..
}
위와 같이 Member Entity 에는 memberId, memberName 등의 필드가 있고, Authority Entity 와 연관관계가 설정되어 있습니다. 아, BaseEntity도 상속을 받고 있네요.
entity.base.BaseEntity
@MappedSuperclass
@Getter
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {
@Column(name = "update_dt")
@Temporal(TemporalType.TIMESTAMP)
@LastModifiedDate
private LocalDateTime updateDate;
@Column(name = "create_dt", updatable = false)
@Temporal(TemporalType.TIMESTAMP)
@CreatedDate
private LocalDateTime createDate;
}
자, 그럼 위의 조건들을 가지고 Query Methods 를 작성해보겠습니다.
@Repository
public interface MemberRepository extends JpaRepository<Member, String> {
// memberName 으로 조회
List<Member> findByMemberName(String memberName);
// memberId (equal) 와 memberName (like) 을 And 조건으로 조회
List<Member> findByMemberIdAndMemberNameLike(String memberId, String memberName);
// memberId (equal) 와 memberName (like) 을 Or 조건으로 조회
List<Member> findByMemberIdOrMemberNameLike(String memberId, String memberName);
// Enum type 조회
List<Member> findByUseYn(UseYn useYn);
// 연관관계인 Authority 의 authorityName 으로 조회
List<Member> findByAuthorityAuthorityName(String authorityName);
// BaseEntity 의 createDate 를 between 조건으로 조회
List<Member> findByCreateDateBetween(LocalDateTime startDate, LocalDateTime endDate);
}
어느정도 감이 오시나요? 필드명과 키워드를 조합하여 CamelCase 로 Method 이름을 작성하면 해당 조회 조건으로 조회가 가능합니다. 이번 주제에 맞게 Query method 만 보여드렸지만 N+1 문제 해결해 성능을 최적화 하기 위한 @EntityGraph는 모두 추가해야 합니다.
Query method 에서는 어떤 keyword를 제공하는지 spring-data-jpa reference에서 확인해보겠습니다.
Keyword | Sample | JPQL |
---|---|---|
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 is 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<Age> ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection<Age> 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) |
조회 조건을 포함, 정렬 조건도 OrderBy
를 통해서 제공하는 것을 알수 있습니다.
find...by 이외에도 존재 여부 확인이나 개수를 조회할 수 있는 키워드도 존재합니다.
Keyword | Description | Sample |
---|---|---|
find…By, read…By, get…By, query…By, search…By, stream…By | find…By 이외에도 다양한 키워드로 가능. | List<User> findByLastname(String lastname); |
exists…By | 존재 여부를 boolean type으로 반환. | boolean existsByEmail(String email); |
count…By | 개수를 숫자로 반환. | long countByAgeGreaterThan(int age); |
delete…By, remove…By | 삭제 쿼리 메서드. void or 삭제된 개수 반환. | void deleteById(Long id); long removeByAgeLessThan(int age); |
…First<number>…, …Top<number>… | 쿼리 결과를 처음 <number>개의 결과로 제한. | User findFirstByOrderByLastnameAsc(); List<User> findTop3ByAge(int age); |
…Distinct… | 고유한 결과만 반환하기 위해 distinct 쿼리를 사용. | List<User> findDistinctByLastname(String lastname); |
이 외에 isEmtpy
, Near
, StartingWith
, Within
등이 있지만, Database 에서 지원하는지 확인하고 사용해야 합니다.
Query Methods 는 간단하게 키워드를 조합해 쿼리문을 직접 작성하지 않고도 원하는 쿼리를 수행할 수 있습니다. 컴파일 시점에 필드 타입이 검증되므로, 문자열 기반 쿼리보다 타입 오류가 발생할 가능성이 낮습니다. 이런 장점에 비해 복잡한 쿼리를 처리하기 어렵고, 메서드명이 길어질수록 가독성이 떨어지기 때문에 단순하고 간단한 쿼리를 작성해야 할 경우에 사용하도록 합니다.