Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 스프링 mvc2 - 로그인 처리
- 자바의 정석 기초편 ch2
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch1
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch4
- jpa 활용2 - api 개발 고급
- 자바의 정석 기초편 ch13
- 스프링 입문(무료)
- 자바의 정석 기초편 ch9
- 스프링 mvc2 - 타임리프
- 자바의 정석 기초편 ch5
- 2024 정보처리기사 시나공 필기
- 자바의 정석 기초편 ch14
- 스프링 mvc1 - 스프링 mvc
- 자바의 정석 기초편 ch7
- 스프링 mvc1 - 서블릿
- 스프링 db1 - 스프링과 문제 해결
- jpa - 객체지향 쿼리 언어
- 타임리프 - 기본기능
- 스프링 db2 - 데이터 접근 기술
- 게시글 목록 api
- 2024 정보처리기사 수제비 실기
- @Aspect
- 자바의 정석 기초편 ch8
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch3
- 자바의 정석 기초편 ch12
- 자바의 정석 기초편 ch6
Archives
- Today
- Total
나구리의 개발공부기록
쿼리 메소드 기능, 메소드 이름으로 쿼리 생성, JPA NamedQuery, @Query - 리포지토리 메소드에 쿼리 정의, @Query - 값/DTO 조회, 파라미터바인딩, 반환타입 본문
인프런 - 스프링부트와 JPA실무 로드맵/실전! 스프링 데이터 JPA
쿼리 메소드 기능, 메소드 이름으로 쿼리 생성, JPA NamedQuery, @Query - 리포지토리 메소드에 쿼리 정의, @Query - 값/DTO 조회, 파라미터바인딩, 반환타입
소소한나구리 2024. 10. 26. 17:49출처 : 인프런 - 실전! 스프링 데이터 JPA (유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 쿼리 메소드 기능 - 메소드 이름으로 쿼리 생성
1) 순수 JPA로 이름과 나이를 기준으로 회원을 조회 및 테스트
(1) MemberJpaRepository
- 순수 JPA로 구현하려면 직접 JPQL로 쿼리를 작성해야만 가능함
// 이름과 나이를 기준으로 회원을 조회 -> 순수 JPA는 직접 짜야함
public List<Member> findByUsernameAndAgeGreaterThan(String username, int age) {
return em.createQuery("select m from Member m where m.username = :username and m.age > :age", Member.class)
.setParameter("username", username)
.setParameter("age", age)
.getResultList();
}
(2) MemberJpaRepositoryTest
- 테스트 코드를 작성한 후 실행해보면 작성해둔 JPQL쿼리가 정상적으로 실행되며 테스트가 성공함
@Test
public void findByUsernameAndAgeGreaterThan() {
Member m1 = new Member("AAA", 10);
Member m2 = new Member("AAA", 20);
memberJpaRepository.save(m1);
memberJpaRepository.save(m2);
List<Member> result = memberJpaRepository.findByUsernameAndAgeGreaterThan("AAA", 15);
assertThat(result.get(0).getUsername()).isEqualTo("AAA");
assertThat(result.get(0).getAge()).isEqualTo(20);
}
2) 스프링 데이터 JPA로 동일한 기능을 구현 및 테스트
(1) MemberRepository
- 위의 기능을 스프링 데이터 JPA로 메서드를 생성,
- 즉 구현하는것이아니라 메소드만 정의
public interface MemberRepository extends JpaRepository<Member, Long> {
// 이름과 나이를 기준으로 회원을 조회 -> 스프링 데이터 JPA 쿼리 메소드 기능을 활용
// 형식에 맞춰서 메소드 이름만 규칙에 맞춰서 작성하면 쿼리를 안짜도 됨
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}
(2) MemberRepositoryTest
- 동일한 테스트 케이스로 MemberRepository의 메소드로 테스트를 수행하보면 테스트도 통과하며 쿼리도 통과함
- 실제 구현을 하지 않았는데 관례대로 메소드 이름을 스프링 데이터 JPA의 리포지토리에 정의만 해두면, 메소드 이름으로 스프링 데이터 JPA가 알아서 쿼리를 만들어서 실행하는데, 이것이 쿼리 메소드 기능임
- 규칙대로 꼭 입력해주어야 쿼리메소드 기능이 실행되며 틀리면 당연히 실행되지 않으며, 조건없이도 쿼리메소드기능을 만들 수 있음
(3) 메소드 분석
- findBy : 조회
- Username, Age : 필드명
- And : and 연산
- GreaterThan : > 연산
- 즉 Username = 비교대상 And Age > 비교대상 처럼 조건이 완성됨,
3) 쿼리 메소드 필터 조건 및 주요 쿼리 메소드 기능 살펴보기
- 공식 문서
- 자세한 내용 및 다양한 기능은 공식문서를 읽어보고 한번 사용해보는 것을 권장
(1) 조회
- find...By, read...By, query...By, get...By
- ...에는 식별하기 위한 내용이나 설명이 들어가도되며 생략해도 됨
(2) COUNT
- count...By
- count 연산기능, 반환타입은 long
(3) EXISTS
- exists...By
- exists(조건이 만족하는지의 여부를 확인) 기능, 반환타입은 boolean
(4) DISTINCT
- findDistinct, findMemberDistinctBy
- 중복제거기능
(5) LIMIT
- findFirst3, findFirst, findTop, findTop3
- limit 기능, First, Top 뒤에 숫자를 입력하면 원하는 개수만큼 가져올 수 있으며 입력하지 않으면 1개만 가져옴
- 역순으로 자르는 것은 없고 역순 정렬로 자르면 됨
- ex) findFirst10ByOrderByIdDesc()
** 참고
- 단점은 조건이 조금만 더 추가되어도 메소드의 이름이 엄청 길어지기때문에 많아도 2개까지만 쿼리메소드기능을 사용해서 해결하고, 조건이 더 많아지면 다른방법으로 해결하는것을 권장함(바로 배움)
- 그리고 쿼리메소드의 매우 중요한 장점 중 하나는, 여러명이서 개발했을 때 누군가 엔터티를 변경하게되면 애플리케이션 실행 시점에 컴파일 에러가 발생하기 때문에 미연에 오류를 찾아낼 수 있음
2. 쿼리 메소드 기능 - JPA NamedQuery
** 참고
- @NamedQuery 애노테이션을 사용하는 기능이며 실무에서 거의 사용할일이 없기때문에 간략히 설명
1) 사용방법
- 엔터티나 XML문서에 @NamedQuery 애노테이션을 입력하여 쿼리를 입력
(1) Member 수정
- Member Entity에 @NamedQuery애노테이션을 입력하여 쿼리를 추가
- name : 쿼리의 이름
- query : 실제 쿼리를 작성, 파라미터 바인딩도 사용할 수 있음
@NamedQuery(
name="Member.findByUsername",
query="select m from Member m where m.username = :username")
public class Member {
// ... 기존 코드 생략
}
(2) 순수 JPA에서 네임드 쿼리를 호출하는 방법
- createNamedQuery메소드로 네임드 쿼리의 이름 인자로 입력
- 그 뒤는 순수 JPA 사용방법과 동일하며, 테스트를 실행하보면 정상적으로 쿼리가 실행됨(테스트 코드 생략)
// 네임드 쿼리 호출
public List<Member> findByUsername(String username) {
return em.createNamedQuery("Member.findByUsername", Member.class)
.setParameter("username", username)
.getResultList();
}
(3) 스프링 데이터 JPA에서 네임드 쿼리 호출
- @Query 애노테이션의 name으로 작성한 네임드 쿼리의 이름을 호출
- 작성한 NamedQuery에 파라미터 바인딩 코드가 있으면 @Param으로 바인딩 이름을 설정해주어야 쿼리가 실행되며, 만약 NamedQuery에 파라미터 바인딩 쿼리가 없다면 @Param이 없어도 됨
- 마찬가지로 테스트를 진행해보면 쿼리가 정상적으로 수행되는 것을 확인할 수 있음(테스트코드는 생략)
** 참고
- 해당 코드에서 @Query 애노테이션 부분을 주석처리해도 정상 동작하는데, 그 이유는 스프링 데이터 JPA의 쿼리메소드기능은 동작 순서가 먼저 작성된 메소드의 이름과 동일한 이름으로 작성한 네임드 쿼리가 있는지 먼저 찾은 뒤에 없으면 위에서 배운 메소드이름으로 쿼리를 생성하도록 동작하기 때문임
- 네임드 쿼리를 조회하는 엔터티는 JpaRepository를 상속 받을때 작성한 제네릭 타입의 Entity에서 찾음
public interface MemberRepository extends JpaRepository<Member, Long> {
// ... 기존 코드 생략
@Query(name = "Member.findByUsername")
List<Member> findByUsername(@Param("username") String username);
}
2) 잘 사용하지 않는 이유와 네임드 쿼리만의 장점
- 리포지토리에 바로 쿼리를 작성할 수 있는 기능이 있기 때문에 굳이 엔터티에 쿼리를 작성해서 쿼리를 분산해서 관리할 필요가 없으며 오히려 유지보수만 어려워 질 수 있음
- 그러나 애플리케이션 로딩시점에 네임드 쿼리에 작성한 쿼리를 한번 파싱을해서 보관하기 때문에 쿼리에 오류가있으면 애플리케이션이 실행되지않고 에러를 미리 확인할 수 있는 장점을 가지고 있지만 많이 사용하지는 않음
3. 쿼리 메소드 기능 - @Query, 리포지토리 메소드에 쿼리 정의하기
1) 실무에서 자주 사용하는 @Query
(1) 스프링 데이터 JPA의 리포지토리의 메소드에 바로 쿼리를 정의
- 메소드 위에 @Query 애노테이션으로 JPQL 쿼리를 작성할 수 있으며 파라미터 바인딩도 사용할 수 있음
- 메소드이름으로 쿼리 생성할 때의 단점으로 메소드의 이름이 길어지는 것인데, 이 기능은 조건이 많고 복잡한 쿼리가 많아도 사용할 메소드의 위에 쿼리를 직접 작성함으로써 간편하고 쉽게 복잡한 쿼리를 작성할 수 있음
@Query("select m from Member m where m.username = :username and m.age = :age")
List<Member> findUser(@Param("username") String username, @Param("age") int age);
(2) 장점
- @Query를 작성한 쿼리는 문자임에도 쿼리에 오타가 있거나 잘못 작성하면 애플리케이션 로딩 시점에 파싱하여 보관하고 있기 때문에 에러가 발생했는지 미리 확인할 수 있음
- 즉, 네임드쿼리와 동일하게 동작하지만 훨씬 유지보수성 뿐만아니라 관리하기도 좋기 때문에 네임드쿼리를 사용할 일이 없어지는 이유이며 대부분의 정적쿼리에서의 문제는 @Query를 사용하여 해결이 가능함(동적 쿼리는 Querydsl로 해결)
4. @Query, 값 / DTO 조회하기
- 엔터티가 아닌 단순 값이나, DTO를 조회하는 방법 (실무에서 자주 사용함)
1) 단순히 값 하나를 조회
- 필드 값으로 조회할 수 있고, 임베디드 타입(값 타입)도 동일한 방식으로 조회할 수 있음
@Query("select m.username from Member m")
List<String> findUsernameList();
2) DTO로 직접 조회
(1) 조회할 Dto 생성
@Data
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) 스프링 데이터 JPA에서 DTO로 직접 조회
- DTO로 직접 조회할 때에는 순수 JPA도 마찬가지로 new 연산자를 사용하기 때문에 패키지 이름을 전부 작성해주어야함
- DTO에서 소속된 team의 이름도 조회해야하기에 join으로 Member와 Team에서 정보를 가져오도록 Query를 작성
- 이런 부분은 나중에 배울 Querydsl을 사용하면 매우 편리하게 작성할 수 있도록 개선할 수 있음
// dto로 조회, new 연산자를 사용하기 때문에 패키지를 전부 입력해야함
@Query("select new study.data_jpa.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();
(3) Test
- 테스트로 조회된 값을 출력해보면 DTO에서 정의한 값들이 출력되는 것을 확인할 수 있음(물론 실무에서는 Assertions로 검증을 하여 테스트를 작성해야 함)
@Test
public void findMemberDto() {
Team teamA = new Team("teamA");
teamRepository.save(teamA);
Member m1 = new Member("AAA", 10);
m1.setTeam(teamA);
memberRepository.save(m1);
List<MemberDto> memberDto = memberRepository.findMemberDto();
for (MemberDto dto : memberDto) {
System.out.println("dto = " + dto);
}
}
5. 파라미터 바인딩
1) 파라미터 바인딩 종류
- 위치 기반
- 이름 기반
- 코드 가독성과 유지보수를 위해서 위치 기반은 사용하지 말고 이름 기반만 사용해야 함
- 위치 기반은 순서가 바뀌거나 엔터티가 변경되면 대참사가 일어날 수 있음
select m from Member m where m.username = ?0 //위치 기반
select m from Member m where m.username = :name //이름 기반
2) 컬렉션 파라미터 바인딩
- 쿼리를 in 절을 활용하여 작성하고 Collection 타입으로 파라미터 바인딩의 값을 입력할 수 있음
- 다양한 컬렉션으로 인자를 입력할 수 있도록 조상타입인 Collection으로 받는것을 권장함
@Query("select m from Member m where m.username in :names")
List<Member> findBynames(@Param("names") Collection<String> names);
6. 반환 타입
1) 스프링 데이터 JPA의 반환타입
- 스프링 데이터 JPA는 반환타입을 유연하게 지원하기 때문에 반환타입을 원하는 대로 지정해서 반환하면 됨
- 스프링 데이터 JPA의 다양한 리턴 타입
List<Member> findListByUsername(String username); // 컬렉션
Member findMemberByUsername(String username); // 단건
Optional<Member> findOptionalByUsername(String username); // Optional
(1) 컬렉션
- 조회 결과가 많을때 사용
- 결과가 없으면 빈 컬렉션을 반환(null이 아님)
- 그래서 컬렉션으로 조회한 뒤에 예외처리를 한다고 != null 이런식으로 검증하는 코드를 짤 필요없이 그대로 받아도 됨
(2) 단건 조회
- Entity, Dto로 직접 반환하거나 Optional로 감싸서 반환할 수 있음
- 결과가 없으면 null을 반환하고 결과가 2건 이상이면 NonUniqueResultException 예외가 발생하며, Spring Framework Exception으로 변경되어 IncorrectResultSizeDataAccessException으로 반환하기 때문에 리포지토리 기술이 바뀌어도 클라이언트의 코드를 바꾸지 않아도 되는 장점이 존재함
- 즉, 단건 조회할 때는 Optional으로 반환하여 orElse 등으로 비어있을 때와 결과가 2건일 때의 예외만 처리해주면 됨
** 참고
- 단건으로 지정한 메서드를 호출하면 스프링 데이터 JPA는 내부에서 JPQL의 getSingleResult메서드를 호출하여 조회 결과가 없으면 순수 JPA처럼 NoResultException을 반환해야하는데, 내부적으로 null로 반환되도록 동작하게 정의 되어있음
- 예외를 반환하는것과 null을 반환하는 것중에 어떤것이 더 좋은지에 대해서는 논쟁의 여지가 있지만 개발을 할 당시에는 예외를 반환하는 것보다 null이 반환되는 것이 조금 수월한 면이 있음
- 그러나 Java8 이후로는 Optional이라는 null에 안전한 반환타입이 있기 때문에 예외처리를 기존보다 더 간편하게 할 수 있음