INFLEARN

[스프링 입문] 8. 스프링 데이터 JPA

ch010104 2026. 3. 6. 12:29

기존에는 리포지토리를 만들 때 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);
}