쿼리 메소드 기능(1)
- JPA
- 2022. 4. 1. 01:21
springData에서는 인테페이스를 통해서 쿼리를 작성한다.
public interface MemberRepository extends JpaRepository<Member, Long> {
}
이상하다.
분명히 아무것도 없는데 이렇게만해도 기본적인 insert라던지 delete update가 전부 된다.
(update같은 경우는 jpa의 특별한 방법을 사용하고 있다.)
@Override
public List<T> findAll() {
return getQuery(null, Sort.unsorted()).getResultList();
}
요런식으로 미리 구현되어 있다. 그래서 비슷하게 만들려고 했지만, 잘 만들어지지 않았다. 이걸 만드는게 중요한게 아니기 때문에 넘어가자.
@Override
public T getById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
return em.getReference(getDomainClass(), id);
}
이렇게 id(pk)기준으로 검색되어진다.
모든 테이블이 pk가 존재하는건 아니지만 거의 모든 테이블은 pk가 있기 때문에 저걸로도 충분히 검색이 가능하다.
근데 모든 검색을 pk로 할 수 있는건 아니다.
그럴때는 어떻게 해야 할까?
여러가지 방법이 있는데
가장 간단한 방법으로는 메소드 이름을 가지고 작성하는 방법이다.
왼쪽이 sql에서 사용되어지는 키워드고 가운데가 예시 가장 오른쪽이 쿼리다.
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<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) |
그 중에서 Distinct를 가지고 설명하면 좋을것 같다.
findDistinctByLastnameAndFirstname
select distinct … where x.lastname = ?1 and x.firstname = ?2
뭔지는 모르겠지만
일단 find를 붙이고 키워드 By(where) 조건문
이런식으로 동작하는 것 같다.
쿼리를 보면 ... 이라고 되어있는데 이게 by까지인지 다른것도 포함인지는 확실히 알기는 어려운것 같다. (확인해보겠지만 지금은 그렇다는 이야기다.)
List<Member> findDistinctAaaByAndUserName(String userName);
요런식으로 작성이 가능하다.
결론적이면
find + 키워드 + (아무거나 입력 가능) + by + 조건문
- 키워드는 0개 이상 사용할 수 있다.
- ()는 굳이 입력할 필요가 없다.
- 그 다음은 자연스럽게? 작성하면 된다.
근데 이걸 계속 사용하는건 너무 길어져서 읽기가 오히려 어려워질 것 같다.
그럴때는 어떻게 해야 할까?
NamedQuery
JPA에서는 NameQuery라는것이 존재한다.
이것을 사용하는 방법은 간단하다.
@NamedQuery(
name = "Member.findByUserName",
query = "select m from Member m where m.userName = :userName"
)
public class Member {
요런식으로 엔티티에 정의를 하면 된다.
여러개도 가능하다.
@NamedQueries(
value = {
@NamedQuery(
name = "Member.findByUserName",
query = "select m from Member m where m.userName = :userName"
),
@NamedQuery(
name = "Member.findByUserName",
query = "select m from Member m where m.userName = :userName")
}
)
지금은 뭐 일부러 이렇게 할 수 있다는것만 보여줄려구 같은걸로 했다.
이렇게 실행하면 duplicate error가 발생하므로 저렇게는 사용하면 안된다.
아무튼 이것을 spring-data-jpa에서도 사용이 가능하다.
public List<Member> findByUserName(String username) {
return em.createNamedQuery("Member.findByUserName", Member.class)
.setParameter("userName", username).getResultList();
}
기존에는 요렇게 작성해서 nameQuery를 사용했지만,
이제는 이렇게 할 필요가 없다.
단지
인터페이스로
@Query(name = "Member.findByUserName")
List<Member> findByUserName(@Param("userName") String userName);
요렇게 선언하면 끝난다고 한다.
만약 @Query를 없애면 어떻게 될까?
없어도 동작은 한다고 한다. 그러면 기존에 메소드이름과의 우선순위는 어떻게 되는 걸까?
우선순위는
NameQuery -> 메서드 이름 지정이라고 한다.
엔티티에 쿼리를 박아서 사용하는것이 조금 그렇다. 더 좋은 방법은 없는걸까?
@Query , dto데이터 조회
다행히도? 엔티티에 쿼리를 박지 않고 사용하는 방법이 존재한다.
위에서 @Query안에 이름을 작성했는데
요 부분에 쿼리를 작성하는것도 가능하다.
@Query("select m.userName from Member m")
List<String> findUsernameList();
요런식으로 작성하게 되면 db안에 모든 userName이 검색이 되어진다.
갑자기 궁금해졌다. 저게 String이 아닌 Inteager으로 하면 어떻게 되어질까?
요렇게 익셉션이 발생된다.
Caused by: java.lang.NumberFormatException: For input string: "AAA"
이것으로 알 수 있는 사실은 리턴은 어떻게 하든 상관없다는 뜻이 된다. 하지만 포맷팅이 실패하게 되면 익셉션이 발생하는 사실을 알게 되었다.
JPA에서 DTO를 검색하는 방법에 대해 학습을 한적이 있다.
여기에서도 똑같이 @Query에 적용하면 된다.
@Query("select new com.datajpa.dto.MemberDto(m.id, m.userName, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();
JPA에서 학습했듯이 new 생성자를 하게 되는데 이 점이 별로 좋아보이지 않는다.
왜냐하면 모든 패키지와 클래스를 모두 작성을 해야하기 때문에 쿼리가 쓸데없이? 길게만 느껴진다.
이거는 쿼리DSL배우면 해결이 된다고 하는데;;
당연한 이야기지만 이것도 dto클래스로 만들어야 한다.
public class MemberDto {
private Long id;
private String username;
private String teamName;
public MemberDto(Long id, String username, String teamName) {
this.id = id;
this.username = username;
this.teamName = teamName;
}
}
보면 생성자를 요렇게 만드는 것을 확인 할 수 있다.
파라미터 바인딩
바인딩을 하는 방법은 2가지..
위치 기반이랑 문자 기반
반드시 알아야 될 사실은 위치 기반은 사용하면 안된다는 점이다.
그 이유는 위치가 변경될 경우 엄청난 일이 발생할 수 도 있기 때문이다.
@Query("select m.age from Member m where m.userName = :userName")
List<String> findUserageList(@Param(value = "userName") String userName);
방법은 요런식으로 하면 된다.
특히 IN절을 사용할때 굉장히 편함을 느꼈다. 물론 메서드 이름으로 충분히 가능하지만 이렇게 받는것도 나쁘지는 않다고 생각한다.
@Query("select m.age from Member m where m.userName IN :userNameList")
List<String> findUserageList(@Param(value = "userNameList") List<String> userNameList);
select member0_.age as col_0_0_ from member member0_ where member0_.user_name in ('AAA' , 'BBB');
나는 분명 ()를 사용하지 않았지만, JPA에서 알아서 ()를 생성해주었다.
마이바티스를 사용할때 반복문을 돌릴려고 foreach검색해서 사용한것이 생각난다.맨날 까먹고...;; ㅜㅜ 그래서 검색하고 귀찮아서 안할때도 있는데 JPA이 쿼리짜는게 훨씬더 편할지도..;
반환 타입
void | Denotes no return value. |
Primitives | Java primitives. |
Wrapper types | Java wrapper types. |
T | A unique entity. Expects the query method to return one result at most. If no result is found, null is returned. More than one result triggers an IncorrectResultSizeDataAccessException. |
Iterator<T> | An Iterator. |
Collection<T> | A Collection. |
List<T> | A List. |
Optional<T> | A Java 8 or Guava Optional. Expects the query method to return one result at most. If no result is found, Optional.empty() or Optional.absent() is returned. More than one result triggers an IncorrectResultSizeDataAccessException. |
Option<T> | Either a Scala or Vavr Option type. Semantically the same behavior as Java 8’s Optional, described earlier. |
Stream<T> | A Java 8 Stream. |
Streamable<T> | A convenience extension of Iterable that directy exposes methods to stream, map and filter results, concatenate them etc. |
Types that implement Streamable and take a Streamable constructor or factory method argument | Types that expose a constructor or ….of(…)/….valueOf(…) factory method taking a Streamable as argument. See Returning Custom Streamable Wrapper Types for details. |
Vavr Seq, List, Map, Set | Vavr collection types. See Support for Vavr Collections for details. |
Future<T> | A Future. Expects a method to be annotated with @Async and requires Spring’s asynchronous method execution capability to be enabled. |
CompletableFuture<T> | A Java 8 CompletableFuture. Expects a method to be annotated with @Async and requires Spring’s asynchronous method execution capability to be enabled. |
ListenableFuture | A org.springframework.util.concurrent.ListenableFuture. Expects a method to be annotated with @Async and requires Spring’s asynchronous method execution capability to be enabled. |
Slice<T> | A sized chunk of data with an indication of whether there is more data available. Requires a Pageable method parameter. |
Page<T> | A Slice with additional information, such as the total number of results. Requires a Pageable method parameter. |
GeoResult<T> | A result entry with additional information, such as the distance to a reference location. |
GeoResults<T> | A list of GeoResult<T> with additional information, such as the average distance to a reference location. |
GeoPage<T> | A Page with GeoResult<T>, such as the average distance to a reference location. |
Mono<T> | A Project Reactor Mono emitting zero or one element using reactive repositories. Expects the query method to return one result at most. If no result is found, Mono.empty() is returned. More than one result triggers an IncorrectResultSizeDataAccessException. |
Flux<T> | A Project Reactor Flux emitting zero, one, or many elements using reactive repositories. Queries returning Flux can emit also an infinite number of elements. |
Single<T> | A RxJava Single emitting a single element using reactive repositories. Expects the query method to return one result at most. If no result is found, Mono.empty() is returned. More than one result triggers an IncorrectResultSizeDataAccessException. |
Maybe<T> | A RxJava Maybe emitting zero or one element using reactive repositories. Expects the query method to return one result at most. If no result is found, Mono.empty() is returned. More than one result triggers an IncorrectResultSizeDataAccessException. |
Flowable<T> | A RxJava Flowable emitting zero, one, or many elements using reactive repositories. Queries returning Flowable can emit also an infinite number of elements. |
spring data jpa는 여러가지 반환타입이 존재한다.
특징으로는
리스트로 조회 했을때, 데이터가 존재하지 않으면 빈 배열을 리턴하고
객체로 조회 했을때는 null로 들어온다.
특히 한개를 조회하는 객체(T)나 optional같은 경우 여러개가 조회한다면 예외를 터트리는데 이때 터트리는 예외는
JPA에서 터트리는 예외가 아니라 스프링 데이터의 예외인점을 알아야 한다.
그 이유는 nosql 이나 spring jdbc data같은 환경에서도? 같은 예외를 던저서 관리를 하기 위함이다.
'JPA' 카테고리의 다른 글
쿼리 메소드 기능(2) (0) | 2022.04.05 |
---|---|
JPA 스터디 7주차 (0) | 2022.02.26 |
JPA 스터디 6주차 (1) | 2022.02.20 |
JPA 스터디 5주차 (0) | 2022.02.11 |
JPA 스터디 4주차 (0) | 2022.02.02 |