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이 구현체(프록시)를 자동으로 만들어 준다.
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 타입 규약
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에 이미 있기 때문이다.
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처럼 의미를 추론하는 것이 아니라, 미리 정해진 메서드 이름 규칙을 파싱해서 쿼리를 생성한다.
엔티티의 실제 필드명을 사용하고, 지원하는 키워드를 조합한다.
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 / OR | findByNameAndStatus, findByNameOrEmail |
| 범위 | findByAgeBetween, findByIdGreaterThan |
| 문자열 포함 | findByNameContaining |
| 목록 안에 포함 | findByStatusIn |
| 정렬 | findByNameOrderByIdDesc |
| 존재 여부 | existsByName |
| 개수 | countByName |
By 뒤의 이름은 반드시 엔티티의 실제 필드와 맞아야 한다.
@Entity
public class Member {
private String name;
}
따라서 findByName()은 가능하지만, email 필드가 없다면 findByEmail()은 애플리케이션 시작 시 오류가 난다.
자동 생성은 단순한 조회 조건 조합에 유용하지만, 모든 쿼리를 메서드 이름만으로 표현할 수는 없다.
단순 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);
JPA에서는 조회한 엔티티를 트랜잭션 안에서 변경하면, JPA가 변경을 감지해 UPDATE SQL을 실행한다.
@Transactional
public void updateName(Long id, String newName) {
Member member = memberRepository.findById(id).orElseThrow();
member.setName(newName);
} // 트랜잭션 커밋 시 UPDATE 실행
일반적인 수정은 save()를 반복 호출하기보다, 조회 → 필드 변경 → 변경 감지 흐름이 자연스럽다.
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)를 함께 수행해 페이지 정보를 만든다.
Spring Data JPA가 CRUD 구현을 제공해도, 트랜잭션 경계는 보통 Service에 둔다.
@Transactional
public Long join(Member member) {
validateDuplicateMember(member);
memberRepository.save(member);
return member.getId();
}
Service는 회원가입·주문처럼 비즈니스 작업 전체를 담당한다. 여러 Repository 작업이 하나의 업무를 이루면, Service 트랜잭션 안에서 모두 성공하면 커밋하고 하나라도 실패하면 함께 롤백할 수 있다.
JpaRepository는 save, findById, findAll, delete 같은 공통 CRUD를 제공한다.findByName처럼 엔티티별 조건 조회는 인터페이스에 선언하면 메서드 이름 규칙으로 쿼리를 자동 생성한다.@Query, QueryDSL, Native SQL 등을 사용한다.Pageable과 Page로 페이지네이션을 기본 지원한다.