Spring Data JPA 게시글은 대부분 인프런의 김영한님의 강의인 '실전! 스프링 데이터 JPA' 기반으로 내용을 정리했습니다.
파라미터 바인딩, 반환 타입
- 위치 기반
select m from Member m where m.username = ?0
- 이름 기반
select m from Member m where m.username = :name
위치 기반은 거의 사용하지 않는다. 코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용하는 것이 좋다.
파라미터 바인딩
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username = :name")
Member findMembers(@Param("name") String username);
}
컬렉션 파라미터 바인딩
Collection 타입을 in 절을 지원한다.
- MemberRepository.interface
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") Collection<String> names);
- MemberRepositoryTest.class
@Test
public void findByNames() {
Member m1 = new Member("AAA", 10);
Member m2 = new Member("BBB", 20);
memberRepository.save(m1);
memberRepository.save(m2);
List<Member> usernameList = memberRepository.findByNames(Arrays.asList("AAA", "BBB"));
for (Member Member : usernameList) {
System.out.println("Member = " + Member);
}
}
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
where
member0_.username in (
? , ?
)
반환 타입
스프링 데이터 JPA는 유연한 반환타입을 지원한다.
- MemberRepository.interface
List<Member> findListByUsername(String username); //컬렉션
Member findMemberByUsername(String username); // 단건
Optional<Member> findOptionalByUsername(String username); // 단건 Optional
- MemberRepositoryTest.class
@Test
public void returnType() {
Member m1 = new Member("AAA", 10);
Member m2 = new Member("BBB", 20);
memberRepository.save(m1);
memberRepository.save(m2);
List<Member> list = memberRepository.findListByUsername("AAA");
System.out.println("=========================");
Member member = memberRepository.findMemberByUsername("AAA");
System.out.println("=========================");
Optional<Member> optional = memberRepository.findOptionalByUsername("AAA");
}
2022-07-30 19:10:02.507 DEBUG 5423 --- [ main] org.hibernate.SQL :
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
where
member0_.username=?
=========================
2022-07-30 19:10:02.516 DEBUG 5423 --- [ main] org.hibernate.SQL :
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
where
member0_.username=?
=========================
2022-07-30 19:10:02.519 DEBUG 5423 --- [ main] org.hibernate.SQL :
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
where
member0_.username=?
DB에 원하는 데이터가 있을 경우 별 특이한 점 없이 반환이 잘 된다. 만약 데이터를 가져오지 못한 경우는 어떻게 될까? 또, 단건 조회를 했는데 여러 개의 데이터가 나온다면?
조회 결과가 생각과 다를 경우
- List
List<Member> list = memberRepository.findListByUsername("CCC"); // CCC는 없다
System.out.println("list == null : " + list == null);
System.out.println("list.size : " + list.size());
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
where
member0_.username=?
false
list.size : 0
List의 경우 결과값이 없는 경우 null이 반환되는 것이 아니라 빈 컬렉션이 반환된다. 그러므로 아래와 같은 로직을 사용하면 원하는 의도대로 작동되지 않는다.
if(list != null) {
....
}
- 단건
Member member = memberRepository.findMemberByUsername("CCC");
System.out.println("member = " + member);
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
where
member0_.username=?
member = null
단건 조회의 경우 데이터가 없으면 null이 반환된다. JPA의 기본 동작 방식과 약간 다른데 원래 순수 JPA에서는 단건 조회 시 데이터가 없으면 NoResultException이 터진다. 스프링 데이터 JPA는 개발자를 생각해 저 에러 부분을 try~catch로 잡아 null로 반환하도록 했다. Exception이 터지는 게 좋은 건가? 아니면 null로 반환 되는 게 좋은 건가에 대한 논쟁은 자바 8이 나오기 전에 의견이 분분했었고, 자바 8이 나온 이후부터는 그냥 Optional을 사용하면 된다.
- Optinal
Optional<Member> optional = memberRepository.findOptionalByUsername("CCC");
System.out.println("optional = " + optional);
select
member0_.member_id as member_i1_0_,
member0_.age as age2_0_,
member0_.team_id as team_id4_0_,
member0_.username as username3_0_
from
member member0_
where
member0_.username=?
optional = Optional.empty
자바 8 이후 부터는 데이터가 있거나 없을 수 있는 상황에선 Optinal을 사용하면 된다.
- 단건, Optinal 일때 여러 건이 조회되는 경우
@Test
public void returnType() {
Member m1 = new Member("AAA", 10);
Member m2 = new Member("AAA", 20);
memberRepository.save(m1);
memberRepository.save(m2);
Optional<Member> optional = memberRepository.findOptionalByUsername("AAA");
System.out.println("optional = " + optional);
}
AAA를 조회하면 m1과 m2가 조회된다. 이렇게 단건일 때 여러 건이 조회되는 경우 javax.persistence.NonUniqueResultException 예외가 터진다. 그런데 예외 메세지를 잘 보면 제일 첫 줄에 org.springframework.dao.IncorrectResultSizeDataAccessException가 있는 걸 볼 수 있다. 원래 JPA에서 터지는 예외는 NonUniqueResultException가 맞다. 하지만 스프링 데이터 JPA를 사용하면 스프링 데이터 JPA에서 NonUniqueResultException을 한 번 감싸버린다. 이유는 JPA가 아닌 다른 몽고 DB나 다른 JDBC로 바꾸는 경우에도 똑같은 에러가 터질 수 있도록 의도한 것이다.
더욱 더 많은 타입을 반환할 수 있는 스프링 데이터 JPA에 대해 더 알고싶으면 아래 공식 사이트를 확인하면 된다.