이제 SpringDataJpa의 전반적인 부분은 어느정도 배웠고,
이제 나머지 기능들에 대해서 소개를 좀 할건데,
이것들은 대부분 복잡도에 비해 다른 좋은 대안들이 있어서 사실 잘 안쓴다.
명세,
Query By Example,
Projections,
네이티브 쿼리
이렇게 4가지에 대해 배울건데,
4번째 네이티브 쿼리 빼고는 위에 3가지는 소개 하고, 왜 안쓰는지 알아볼 예정.
명세라는 것은 뭐냐, 우리가 where문에서 and, or 해서 조건같은 걸 막 넣는데,
그걸 조립해서 쓸 수 있도록 만든 개념 (근데 이건 이미 QueryDSL에서..)
그냥 나도 한번 써보는 정도는 보여 줄 거임.
public interface MemberRepository extends JpaRepository<Member, Long>, 커스텀할거야 , JpaSpecificationExecutor<Member>{
Spring Data Jpa 인터페이스에다가, 저
JpaSpecificationExecutor<T>
이거를 상속받아 주면 됨.
저거 들어가 보면
public interface JpaSpecificationExecutor<T> {
Optional<T> findOne(Specification<T> spec);
List<T> findAll(Specification<T> spec);
Page<T> findAll(Specification<T> spec, Pageable pageable);
List<T> findAll(Specification<T> spec, Sort sort);
long count(Specification<T> spec);
boolean exists(Specification<T> spec);
long delete(Specification<T> spec);
<S extends T, R> R findBy(Specification<T> spec, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);
}
여러 메소드 들이 있는데, 우리가 좀 익숙한 findAll이라던지 이런 R에 대한 것들.
아무래도 뭔가 동적쿼리에 대한 기능 같으므로 find에 대한..
근데 차이점은 Specification<T>라는 객체를 받음.
그럼 추측하기로는 저 Specification이라는 것이 동적쿼리를 짤 때 무언가 도움이 되라고 만들어 놓은 객체 같음.
이거는 Jpa의 Criteria라는 기술을 쉽게 활용하도록 만든 거라고 함.
그럼 이제 구현해 보겠음.
이걸 쓰기 위해서는 우리가 따로 그 조합할? Specification<T>에 대한 조합을 우리가 정의해 줘야 함.
public class MemberSpec {
public static Specification<Member> teamName(final String teamName){
return new Specification<Member>() {
@Override
public Predicate toPredicate(Root<Member> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
if(!StringUtils.hasText(teamName)) return null;
Join<Member, Team> t = root.join("team", JoinType.INNER);
return builder.equal(t.get("name"), teamName);
}
};
}
public static Specification<Member> username(final String username){
return (Specification<Member>) (root, query, builder)->{
return builder.equal(root.get("username"), username);
};
}
}
Specification을 return 해야 저걸로 계속 이어서 할 수 있으니까, 리턴타입을 저걸로 하고,
보면 인자로 받은 teamName이 null이면 그냥 null을 return 시켜버리고(빈 Specification을 리턴시키는 게 아니라?),
있으면 Join해서 뭐 저게 Join해서 가져온다 그런 메소드인가 봄. root가 엔티티? 같고, Join해서 Team을 가져오는 거니 t라고 한 듯.
그거를 크리터리아 빌더 builder.equal 해서 t의 이름과 인자로 받은 teamName을 비교하는 equal문의 정보를 가진 객체를 반환. 저게 Specification으로 반환 되는 듯?
그 다음 username. 저거는 findByUsername하면 되지 않나? 생각했는데 동적쿼리는 여러개 조건으로 내거는 거니까 저것도 필요가 있었나 봄.
저것도 똑같음. username을 인자로 받아서 root의 username필드와 비교시키는 equal문의 정보를 가진 Specification 객체를 반환
그래서,
@Test
public void specBasic(){
Team teamA = new Team("teamA");
em.persist(teamA);
Member m1 = new Member("m1", 0, teamA);
Member m2 = new Member("m2", 0, teamA);
em.persist(m1);
em.persist(m2);
em.flush();
em.clear();
Specification<Member> spec = MemberSpec.username("m1").and(MemberSpec.teamName("teamA"));
List<Member> result = memberRepository.findAll(spec);
assertThat(result.size()).isEqualTo(1);
assertThat(result.get(0).getUsername()).isEqualTo("m1");
assertThat(result.get(0).getTeam().getName()).isEqualTo("teamA");
}
이렇게 테스트 코드 돌려 보면.
저 findAll 하는 순간
select
m1_0.member_id,
m1_0.age,
m1_0.create_by,
m1_0.created_date,
m1_0.last_modified_by,
m1_0.last_modified_date,
m1_0.team_id,
m1_0.username
from
member m1_0
join
team t1_0
on t1_0.team_id=m1_0.team_id
where
m1_0.username=?
and t1_0.name=?
이렇게 쿼리 잘 나감.
join했던 Team과,
equal했던 username, team의 name
위에는 and만 썼는데 and, or, not 등 제공
여튼, QueryDSL을 쓰자. Jpa의 Criteria는 쓰지 말자.
'스프링데이터 + JPA > 스프링 데이터 JPA' 카테고리의 다른 글
28. Projections (0) | 2023.11.26 |
---|---|
27. Query By Example (0) | 2023.11.26 |
25. 새로운 엔티티 구별방법, isNew (0) | 2023.11.26 |
24. 스프링 데이터 JPA 구현체 분석 (0) | 2023.11.26 |
23. 웹 확장 페이징과 정렬 (0) | 2023.11.24 |