티스토리 뷰
모든 소스 코드는 여기에서 확인 가능합니다.
스프링 데이터 JPA를 사용하면 기본 인터페이스 외에도 메서드 추가 만으로 직접 구현체를 구현하지 않아도 기능을 사용할 수 있습니다.
쿼리 메서드 기능
메서드 이름으로 쿼리 생성
메서드 이름을 분석하여 JPQL 쿼리를 수행합니다.
package io.lcalmsky.springdatajpa.domain.entity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.*;
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
private int age;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
@ToString.Exclude
private Team team;
public Member(String username, int age, Team team) {
this.username = username;
this.age = age;
if (team != null) {
changeTeam(team);
}
}
public void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
위와 같은 Member
Entity가 있을 때 이름과 나이를 기준으로 조회한다면,
package io.lcalmsky.springdatajpa.domain.repository;
import io.lcalmsky.springdatajpa.domain.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAgeGreaterThan(String username, int age);
}
List<Member> findByUsernameAndAgeGreaterThan(String username, int age)
이런식으로 표현할 수 있습니다.
스프링 데이터 JPA가 제공하는 쿼리 메서드 세부 기능은 다음과 같습니다.
- 조회: find…By ,read…By ,query…By get…By,
<T>
타입을 반환합니다.
- COUNT: count…By
long
타입을 반환합니다.
- EXISTS: exists…By
boolean
타입을 반환합니다.
- 삭제: delete…By, remove…By
long
타입을 반환합니다.
- DISTINCT: findDistinct, findMemberDistinctBy
- LIMIT: findFirst3, findFirst, findTop, findTop3
위 feature들을 적절하게 섞어 SQL문을 작성하듯이 메서드를 작성할 수 있습니다. IntelliJ 같은 IDE에서는 메서드를 만들 때 자동완성을 지원해주기도 합니다.
Entity의 필드명이 변경될 경우 인터페이스의 메서드명도 같이 변경되어야 합니다.
NamedQuery
@Entity
클래스에 @NamedQuery
애너테이션을 추가하여 사용합니다.
package io.lcalmsky.springdatajpa.domain.entity;
import javax.persistence.*;
@Entity
@NamedQuery(
name = "Member.findByUsername",
query = "select m from Member where m.username = :username"
)
public class Member {
...
}
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsername(@Param("username") String username);
}
- 도메인 클래스 + "." + 메서드 이름으로 NamedQuery를 찾아 실행합니다.
실무에선 거의 사용하지 않으므로 자세한 내용은 생략하겠습니다.
@Query 애너테이션 사용
Repository 인터페이스 메서드에 @Query
애너테이션을 추가한 뒤 JPQL을 작성합니다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@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);
}
- 이름 없는 NamedQuery라고 할 수 있습니다.
- 앱 실행 시점에 문법 오류를 발견할 수 있습니다.
- 파라미터가 증가하면 메서드 이름만으로 개발하기에 한계가 있으므로 실무에서 많이 사용하는 방법입니다.
@Query로 바로 클래스에 매핑하기
Entity나 Entity List로 매핑하는 게 아닌 다른 데이터 타입 클래스로 매핑할 수 있습니다.
username
이 String
이므로 List<String>
으로 반환 타입을 지정하면 알아서 매핑해줍니다.
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m.username from Member m")
List<String> findUsernameList();
}
실무에서 성능을 위해 DTO로 바로 매핑해야 하는 경우가 있습니다.
그럴 땐 아래 처럼 new operation을 이용해 바로 객체를 생성해 반환할 수 있습니다.
package io.lcalmsky.springdatajpa.domain.dto;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class MemberDto {
private Long id;
private String username;
private String teamName;
}
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select new io.lcalmsky.springdatajpa.domain.dto.MemberDto(m.id, m.username, t.name) from Member m join m.team t")
List<MemberDto> findMemberDto();
}
파라미터 바인딩(Parameter Binding)
JPQL에서 파라미터를 위치 기반, 이름 기반으로 바인딩 할 수 있습니다.
select m from Member m where m.username = ?0 // 위치 기반
select m from Member m where m.username = :username // 이름 기반
위치 기반 파라미터 바인딩은 파라미터의 위치가 바뀌거나 중간에 where 조건이 추가되면 반드시 같이 수정되어야 하므로 실무에선 잘 쓰이지 않습니다.
되도록이면 이름 기반 파라미터 바인딩을 사용해야 합니다.
public interface MemberRepository extends JpaRepository<Member, Long> {
@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);
}
이름 기반 파라미터 바인딩을 사용하는 경우 @Param
애너테이션으로 어떤 이름에 매핑시킬지 정의합니다.
@Query
를 쓴 것이 아니라 메서드 이름으로 쿼리를 생성했을 경우 @Param
은 생략 가능합니다.
public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByUsernameAndAge(String username, int age);
}
컬렉션 파라미터를 바인딩 할 수도 있습니다.
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("select m from Member m where m.username in :names")
List<Member> findByNames(@Param("names") Collection<String> names);
}
반환 타입
Optional<T>
, T
, List<T>
등 유연한 반환 타입을 제공합니다.
@Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
List<Team> findTeamsByName(String name); // 여러 개 조회
Team findTeamByName(String name); // 한 개 조회
Optional<Team> findNullableTeamByName(String name); // 0..1개 조회
}
한 개만 조회해야 하는 경우 여러 개가 조회되면 에러가 날 수 있습니다. 따라서 반드시 한 개가 조회되는 경우에만 반환 타입을 Entity의 타입으로 지정하셔야 합니다.
하나도 조회되지 않을 경우 null
을 반환하니 NullPointerException
을 방지하기 위한 로직을 추가하든지 Optional
을 사용하는 것이 더 좋은 방법입니다.
이 외에도 void
, primitives
, wrapper types
, Stream<T>
, Future<T>
, CompletableFuture<T>
, Page<T>
등 어마어마하게 많은 타입으로 반환할 수 있으니 자세한 내용은 스프링 공식 문서를 참고하시면 됩니다.
'JPA' 카테고리의 다른 글
스프링 데이터 JPA - 벌크 업데이트 (1) | 2021.07.01 |
---|---|
스프링 데이터 JPA - 페이징과 정렬 (0) | 2021.06.30 |
JPA란? (0) | 2020.04.03 |
객체와 데이터베이스의 체계와 한계 (0) | 2020.04.02 |
SQL의 문제점 (0) | 2020.04.01 |
- Total
- Today
- Yesterday
- intellij
- Linux
- 알고리즘
- 함께 자라기 후기
- leetcode
- Jackson
- spring boot jwt
- 스프링 부트
- JPA
- Java
- 스프링 부트 튜토리얼
- 함께 자라기
- 헥사고날 아키텍처
- 스프링 부트 회원 가입
- 클린 아키텍처
- Spring Boot JPA
- 스프링부트
- proto3
- 스프링 부트 애플리케이션
- Spring Data JPA
- gRPC
- Spring Boot Tutorial
- r
- JSON
- spring boot app
- spring boot application
- QueryDSL
- 스프링 데이터 jpa
- Spring Boot
- @ManyToOne
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |