JPA를 공부하고는 있지만, 아직 실무에서 쓰질 않다보니 JPA를 쓸때마다 항상 까먹고... 검색하고... 허비하는 시간이 많아져서 포스팅한다. 지금당장 구현원리까지는 몰라도, 사용하는데는 불편함이 없도록 하자!
SpringDataJPA는 눈에 띄는 몇가지 기능을 제공하는데, 바로 구현체 없이 인터페이스 선언만으로 쿼리를 사용 할 수 있는 점이다.
인터페이스만 작성하면 실행시점에 SpringDataJPA가 구현 객체를 동적으로 생성해서 주입한다.
그럼 거두절미하고 바로 구현방법을 코드로 알아보자!
@Repository
public interface PostRepositroy extends JpaRepository<Post, Long> {
}
이게 끝이다. ㅎㅎ JpaRepository를 상속받은 인터페이스를 선언만 해주면 된다. 단, 제네릭은 해당 Entitiy와, 해당 Entitiy를 식별할 식별자 타입을 넣어주면 된다. 여기선 Post 엔티티와 해당 엔티티를 식별할때 Long타입 Id로 식별하므로, Post, Long 타입을 넘겨주었다.
그런데!..... JpaRepository는 식별자를 통한 저장,조회 등만을 지원해준다. 그런데 사실 쿼리가 PK로만 모든것을 하지는 않는데, 이러면 사실 대부분의 쿼리는 결국 직접 작성하게 된다. 그러면 크~게 생산성을 해결해주는건 아니지 않나? 싶지만!... 놀랍게도 SpringDataJPA는 메소드 이름을 분석해서 JPQL을 실행해준다. 물론 규칙은 존재한다.
다음의 3가지 요소를 규칙에 따라 결합된 메소드 이름을 Repository 인터페이스에 선언하면 자동 구현이 가능하다.
- 접두사 (find ... By, read ... By, query ... By, count ... By, get ... By)
- 키워드
- 필드 이름
메소드 생성 키워드
- 너무 많아서 토글!
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) |
상당히 유용한 기능이다. 그런데 그럼에도 아직 쿼리들을 커버하기엔 부족하다. 결국 사용자가 직접 작성한 쿼리가 쓰일 수 밖에 없다. 그렇다고 직접 구현하자니, 인터페이스가 제공하는 모든 기능을 다 재정의 해야한다. 이 부분을 SpringDataJPA는 우회해서 사용할 수 있게 해준다.
1. 먼저 직접 구현할 메소드를 위해 사용자 정의 인터페이스를 작성한다. 이름은 자유롭게 지으면 된다.
public interface PostRepositoryCustom {
List<Post> getList(PostSearch postSearch);
}
2. 다음으로 위에서 작성한 인터페이스를 구현해야 하는데, 이때 클래스이름을 짓는 규칙이 있다. Repository인터페이스 명 +Impl로 지어야 한다. 이렇게 하면 SpringDataJPA가 사용자 정의 구현 클래스로 인식한다.
public class PostRepositoryCustomImpl implements PostRepositoryCustom{
private final JPAQueryFactory jpaQueryFactory;
@Override
public List<Post> getList(PostSearch postSearch) {
return jpaQueryFactory.selectFrom(QPost.post)
.limit(postSearch.getPageSize())
.offset(postSearch.getOffset())
.fetch();
}
}
3. 그리고 마지막으로, Repository 인터페이스에서 사용자 정의 인터페이스를 상속 받으면 된다.
@Repository
public interface PostRepositroy extends JpaRepository<Post, Long>, PostRepositoryCustom {
}
급하게 쓰느라 여기까지만 작성한다.
'프로그래밍 > Java' 카테고리의 다른 글
Builder패턴이란? (0) | 2022.08.18 |
---|---|
Java GC 1차개정본 (0) | 2022.06.25 |
Dynamic Proxy 직접 구현해보기-2 (0) | 2021.08.10 |
Dynamic Proxy직접 구현해보기-1 (0) | 2021.08.10 |
Comparator의 동작방식 (0) | 2021.07.27 |