JPA(Java Persistence API) | 자동 생성 쿼리 메소드의 명명 규칙

Spring Data JPA는 Repository 인터페이스에 선언된 메소드를 그 이름에서 쿼리를 생성하고 자동으로 생성 해주는 것이 편리한 기능이 있다. 어떤 명명 규칙이 있는지에 대해서 알아보자.

구문

다음의 3가지 요소를 규칙에 따라 결합된 메소드 이름을 Repository 인터페이스에 선언하면 자동 구현이 가능하다.

  • 접두사 (find ... By, read ... By, query ... By, count ... By, get ... By)
  • 키워드
  • 필드 이름

다음와 같은 엔티티를 예로 사용한다.

Employee.java

@Entity
public class Employee implements Serializable {

    private static final long serialVersionUID = 3453583737318640866L;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String firstname;
    private String lastname;
    private int age;
    @Temporal(TemporalType.DATE)
    private Date hiredAt;
    private Boolean active;
    @ManyToOne
    @JoinTable(
            name="department_employee",
            joinColumns=@JoinColumn(name="employee_id"),
            inverseJoinColumns=@JoinColumn(name="department_code")
        )
    private Department department;

    // 이하 Getter/Setter...

Department.java

@Entity
public class Department implements Serializable {

    private static final long serialVersionUID = -6771704436232452390L;
    @Id
    private String code;
    private String name;
    private boolean flg;
    @OneToMany(fetch=FetchType.LAZY, mappedBy="department")
    private transient List<Employee> employeeList;

    // 이하 Getter/Setter...

메소드 생성 키워드

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 ( %가 뒤에 추가된 매개 변수)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 ( %가 앞에 추가된 매개 변수)
Containing findByFirstnameContaining … where x.firstname like ?1 ( %가 래핑된 매개 변수)
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)

예제

Is / Equals / Not

// SELECT e FROM Employee e
Employee findById(Long id);

// SELECT e FROM Employee e WHERE e.firstname = ?1 
List<Employee> findByFirstnameEquals(String firstname);

// SELECT e FROM Employee e WHERE e.age = ?1
List<Employee> findByAgeIs(int age);

// SELECT e FROM Employee e WHERE e.lastname != ?1
List<Employee> findByLastnameNot(String lastname);

// SELECT e FROM Employee e WHERE e.department = ?1
List<Employee> findByDepartment(Department department);

필드의 뒤에 Equals / Is를 붙이면 일치, Not를 넣으면 불일치 레코드를 검색한다. 대소문자를 구분되는지는 데이터베이스의 데이터 설정 등에 따라 달라진다.

LessThan / GreaterThan

등호 조건을 사용하려고 할 때, 사용하는 키워드는 LessThan, GreaterThan이다.

// SELECT e FROM Employee e WHERE e.age < ?1
List<Employee> findByAgeLessThan(int age);

// SELECT e FROM Employee e WHERE e.age > ?1
List<Employee> findByAgeGreaterThan(int age);

// SELECT e FROM Employee e WHERE e.hired_at > ?1
List<Employee> findByHiredAtGreaterThan(Date date);

경계 값을 포함하면 위의 키워드 Equal을 더하여 GreaterThanEqual, LessThanEqual으로 하면 된다. 또한, Between 키워드으로도 경계 값을 포함한 범위 검색이 가능하다.

// SELECT e FROM Employee WHERE e.age <= ?1
List<Employee> findByAgeLessThanEqual(int age);

// SELECT e FROM Employee WHERE e.age >= ?1
List<Employee> findByAgeGreaterThanEqual(int age);

// SELECT e FROM Employee WHERE e.hired_at BETWEEN ?1 AND ?2
List<Employee> findByHiredAtBetween(Date since, Date until);

추가로, findByFirstnameLessThan(String name)과 같이 String 형식의 필드에 LessThan, GreaterThan를 붙일 수도 있다. 다만, DBMS 종속되긴 하지만…

Like / NotLike / StartingWith / EndingWith / Containing

와일드 카드를 사용한 부분 일치 검색은Like, NotLike, StartingWith, EndingWith, Containing을 사용한다.

// SELECT e FROM Employee WHERE e.firstname LIKE ?1
List<Employee> findByFirstnameLike(int age);

// SELECT e FROM Employee WHERE e.firstname NOT LIKE ?1
List<Employee> findByFirstnameNotLike(String firstname);

// SELECT e FROM Employee WHERE e.lastname LIKE ?1 (앞에 일치)
List<Employee> findByLastnameStartingWith(String lastname);

// SELECT e FROM Employee WHERE e.lastname LIKE ?1 (뒤에 일치)
List<Employee> findByLastnameEndingWith(String lastname);

// SELECT e FROM Employee WHERE e.lastname LIKE ?1 (모두 일치)
List<Employee> findByLastnameContaining(String lastname);

이 중에 StartingWith, EndingWith, Containing 내용은 각각 순서대로 전방 일치, 후방 일치, 부분 일치를 의미한다. 임의의 위치에 와일드 카드를 삽입 한 경우 Like, NotLike을 사용하다.

Between

// SELECT e FROM Employee e WHERE e.age BETWEEN ?1 AND ?2
List<Employee> findByAgeBetween(int olderThan, int youngerThan);

// SELECT e FROM Employee e WHERE e.hiredAt BETWEEN ?1 AND ?2
List<Employee> findByHiredAtBetween(Date since, Date until);

IsNull / (Is)NotNull

// SELECT e FROM Employee WHERE e.hiredAt IS NULL
List<Employee> findByHiredAtIsNull();

// SELECT e FROM Employee WHERE e.hiredAt IS NOT NULL
List<Employee> findByHiredAtIsNotNull();

// SELECT e FROM Employee WHERE e.hiredAt IS NOT NULL
List<Employee> findByHiredAtNotNull();

True / False

// SELECT e FROM Employee WHERE e.active = true
List<Employee> findByActiveTrue();

// SELECT e FROM Employee WHERE e.active = false
List<Employee> findByActiveFalse();

IN

여러개의 값으로 검색하는 경우에는 SQL과 마찬가지로 In을 사용한다.

// SELECT e FROM Employee WHERE e.lastname in ?1
List<Employee> findByLastnameIn(List<String> lastname);

After / Before

날짜 비교는 LessThan(Equal), GreaterThan(Equal)를 사용할 수 있지만, After, Before에서도 표현할 수 있다.

// SELECT e FROM Employee WHERE e.lastname > ?1
List<Employee> findByHiredAtAfter(Date date);

// SELECT e FROM Employee WHERE e.lastname < ?1
List<Employee> findByHiredAtBefore(Date date);

OrderBy

조회 결과를 정렬하려면 OrderBy를 사용한다. 필드 이름은 OrderBy 뒤에 작성한다. 오름차순, 내림차순는 필드 이름 뒤에 작성한다. 그러면 OrderBy + 필드이름 + Asc(Desc)와 같은 형태가 된다.

// SELECT e FROM Employee e WHERE e.lastname = ?1 ORDER BY e.age
List<Employee> findByLastnameOrderByAge(String lastname);

// SELECT e FROM Employee e WHERE e.firstname = ?1 ORDER BY e.firstname ASC
List<Employee> findByFirstnameOrderByHiredAtAsc(String firstname);

여러 필드로 정렬하려면, Asc, Desc을 지정하여 아래와 같이 연결하면 된다.

// SELECT e FROM Employee e WHERE e.lastname = ?1 ORDER BY e.age ASC, e.firstname DESC
List<Employee> findByLastnameOrderByAgeAscFirstnameDesc(String lastname);

Top / First

find 이후에 First 또는 Top을 붙이는 것으로 조회 건수를 줄일 수 있다.

Employee findFirstByLastname(String lastname);

Employee findTopByLastname(String lastname);

List<Employee> findFirst3ByActiveTrueOrderByAgeDesc();

List<Employee> findTop5ByHiredAtIs(Date date);

키워드를 결합

And, Or으로 조건의 조합 수 있다.

// SELECT e FROM Employee e WHERE e.age = ?1, e.active = true
List<Employee> findByAgeIsAndActiveTrue(int age);

// SELECT e FROM Employee e WHERE e.lastname LIKE ?1 OR e.lastname LIKE ?2
List<Employee> findByLastnameStartingWithOrFirstnameEndingWith(String lastname, String firstname);

// SELECT e FROM Employee e WHERE e.age BETWEEN ?1 AND ?2 AND e.department = ?3
List<Employee> findByAgeBetweenAndDepartmentIs(int startAge, int endAge, Department department);

참조