JPA Search By Condition(1) - Query Methods

GEONNY·2024년 8월 22일
0
post-thumbnail

Spring-data-jpa 를 사용할 때 조건 조회를 할 수 있는 여러 방법들에 대해 알아볼 예정입니다. 그 첫번째 가장 간단하게 조건 조회를 할 수 있는 Query methods 에 대해서 알아보겠습니다.

📌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 methods keyword

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 에서 지원하는지 확인하고 사용해야 합니다.

📌Conclution

Query Methods 는 간단하게 키워드를 조합해 쿼리문을 직접 작성하지 않고도 원하는 쿼리를 수행할 수 있습니다. 컴파일 시점에 필드 타입이 검증되므로, 문자열 기반 쿼리보다 타입 오류가 발생할 가능성이 낮습니다. 이런 장점에 비해 복잡한 쿼리를 처리하기 어렵고, 메서드명이 길어질수록 가독성이 떨어지기 때문에 단순하고 간단한 쿼리를 작성해야 할 경우에 사용하도록 합니다.

profile
Back-end developer

0개의 댓글