[Spring] Spring Data Jpa

윤여송·2026년 9월 19일

Spring

목록 보기
6/12

Spring Data JPA

1. Spring Data JPA는 무엇인가?

Spring Data JPA는 JPA를 더 적은 코드로 사용하게 해 주는 Spring 프로젝트다.

Spring Data JPA
  ↓ JPA를 편하게 사용하도록 지원
JPA (표준 규칙)
  ↓
Hibernate (대표 구현체)
  ↓
JDBC
  ↓
H2 / MySQL / PostgreSQL

JPA만 사용할 때는 EntityManager로 Repository 구현 클래스를 직접 작성한다.

public class JpaMemberRepository implements MemberRepository {
    private final EntityManager em;

    public Member save(Member member) {
        em.persist(member);
        return member;
    }
}

Spring Data JPA는 Repository 인터페이스만 선언하면, 실행 시점에 Spring이 구현체(프록시)를 자동으로 만들어 준다.

2. 현재 프로젝트의 Repository

public interface SpringDataJpaMemberRepository
        extends JpaRepository<Member, Long>, MemberRepository {

    Optional<Member> findByName(String name);
}

여기서:

  • Member: 관리할 엔티티 타입
  • Long: Member 엔티티의 ID 타입
  • MemberRepository: 프로젝트에서 만든 추상화 인터페이스
  • JpaRepository: Spring Data JPA가 제공하는 기본 CRUD 기능 인터페이스
SpringDataJpaMemberRepository
  ├─ JpaRepository<Member, Long>
  │    └─ 공통 CRUD 기능
  └─ MemberRepository
       └─ 프로젝트에서 정한 MemberRepository 타입 규약

3. 기본 CRUD는 JpaRepository가 제공한다

JpaRepository<Member, Long>을 상속하면 기본적인 CRUD 메서드를 직접 구현하지 않아도 사용할 수 있다.

memberRepository.save(member);      // INSERT 또는 UPDATE
memberRepository.findById(id);      // ID로 한 건 조회
memberRepository.findAll();         // 전체 조회
memberRepository.existsById(id);    // 존재 여부 확인
memberRepository.count();           // 전체 개수
memberRepository.deleteById(id);    // ID로 삭제
memberRepository.delete(member);    // 엔티티 삭제

현재 MemberService.join()도 save()를 직접 구현하지 않았지만 정상 동작한다.

public Long join(Member member) {
    validateDuplicateMember(member);
    memberRepository.save(member);
    return member.getId();
}

그 이유는 save()가 JpaRepository에 이미 있기 때문이다.

4. findById()와 findByName()의 차이

findById()는 모든 엔티티에 존재하는 ID 기준 조회이므로 JpaRepository가 기본 제공한다.

Optional<Member> findById(Long id);

반면 모든 엔티티가 name 필드를 갖는 것은 아니므로 findByName()은 기본 제공되지 않는다.

Optional<Member> findByName(String name);

하지만 Member 엔티티에 name 필드가 있으므로, 인터페이스에 위 메서드만 선언하면 Spring Data JPA가 이름을 해석해서 구현한다.

find By Name
 ↓      ↓
조회    Member 엔티티의 name 필드 조건

개념적으로는 아래 JPQL을 만든다.

select m from Member m where m.name = :name

Spring Data JPA는 AI처럼 의미를 추론하는 것이 아니라, 미리 정해진 메서드 이름 규칙을 파싱해서 쿼리를 생성한다.

5. 메서드 이름 기반 쿼리 예시

엔티티의 실제 필드명을 사용하고, 지원하는 키워드를 조합한다.

Optional<Member> findByName(String name);

List<Member> findByNameContaining(String keyword);

boolean existsByName(String name);

long countByName(String name);

List<Member> findByNameAndIdGreaterThan(String name, Long id);

List<Member> findByNameOrderByIdDesc(String name);

자주 쓰는 키워드:

목적예시
동일 조건findByName
AND / ORfindByNameAndStatus, findByNameOrEmail
범위findByAgeBetween, findByIdGreaterThan
문자열 포함findByNameContaining
목록 안에 포함findByStatusIn
정렬findByNameOrderByIdDesc
존재 여부existsByName
개수countByName

By 뒤의 이름은 반드시 엔티티의 실제 필드와 맞아야 한다.

@Entity
public class Member {
    private String name;
}

따라서 findByName()은 가능하지만, email 필드가 없다면 findByEmail()은 애플리케이션 시작 시 오류가 난다.

6. 모든 메서드를 자동 구현할 수 있는 것은 아니다

자동 생성은 단순한 조회 조건 조합에 유용하지만, 모든 쿼리를 메서드 이름만으로 표현할 수는 없다.

단순 CRUD·조건 조회
  → JpaRepository / 메서드 이름 쿼리

복잡하지만 고정된 조회 쿼리
  → @Query로 JPQL 작성

조건이 다양하게 바뀌는 복잡한 검색
  → QueryDSL 등 사용

DB 전용 문법·성능이 매우 중요한 SQL
  → Native SQL, JdbcTemplate, MyBatis 등 고려

메서드 이름이 지나치게 길어지면 읽기도 어렵고 유지보수도 힘들다.

// 가능하더라도 읽기 어려운 예시
findByNameAndStatusAndCreatedAtBetweenAndAgeGreaterThanOrderByIdDesc(...)

이 경우에는 @Query로 JPQL을 직접 선언할 수 있다.

@Query("select m from Member m where m.name = :name")
Optional<Member> findMemberByName(@Param("name") String name);

7. 수정은 보통 변경 감지를 사용한다

JPA에서는 조회한 엔티티를 트랜잭션 안에서 변경하면, JPA가 변경을 감지해 UPDATE SQL을 실행한다.

@Transactional
public void updateName(Long id, String newName) {
    Member member = memberRepository.findById(id).orElseThrow();
    member.setName(newName);
} // 트랜잭션 커밋 시 UPDATE 실행

일반적인 수정은 save()를 반복 호출하기보다, 조회 → 필드 변경 → 변경 감지 흐름이 자연스럽다.

8. 페이지네이션

Spring Data JPA는 Pageable과 Page로 페이지네이션을 기본 지원한다.

Pageable pageable = PageRequest.of(
        0, // 페이지 번호는 0부터
        10,
        Sort.by("id").descending()
);

Page<Member> page = memberRepository.findAll(pageable);

Page<Member>에서 얻을 수 있는 정보:

page.getContent();        // 현재 페이지의 데이터 목록
page.getTotalElements();  // 전체 데이터 수
page.getTotalPages();     // 전체 페이지 수
page.getNumber();         // 현재 페이지 번호 (0부터)
page.hasNext();           // 다음 페이지 존재 여부

조건 조회에도 적용할 수 있다.

Page<Member> findByNameContaining(String keyword, Pageable pageable);

일반적으로 Spring Data JPA는 페이지 데이터 조회와 전체 개수 조회(count)를 함께 수행해 페이지 정보를 만든다.

9. Service와 트랜잭션

Spring Data JPA가 CRUD 구현을 제공해도, 트랜잭션 경계는 보통 Service에 둔다.

@Transactional
public Long join(Member member) {
    validateDuplicateMember(member);
    memberRepository.save(member);
    return member.getId();
}

Service는 회원가입·주문처럼 비즈니스 작업 전체를 담당한다. 여러 Repository 작업이 하나의 업무를 이루면, Service 트랜잭션 안에서 모두 성공하면 커밋하고 하나라도 실패하면 함께 롤백할 수 있다.

핵심 요약

  1. Spring Data JPA는 Repository 인터페이스의 구현체를 자동 생성한다.
  2. JpaRepository는 save, findById, findAll, delete 같은 공통 CRUD를 제공한다.
  3. findByName처럼 엔티티별 조건 조회는 인터페이스에 선언하면 메서드 이름 규칙으로 쿼리를 자동 생성한다.
  4. 자동 생성은 지원하는 키워드와 엔티티 필드에 한정된다.
  5. 복잡한 조회는 @Query, QueryDSL, Native SQL 등을 사용한다.
  6. Pageable과 Page로 페이지네이션을 기본 지원한다.
  7. 비즈니스 작업 전체의 트랜잭션 경계는 보통 Service에 둔다.
profile
y_some__velog

0개의 댓글