기존에는 리포지토리를 만들 때 EntityManager를 주입받고, JPQL을 직접 작성하고, 구현 클래스를 만들어야 했습니다. 하지만 스프링 데이터 JPA를 사용하면 인터페이스 선언만으로 모든 게 끝납니다.
1. 핵심 구조: JpaRepository 상속
가장 먼저 해야 할 일은 JpaRepository를 상속받는 인터페이스를 만드는 것입니다.

package com.example.spring_study.domain.repository;
import com.example.spring_study.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
// select m form Member m where m.name = ? 로 쿼리를 자동 생성해줌
@Override
Optional<Member> findByName(String name);
}
2. 마법의 원리: 메소드 이름 쿼리 생성
스프링 데이터 JPA는 메소드 이름을 분석해서 쿼리를 자동으로 생성합니다. 이때 반환 타입에 따라 내부 동작이 달라집니다.
반환 타입에 따른 선택 기준
반환 타입 상황 특징
| Optional<T> | 결과가 0개 또는 1개일 때 | findById, findByName 등 고유값 조회 시 안전함 |
| List<T> | 결과가 여러 개일 때 | OrderBy 등을 사용할 때 필수! 결과 없으면 [] 반환 |
| boolean | 존재 여부만 궁금할 때 | existsByName(String name) -> true/false 반환 |
3. 주요 명명 규칙 (Query Method Keywords)
인터페이스에 이름을 지을 때 아래 키워드들을 조합하면 복잡한 쿼리도 뚝딱 만들어집니다.
- 조회: find...By, read...By, query...By, get...By
- 조건(Where):
- And, Or: findByNameAndEmail(String name, String email)
- LessThan, GreaterThan: findByAgeGreaterThan(int age)
- Between: findByAgeBetween(int start, int end)
- Like, Containing: 키워드 포함 여부
- 정렬(OrderBy):
- OrderBy[필드명][Asc/Desc]: findAllOrderByIdDesc()
- 페이징: 반환 타입을 Page<T>로 설정하고 매개변수에 Pageable을 넣으면 페이징 쿼리가 나갑니다.
4. 스프링 설정 (SpringConfig) 변경
스프링 데이터 JPA를 사용하면 리포지토리를 직접 @Bean으로 등록할 필요가 없습니다. 스프링이 인터페이스를 보고 자동으로 구현체를 만들어 스프링 빈에 올려주기 때문입니다.
package com.example.spring_study;
import com.example.spring_study.domain.repository.JdbcTemplateMemberRepository;
import com.example.spring_study.domain.repository.JpaMemberRepository;
import com.example.spring_study.domain.repository.MemberRepository;
import com.example.spring_study.domain.repository.MemoryMemberRepository;
import com.example.spring_study.domain.service.MemberService;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
// SpringDataJpaMemberRepository에서 JpaRepository<Member, Long>가 알아서 객체를 생성해서 container에 등록해줌
@Autowired
private final MemberRepository memberRepository;
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
}
5. 스프링 데이터 JPA 메소드 보충
| 메소드 이름 | 실제 생성되는 쿼리 (JPQL) | 설명 및 반환 타입 팁 |
| findById(Long id) | select m from Member m where m.id = ? | 기본 제공. ID는 유일하므로 Optional<Member> 사용. |
| findByName(String name) | select m from Member m where m.name = ? | 이름이 중복될 가능성이 없다면 Optional, 있다면 List. |
| findByEmailAndName(S, S) | ... where m.email = ? and m.name = ? | 여러 조건을 And로 결합. 결과가 여러 개면 List<Member>. |
| findByNameOrderByAgeDesc(S) | ... where m.name = ? order by m.age desc | 특정 조건으로 찾고 정렬까지 할 때. 보통 List<Member> 사용. |
| findByAgeGreaterThan(int age) | ... where m.age > ? | 특정 값보다 큰 데이터를 찾을 때. (미만은 LessThan) |
| findByNameContaining(S name) | ... where m.name like %?% | SQL의 LIKE 검색. 특정 글자가 포함된 이름을 찾을 때. |
| countByName(String name) | select count(m) from Member m where m.name = ? | 조건에 맞는 데이터 개수 확인. 반환 타입은 long. |
| existsByEmail(String email) | select count(m) > 0 from Member m ... | 데이터 존재 여부만 확인. 반환 타입은 boolean. |
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
/**
* 1. 이름으로 회원 찾기
* 쿼리: SELECT m FROM Member m WHERE m.name = :name
* [Optional] 결과가 없으면 empty, 있으면 1개를 담아서 반환 (2개 이상이면 에러 발생)
*/
@Override
Optional<Member> findByName(String name);
/**
* 2. 이메일과 이름이 모두 일치하는 회원 목록 조회
* 쿼리: SELECT m FROM Member m WHERE m.email = :email AND m.name = :name
* [List] 조건에 맞는 모든 회원을 리스트에 담아 반환 (결과 없으면 빈 리스트 [] 반환)
*/
List<Member> findByEmailAndName(String email, String name);
/**
* 3. 특정 이름을 가진 사람들을 나이 내림차순(많은 순)으로 정렬하여 조회
* 쿼리: SELECT m FROM Member m WHERE m.name = :name ORDER BY m.age DESC
* [List] 정렬된 결과를 리스트로 반환하며, 나이가 같을 경우 내부 순서는 보장되지 않음
*/
List<Member> findByNameOrderByAgeDesc(String name);
/**
* 추가 팁: 나이가 특정 숫자보다 큰 회원들 조회
* 쿼리: SELECT m FROM Member m WHERE m.age > :age
*/
List<Member> findByAgeGreaterThan(int age);
}
'INFLEARN' 카테고리의 다른 글
| [스프링 핵심 원리 - 기본편] 1. SOLID 원칙 (0) | 2026.03.12 |
|---|---|
| [스프링 입문] 9. AOP 와 프록시 (0) | 2026.03.06 |
| [스프링 입문] 5. 컴포넌트 스캔과 자동 의존관계 설정(@Componet와 @Bean) (0) | 2026.03.06 |
| [스프링 입문] 4. JUnit5 테스트 코드 작성 (0) | 2026.03.03 |
| [스프링 입문] 3. Optimal 타입 (0) | 2026.03.03 |