실전! 스프링 데이터 JPA - 섹션4. 쿼리 메소드 기능
CS/김영한 스프링 강의

실전! 스프링 데이터 JPA - 섹션4. 쿼리 메소드 기능

공통으로 있는게 들어있는건 알겠는데 내 앱이여서 내 도메인에 특화된 건 어떻게 하느냐. 즉, data jpa를 그대로 쓰고 여기에 나만의 커스텀 함수는 어떻게 만드느냐. 이게 쿼리 메소드 기능임.

이름만으로 된다.

 

이름이 같고 나이는 어느 이상인 조건을 찾는 함수를 만든다고 하자. 정석은 EntityManager로 불러오는 것.

하지만 data jpa는 이름만으로도 가능하다는 것.

 

자동완성이 되어서 참고하면서 하면 된다.

 

 

함수 이름은 규칙이 있기 때문에 자세히 알고싶으면 공식 문서를 보면 된다.

 

https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation

 

Spring Data JPA - Reference Documentation

Example 121. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del

docs.spring.io

 

@NamedQuery라고 있는데 실무에서 거의 안쓰고 전에 해봤으니 넘어감. 장점은 실행을 위해 앱 로딩할 때 떠서 문법 오류같은 것들 바로 확인 가능하다는 것. 실제로는 상위호환인 @Query를 쓴다.

 

이 @Query는 엔티티에 대해서만 할 수 있는게 아니라 컬럼 및 dto 클래스에 대해서도 가능하다.

 

 

 

 

 

 

파라미터 바인딩도 가능

 

반환 타입도 다양하게 지원한다.

주의할 점은 만약 값이 없을 시, 각 반환타입에 따라 반환값이 달라지는데, List는 그냥 비어있는 list가 나오고, 단건은 null, Optional은 Optional.empty가 나온다. 많이 헷갈리므로 단건에서 값이 없을 수도 있는 경우는 Optional을 사용하자.

 

 

페이징 처리. 일단 직접 jpql로 짜보자.

offset과 limit을 이용한다. 페이지를 구현할 경우 페이지 갯수 같은 것 때문에 보통 카운트도 같이 정의해서 사용한다.

이런걸 data jpa는 그냥 주어진다.

 

특이한건 패키지가 지금까지 처럼 data-jpa 하위 패키지가 아니라 그냥 data.domain으로 되어있다. 즉, data-jpa에만 한정되어 있지 않고 어느 db든 사용할 수 있다는 소리.

 

요즘은 일반 페이지 뿐만 아니라 더보기 기능을 위한 slice도 제공한다.

 

총 갯수를 위한 count까지 자기가 직접 만들어 사용하는 걸 볼 수 있다.

 

 

페이지 대신 슬라이드 기능을 써보자. 이건 페이지 단위로 하지만 가져와야 할 갯수에 바로 다음것까지 +1해서 만약 다음이 있으면 더보기 눌러서 더 진행하고.. 하는 기능을 만든거다.

 

더보기이기 때문에 사용자가 만든 갯수에 +1을 가져오고, 전체 갯수를 알 필요가 없어 얘는 카운트를 따로 안한다.

 

근데 이 카운트가 문제가 될 수 있는것이, 어차피 갯수 세는거는 join을 하든 안하든 결국엔 같을텐데 따로지정 안해주면 join을 한 뒤에 count를 하여 성능에 저하를 준다. 그래서 countQuery는 어떻게 해야할지 따로 설정할 수 있다.

 

 

 

설정 안했을 때와 했을 때의 쿼리.

 

 

벌크 쿼리도 알아볼건데 벌크 쿼리는 모든 row애들 한번에 업데이트 하는거. 자바는 객체 중심이다보니 하나하나 해야하는 개념이라 직접 @Query로 만들어야 한다.

일단 수동으로 만들었을 때

 

data-jpa를 이용해 만들었을 때

 

@Modifying을 넣어줘야 찾는게 아니라 업데이트를 한다.

 

@Modifying 없을 경우 에러 뜸

 

그럼 @Modifying 하고 @Query하면 다 끝나는게 아니냐? 싶은데 이런 벌크 연산은 영속성 컨텍스트에 저장해서 반영하니 마니 하는게 아니라 그냥 직접 db로 쏴주는거다 보니 한 트랜잭션 안에서 벌크연산 하나만 있는게 아니라 다른것도 같이 있다면 영속성 컨텍스트 안에 들어있는 내용까지 고려해야 한다. 벌크 연산 전 flush는 자동으로 되지만 clear는 안되서 벌크연산이 반영되기 전의 내용이 영속성 컨텍스트 안에 들어있다.

이렇게 남아있으니 EntityManager를 가져와서 비워주는 작업을 해야 한다. 어차피 repository에서 사용하는 entitymanager나 @PersistenceContext에서 가져오는 entitymanager나 같은 거라 해도 된다.

 

flush는 자동으로 되서 굳이 안해도 된다.

 

clear를 자동으로 하라는것도 @Modifying의 매개변수로 넣을 수 있다.

 

근데 이런 벌크 연산은 하나의 트랜잭션 단위로 만들면 이런 고민을 할 필요가 없다. 그러니 잘 설계하자.

 

 

@EntityGraph는 지연로딩에 관련된거라 지연로딩 부터 알아야 한다. 복습 개념으로 보자.

만약 lazy로 설정되어 있을 경우, 불러오라고 했을 때 실제로 한번에 join까지 해서 db에서 불러오는게 아니라, 가짜 프록시 객체를 가지고 있고 값을 요구할 때 그때서야 불러온다.

가짜 프록시 객체를 가지고 있고 요구하면 그때서야 where를 통해 해당 값을 가지고 오는걸 볼 수 있다.

그래서 fetch join을 써왔었는데 실제로 어떻게 사용하는지를 보는거다.

 

정석적으론 @Query에 fetch를 직접 작성해서 불러올 수 있다.

fetch를 통해 한번에 다 불러와 실제 객체를 가지고 있으면 fetch에 성공한 것이다.

 

이렇게 @Query 적고 직접 fetch를 날리는게 귀찮은 사람을 위해 @EntityGraph 기능을 넣었다. 상위 함수에 정의된 findAll을 override하여 작성해보자. 여기에 내가 fetch join하길 원하는 객체를 적어주면 자동으로 fetch join이 되면서 반환된다.

 

 

이 fetch join을 하는것도 여러 방법이 있다. 평소엔 그냥만 불러오다가 다른 객체까지 같이 사용하는 경우가 많으니 따로 함수를 만들어서 한다던가.. 원하는걸로 하자.

매개변수도 가능하다.

 

잘 안쓰긴 하지만 @NamedQuery마냥 @NamedEntityGraph도 정의 가능하다.

 

 

 

@QueryHint랑 @Lock도 있는데 hint는 쓰는게 그다지 의미가 없고 lock은 실시간에서 별로임

힌트는 진짜 변경 없이 100% 읽기만 하는거라는 확신이 있을 때 readonly로써 불러와 최적화 화는거다. 이러면 프록시 만들어서 추적을 할 필요 없으니 자원이 조금 줄어든다. jpa에선 지원을 하지 않고 hibernate에서만 자체적으로 지원하는 것.

근데 이걸 사용하기가 애매한데, db성능에 영향을 미치는건 잘못된 쿼리인 경우가 대부분이지, 여기서 이거 조금 개선한다고 막 엄청 좋아지고 그런건 아니고, 이걸 쓸 때 쯤이면 이미 캐시 등 다른 기술을 사용할 때이기 때문.

 

lock은 내가 보고있는 동안 다른건 접근 못하게 막는거. 근데 내용이 엄청 어려운데다 잘 안쓴다.