DB/자바 ORM 표준 JPA 프로그래밍

[12장] 스프링 데이터 JPA

꾸준함. 2025. 3. 15. 18:22

스프링 데이터 JPA 소개

  • 스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트
    • CRUD를 처리하기 위한 공통 인터페이스 제공
    • 레포지토리를 개발할 때 엔터피에스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성해서 주입
    • 따라서 데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발을 완료할 수 있음

 

https://leejaedoo.github.io/spring_data_JPA/

 

 

부연 설명

  • 일반적인 CRUD 메서드는 JpaRepository 인터페이스가 공통으로 제공하므로 문제가 없지만
  • MemberRepository.findByUsername(...)처럼 공통으로 처리할 수 없어 직접 작성한 메서드는 스프링 데이터 JPA가 메서드명을 분석해서 다음 JPQL을 실행
    • SELECT m FROM Member m WHERE username = :username

 

1. 스프링 데이터 프로젝트

  • 스프링 데이터 JPA는 스프링 데이터 프로젝트의 하위 프로젝트 중 하나
    • 스프링 데이터 JPA 프로젝트는 JPA에 특화된 기능을 제공
    • 스프링 프레임워크와 JPA를 함께 사용한다면 스프링 데이터 JPA 사용을 적극 추천

 

  • 스프링 데이터 프로젝트는 JPA, 몽고 DB, NEO4J, REDIS, HADOOP, GEMFIRE와 같은 다양한 데이터 저장소에 대한 접근을 추상화해서 개발자 편의를 제공하고 지루하게 반복하는 데이터 접근 코드를 줄여줌

 

https://ddd4117.github.io/2021/04/jpa-12%EC%9E%A5-spring-data-jpa/

 

스프링 데이터 JPA 설정

이 책이 집필된 지 10년이 넘었기 때문에, 책에서 언급된 라이브러리 버전은 더 이상 최신이 아닙니다.

 

1. 필요 라이브러리

 

 

 

2. 환경 설정

  • 스프링 설정에 JavaConfig를 사용하면 다음과 같이 `org.springframework.data.jpa.repository.config.EnableJpaRepositories` 어노테이션을 추가하고 basePackages에는 레포지토리를 검색할 패키지 위치를 기입
  • 스프링 데이터 JPA는 애플리케이션을 실행할 때 basePackage에 있는 레포지토리 인터페이스를 찾아서 해당 인터페이스를 구현한 클래스를 동적으로 생성한 뒤 스프링 빈으로 등록
    • 이에 따라 개발자가 직접 구현 클래스를 생성하지 않아도 됨

 

 

https://velog.io/@junho918/Spring-Data-Jpa-JPA..%EA%B7%B8%EB%9E%98-%EC%95%8C%EA%B2%A0%EC%96%B4..-%EA%B7%B8%EB%9E%98%EC%84%9C-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-JPA%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%93%B0%EB%8A%94%EB%8D%B0

 

공통 인터페이스 기능

  • 스프링 데이터 JPA는 간단한 CRUD 기능을 공통으로 처리하는 JpaRepository 인터페이스를 제공
  • 스프링 데이터 JPA를 사용하는 가장 단순한 방법은 해당 인터페이스를 상속받은 후 제네릭에 엔티티 클래스와 엔티티 클래스가 사용하는 식별자 타입을 지정하는 것

 

 

https://bombo96.tistory.com/67

 

부연 설명

  • 윗부분에 스프링 데이터 모듈이 있고 그 안에 Repository, CrudRepository, 그리고 PagingAndSortingRepository가 있는데 이것은 스프링 데이터 프로젝트가 공통으로 사용하는 인터페이스
  • 스프링 데이터 JPA가 제공하는 JpaRepository 인터페이스는 여기에 추가로 JPA에 특화된 기능을 제공
  • JpaRepository 인터페이스를 상속받으면 다음과 같은 메서드를 사용할 수 있으며 T는 엔티티, ID는 엔티티의 식별자 타입, 그리고 S는 엔티티와 그 자식 타입을 뜻함
    • save(S): 새로운 엔티티를 젖아하고 이미 있는 엔티티는 수정
    • delete(T): 엔티티 하나를 삭제하며 내부에서 EntityManager.remove()를 호출
    • findOne(ID): 엔티티 하나를 조회하며 내부에서 EntityManager.find()를 호출
    • getOne(ID): 엔티티를 프록시로 조회하며 내부에서 EntityManager.getReference()를 호출
    • findAll(...): 모든 엔티티를 조회하며 정렬이나 페이징 조건을 파라미터로 제공

 

쿼리 메서드 기능

  • 쿼리 메서드 기능은 슬프리이 데이터 JPA가 제공하는 마법 같은 기능으로 메서드 이름만으로 쿼리를 생성하는 기능이 대표적 기능
    • 인터페이스에 메서드만 선언하면 해당 메서드 이름을 기반으로 JPQL 쿼리를 생성해서 실행

 

  • 스프링 데이터 JPA가 제공하는 쿼리 메서드 기능은 크게 세 가지가 있음
    • 메서드명으로 쿼리 생성
    • 메서드명으로 JPA NamedQuery 호출
    • @Query 어노테이션을 사용해서 레포지토리 인터페이스에 쿼리 직접 정의

 

1. 메서드 이름으로 쿼리 생성

  • 이메일과 이름으로 회원을 조회하고 싶을 경우 다음과 같이 메서드를 정의하면 됨
    • 인터페이스에 정의한 findByEmailAndName(...) 메서드를 실행하면 스프링 데이터 JPA는 메서드명을 분석해서 JPQL을 생성하고 실행함
    • 실행된 JPQL은 SELECT m FROM Member m WHERE m.email = ?1 AND m.name = ?2
    • 물론 정해진 규칙에 따라서 메서드명을 지어야 함
    • https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html 참고

 

 

주의: 메서드 이름으로 쿼리를 생성했을 경우 엔티티의 필드명이 변경되었을 때 인터페이스에 정의한 메서드 이름도 꼭 함께 변경해야 합니다.

 

2. JPA NamedQuery

  • 스프링 데이터 JPA는 메서드명으로 JPA Named 쿼리를 호출하는 기능 제공
  • JPA Named 쿼리는 이름 그대로 쿼리에 이름을 부여해서 사용하는 방법이며 다음과 같이 어노테이션으로 정의 및 호출 가능

 

 

  • 스프링 데이터 JPA를 사용하면 다음과 같이 메서드명만으로 Named 쿼리를 호출할 수 있음

 

 

  • 스프링 데이터 JPA는 선언한 `{도메인 클래스}.{메서드명}`으로 Named 쿼리를 찾아서 실행하기 때문에 앞선 예제는 Member.findByUsername이라는 Named 쿼리를 실행하며 만약 실행할 Named 쿼리가 없으면 메서드명으로 쿼리 생성 전략을 사용

 

3. @Query, 레포지토리 메서드에 쿼리 정의

  • 레포지토리 메서드에 직접 쿼리를 정의하려면 @org.springframework.data.jpa.repository.Query 어노테이션을 사용하며 해당 방법은 실행할 메서드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라 할 수 있음
    • 또한 JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있는 장점이 있음

 

 

  • 네이티브 SQL을 사용하려면 다음과 같이 @Query 어노테이션에 `nativeQuery = true`를 설정하면 됨
    • 스프링 데이터 JPA가 지원하는 파라미터 바인딩 사용 시 JPQL은 위치 기반 파라미터를 1부터 시작하지만
    • 네이티브 SQL은 0부터 시작

 

 

4. 파라미터 바인딩

  • 스프링 데이터 JPA는 위치 기반 파라미터 바인딩과 이름 기반 파라미터 바인딩을 모두 지원
    • 기본값은 위치 기반이고 파라미터 순서로 바인딩
    • 이름 기반 파라미터 바인딩을 사용하기 위해서는 org.springframework.data.repository.query.Param(파라미터명) 어노테이션을 사용하면 됨
    • 코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 권장

 

 

5. 벌크성 수정 쿼리

  • 스프링 데이터 JPA에서 벌크성 수정, 삭제 쿼리는 org.springframework.data.jpa.repository.Modifying 어노테이션을 사용하면 됨
  • 벌크성 쿼리를 실행하고 나서 영속성 컨텍스트를 초기화하고 싶으면 @Modifying(clearAutomatically = true)처럼 clearAutomatically 옵션을 true로 설정하면 됨
    • default 값은 false

 

 

6. 반환 타입

  • 스프링 데이터 JPA는 유연한 반환 타입을 지원하는데 결과가 한 건 이상이면 컬렉션 인터페이스를 사용하고, 단건이면 반환 타입을 지정
    • 만약 조회 결과가 없을 경우 컬렉션은 빈 컬렉션을 반환하고 단건은 null을 반환
    • 단건을 기대하고 반환 타입을 지정했는데 결과가 두 건 이상 조회되면 NonUniqueResultException 예외 발생

 

 

7. 페이징과 정렬

  • 스프링 데이터 JPA는 쿼리 메서드에 페이징과 정렬 기능을 사용할 수 있도록 두 가지 특별한 파라미터 제공
    • org.springframework.data.domain.Sort: 정렬 기능
    • org.springframework.data.domain.Pageable: 페이징 기능

 

  • 파라미터에 Pageable을 사용하면 반환 타입으로 List나 org.springframework.data.domain.Page 사용 가능
    • 반환 타입으로 Page를 사용하면 스프링 데이터 JPA는 페이징 기능을 제공하기 위해 검색된 전체 데이터 건수를 조회하는 count 쿼리를 추가로 호출

 

 

  • Pageable은 인터페이스이므로 실제 사용할 때는 해당 인터페이스를 구현한 org.springframework.data.domain.PageRequest 객체 사용
    • PageRequest 생성자의 첫 번째 파라미터에는 현재 페이지를, 두 번째 파라미터에는 조회할 데이터 수를 입력

 

 

8. 힌트

  • JPA 쿼리 힌트를 사용하려면 org.springframework.data.jpa.repository.QueryHints 어노테이션을 사용하면 됨
    • SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트

 

 

부연 설명

  • forCounting 속성은 반환 타입으로 Page 인터페이스를 적용할 경우 추가로 호출하는 페이징을 위한 count 쿼리에도 쿼리 힌트를 적용할지를 설정하는 옵션 (default 값 true)

 

9. Lock

  • 쿼리 시 락을 걸려면 org.springframework.data.jpa.repository.Lock 어노테이션을 사용

 

 

명세 (Specification)

  • 스프링 데이터 JPA는 JPA Criteria로 명세 개념을 사용할 수 있도록 지원
  • 명세를 이해하기 위한 핵심 단어는 술어 (predicate)인데 이것은 단순히 참이나 거짓으로 평가되며 AND, OR 같은 연산자로 조합 가능
    • ex) 데이터를 검색하기 위한 제약 조건 하나하나를 술어라 할 수 있음
    • 스프링 데이터 JPA는 술어를 org.springframework.data.jpa.domain.Specification 클래스로 정의함
    • Specification은 컴포지트 패턴으로 구성되어 있어 여러 Specification을 조합할 수 있으며 이에 따라 다양한 검색 조건을 조립해서 새로운 검색조건을 쉽게 만들 수 있음

 

  • 명세 기능을 사용하려면 레포지토리에서 org.springframework.data.jpa.repository.JpaSpecificationExecutor 인터페이스를 상속받으면 됨

 

 

  • JpaSpecificationExecutor의 메서드들은 Specification을 파라미터로 받아서 검색 조건으로 사용 가능
    • Specifications는 명세들을 조립할 수 있도록 도와주는 클래스인데 where(), and(), or(), 그리고 not() 메서드를 제공

 

 

사용자 정의 레포지토리 구현

  • 스프링 데이터 JPA로 레포지토리를 개발하면 인터페이스만 정의하고 구현체는 만들지 않지만 다양한 이유로 인해 메서드를 직접 구현해야 하는 케이스도 존재
    • 하지만 레포지토리를 직접 구현하면 공통 인터페이스가 제공하는 기능까지 모두 구현해야 함

 

  • 스프링 데이터 JPA는 위와 같은 문제를 우회해서 필요한 메서드만 구현할 수 있는 방법을 제공
    • 직접 구현할 메서드를 위한 사용자 정의 인터페이스를 작성한 뒤
    • 해당 인터페이스를 구현할 클래스를 정의하되 스프링 데이터 JPA가 사용자 정의 구현 클래스로 인식하도록 클래스명을 `{레포지토리 인터페이스명}Impl`과 같이 지음 
    • 마지막으로 레포지토리 인터페이스에서 사용자 정의 인터페이스를 상속받으면 됨

 

 

Web 확장

  • 스프링 데이터 프로젝트는 스프링 MVC에서 사용할 수 있는 편리한 기능 제공

 

1. 설정

  • 스프링 데이터가 제공하는 Web 확장 기능을 활성화하려면 org.springframework.data.web.config.SpringDataWebConfiguration을 스프링 빈으로 등록하면 됨
    • 설정을 완료하면 도메인 클래스 컨버터와 페이징과 정렬을 위한 HandlerMethodArgumentResolver가 스프링 빈으로 등록됨
    • 등록되는 도메인 클래스 컨버터는 org.springframework.data.repository.support.DomainClassConverter

 

 

2. 도메인 클래스 컨버터 기능

  • 도메인 클래스 컨버터는 HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩해줌
    • ex) 특정 회원을 수정하는 화면을 보여주려면 컨트롤러는 HTTP 요청으로 넘어온 회원의 아이디를 사용해서 레포지토리를 통해 회원 엔티티를 조회해야 함

 

 

부연 설명

  • @RequestParam("id") Member member 부분을 보면 HTTP 요청으로 회원 아이디를 받지만 도메인 클래스 컨버터가 중간에 동작해서 아이디를 회원 엔티티 객체로 변환해서 넘겨줌
  • 도메인 클래스 컨버터는 해당 엔티티와 관련된 레포지토리를 사용해서 엔티티를 조회
    • 여기서는 회원 레포지토리를 통해 회원 아이디로 회원 엔티티를 조회

 

3. 페이징과 정렬 기능

  • 스프링 데이터가 제공하는 페이징과 정렬 기능을 스프링 MVC에서 편리하게 사용할 수 있도록 HandlerMethodArgumentResolver를 제공
    • 페이징 기능: PageableHandlerMethodArgumentResolver
    • 정렬 기능: SortHandlerMethodArgumentResolver

 

 

 

부연 설명

  • 파라미터로 받은 Pageable은 다음 요청 파라미터 정보로 만들어짐
    • page: 현재 페이지, 0부터 시작
    • size: 한 페이지에 노출할 데이터 건수
    • sort: 정렬 조건을 정의

 

접두사

  • 사용해야 할 페이징 정보가 둘 이상이면 접두사를 사용해서 구분 가능
  • 접두사는 스프링 프레임워크가 제공하는 @Qualifier 어노테이션을 사용하고 `[접두사명]_`로 구분

 

 

기본값

  • Pageable의 기본값은 page = 0, size = 20
  • 만약 기본값을 변경하고 싶으면 @PageableDefault 어노테이션을 사용하면 됨

 

 

스프링 데이터 JPA가 사용하는 구현체

  • 스프링 데이터 JPA가 제공하는 공통 인터페이스는 org.springframework.data.jpa.repository.support.SimpleJpaRepository 클래스가 구현함

 

 

부연 설명

  • @Repository 적용: JPA 예외를 스프링이 추상화한 예외로 변환
  • @Transactional 트랜잭션 적용: JPA의 모든 변경은 트랜잭션 안에서 이루어져야 하므로 스프링 데이터 JPA가 제공하는 공통 인터페이스를 사용하면 데이터를 변경하는 메서드에 @Transactional로 트랜잭션 처리가 되어 있음
    • 따라서 서비스 계층에서 트랜잭션을 시작하지 않으면 레포지토리에서 트랜잭션을 시작
    • 물론 서비스 계층에서 트랜잭션을 시작했다면 레포지토리도 해당 트랜잭션을 전파받아서 그대로 사용

 

  • @Transactional(readOnly = true): 데이터를 조회하는 메서드에는 readOnly = true 옵션이 적용되어 있고 데이터를 변경하지 않는 트랜잭션에서 readOnly = true 옵션을 사용하면 flush를 생략해서 약간의 성능 향상을 얻을 수 있음
  • save() 메서드: 해당 메서드는 저장할 엔티티가 새로운 엔티티면 저장하고 이미 있는 엔티티면 병합 진행
    • 새로운 엔티티를 판단하는 기본 전략은 엔티티의 식별자로 판단하는데 식별자가 객체일 때 null, 자바 기본 타입일 때 숫자 0 값이면 새로운 엔티티로 판단

 

스프링 데이터 JPA와 QueryDSL 통합

  • 스프링 데이터 JPA는 두 가지 방법으로 QueryDSL을 지원
    • org.springframework.data.querydsl.QueryDslPredicateExecutor
    • org.springframework.data.querydsl.QueryDslRepositorySupport

 

1. QueryDslPredicateExecutor 사용

  • 첫 번째 방법은 레포지토리에서 QueryDslPredicateExecutor를 상속받으면 됨
  • 다음과 같이 ItemRepository가 QueryDslPredicateExecutor를 상속받으면 QueryDSL을 사용할 수 있음

 

 

 

  • QueryDslPredicateExecutor 인터페이스를 보면 QueryDSL을 검색조건으로 사용하면서 스프링 데이터 JPA가 제공하는 페이징과 정렬 기능도 함께 사용 가능
  • 이처럼 QueryDslPredicateExecutor는 스프링 데이터 JPA에서 편리하게 QueryDSL을 사용할 수 있지만 기능에 한계가 있음
    • ex) join, fetch를 사용할 수 없음 (JPQL에서 이야기하는 묵시적 조인은 가능)
    • 이에 따라 QueryDSL이 제공하는 다양한 기능을 사용하기 위해서는 JPAQuery를 직접 사용하거나 스프링 데이터 JPA가 제공하는 QueryDslRepositorySupport을 사용해야 함

 

2. QueryDslRepositorySupport 사용

  • QueryDSL의 모든 기능을 사용하려면 JPAQuery 객체를 직접 생성해서 사용하면 되며 이때 스프링 데이터 JPA가 제공하는 QueryDslRepositorySupport을 상속받아 사용하면 조금 더 편리하게 QueryDSL을 사용할 수 있음

 

 

 

부연 설명

  • 스프링 데이터 JPA가 제공하는 공통 인터페이스는 직접 구현할 수 없기 때문에 CustomOrderRepository라는 사용자 정의 레포지토리 생성
  • QueryDslRepositorySupport를 사용해서 QueryDSL로 구현한 예제로 검색 조건에 따라 동적으로 쿼리를 생성
    • 반드시 생성자에서 QueryDslRepositorySupport에 엔티티 클래스 정보를 넘겨주어야 함

 

참고

자바 ORM 표준 JPA 프로그래밍 - 김영한 저

반응형