• JPA를 사용해서 데이터 접근 계층을 개발할 때도 유사한 코드를 반복해서 개발해야 한다.
  • 이런 문제를 해결하려면 제네릭과 상속을 적절히 사용해서 공통 부분을 처리하는 부모 클래스를 만들면 되지만, 공통 기능이 부모 클래스에 너무 종속되고 구현 클래스 상속이 가지는 단점에 노출된다.

스프링 데이터 JPA 소개

  • 스프링 데이터 JPA는 데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발을 완료할 수 있다.
    • MemberRepository.findByUsername() 처럼 직접 작성한 메소드도 메소드 이름을 분석해서 다음 JPQL을 실행한다.
      • select m from Member m where username =:username

스프링 데이터 프로젝트

  • 스프링 데이터 JPA는 스프링 데이터 프로젝트의 하위 프로젝트 중 하나다.

스프링 데이터 JPA 설정

  • 의존성 추가
  • 스프링 설정에서 @EnableJpaRepositories 어노테이션을 추가하고 basePackages에서는 리포지토리를 검색할 패키지를 적는다.

공통 인터페이스 기능

  • 스프링 데이터 JPA는 간단한 CRUD 기능을 처리하는 JpaRepository 인터페이스를 제공한다.
  • 주요 메서드
    • save(S): 새로운 엔티티를 저장하고 이미 있는 엔티티는 수정한다.
      • 엔티티에 식별자 값이 없으면 새로운 엔티티로 판단해서 EntityManager.persist()를 호출하고 식별자 값이 있음녀 이미 있는 엔티티로 판단해서 EntityManager.merge()를 호출한다.
      • 필요하다면 스프링 데이터 JPA의 기능을 확장해서 신규 엔티티 판단 전략을 변경할 수 있다.
    • delete(T): 엔티티 하나를 삭제한다. EntityManager.remove()를 호출한다.
    • findOne(ID): 엔티티 하나를 조회한다. EntityManager.find()를 호출한다.
    • getOne(ID): 엔티티를 프록시로 조회한다. EntityManager.getReference()를 호출한다.
    • findAll(...): 모든 엔티티를 조회한다. 정렬이나 페이징 조건ㅇ르 파라미터로 제공할 수 있다.

쿼리 메소드 기능

  • 스프링 데이터 JPA가 제공하는 쿼리 메소드 기능 3가지
    • 메소드 이름으로 쿼리 생성
    • 메소드 이름으로 JPA NamedQuery 호출
    • @Qeury 어노테이션을 사용해서 리포지토리 인터페이스에 쿼리 직접 정의

메소드 이름으로 쿼리 생성

JPA NamedQuery

  • 메소드 이름으로 JPA Named 쿼리를 호출하는 기능을 제공한다.
  • 스프링 데이터 JPA는 선언한 “도메인 클래스 + .(점) + 메소드 이름"으로 Named 쿼리를 찾아서 실행한다.
  • 메소드 파라미터에 @Param을 사용했는데 이것은 이름기반 파라미터 바인딩할 때 사용하는 어노테이션이다.

@Query, 리포지토리 메소드에 쿼리 정의

  • @Query 어노테이션을 사용해서 실행할 메소드에 정적 쿼리를 작성할 수 있다.
  • JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있는 장점이 있다.
  • @Query 어노테이션에 nativeQuery = true를 설정하면 네이티브 SQL을 작성할 수 있다.

파라미터 바인딩

  • 스프링 데이터 JPA는 위치 기반 파라미터 바인딩과 이름 기반 파라미터 바인딩을 모두 지원한다.
  • 기본값은 위치 기반인데 파라미터 순서로 바인딩한다.
  • 이름 기반 파라미터 바인딩을 사용하려면 @Param(파라미터 이름) 어노테이션을 사용하면 된다.
  • 코드 가독성과 유지보수를 위해 이름 기반 파라미터 바인딩을 사용하자.

벌크성 수정 쿼리

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

반환 타입

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

페이징과 정렬

  • 스프링 데이터 JPA는 쿼리 메소드에 페이징과 정렬 기능을 사용할 수 있도록 2가지 특별한 파라미터를 제공한다.
    • Sort: 정렬 기능
    • Pageable: 페이징 기능(내부에 Sort 포함)
  • 파라미터에 Pageable을 사용하면 반환 타입으로 ListPage를 사용할 수 있다.
    • Page를 사용하면 스프링 데이터 JPA는 페이징 기능을 제공하기 위해 검색된 전체 데이터 건수를 조회하는 count 쿼리를 추ㅏㄱ로 호출한다.
  • 참고로 페이지는 0부터 시작한다.

힌트

  • JPA 쿼리 힌트를 사용하려면 @QueryHints 어노테이션을 사용하면 된다.
  • 참고로 이것은 SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트다.

Lock

  • 쿼리 시 락을 걸려면 @Lock 어노테이션을 사용하면 된다.

명세

  • 책 도메인 주도 설계는 명세라는 개념을 소개하는데, 스프링 데이터 JPA는 JPA Criteria로 이 개념을 사용할 수 있도록 지원한다.
  • 명세를 이해하기 위한 핵심 단어는 술어인데 이것은 단순히 참이나 거짓으로 평가된다.
  • 스프링 데이터 JPA는 Specification 클래스로 정의했다.
    • Specification은 컴포지트 패턴으로 구성되어 있어서 여러 Specfication을 조합할 수 잇다.
  • 명세 기능을 사용하려면 리포지토리에서 JpaSpecificationExecutor 인터페이스를 상속받으면 된다.
  • 명세를 사용하는 코드
    • where(), and(), or(), not() 메소드를 제공한다.
  • 명세를 정의하는 코드
    • 며엣를 정의할 때는 toPredicate() 메소드만 구현하면 되는데 JPA Criteria의 Root, CriteriaQuery, CriteriaBuilder 클래스가 모두 파라미터로 주어진다.

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

  • 스프링 데이터 JPA는 필요한 메소드만 직접 구현할 수 있는 방법을 제공한다.
  • 먼저 직접 구현할 메소드를 위한 사용자 정의 인터페이스를 작성해야 한다.
  • 다음으로 사용자 정의 인터페이스를 구현한 클래슬르 작성해야한다.
    • 이때 클래스 이름을 짓는 규칙이 있는데 리포지토리 인터페이스 이름 + Impl로 지어야 한다.
  • 마지막으로 리포지토리 인터페이스 사용자 정의 인터페이스를 상속받으면 된다.
  • 만약 사용자 정의 구현 클래스 이름 끝에 Impl 대신 다른 이름ㅇ르 붙이고 싶으면 repositoryImplementationPostfix 속성을 변경하면 된다.

Web 확장

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

설정

  • 스프링 데이터가 제공하는 Web 확장 기능을 활성화하려면 @EnablespringDatanebsupport어노테이션을 사용하면 된다.

도메인 클래스 컨버터 기능

  • 도메인 클래스 컨버터는 HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩해준다.
  • OSIV를 사용하지 않으면 조회한 엔티티는 준영속 상태로, 엔티티를 컨트롤러에서 수정해도 실제 데이터베이스에는 반영되지 않는다.

페이징과 정렬 기능

  • 스프링 데이터가 제공ㄹ하는 페이징과 정렬 기능을 스프링 MV에서 편리하게 사용할 수 있도록 HandlerMethodArgumentResolver를 제공한다.
    • 페이징 기능: PageableHandlerMethodArgumentResolver
    • 정렬 기능: SortHandlerMethodArgumentReesovler
  • PageablePageRequest의 인터페이스다.
    • page: 현재 페이지, 0부터 시작
    • size: 한 페이지에 노출할 데이터 건수
    • sort: 정렬 조건
    • 예:
  • 사용해야 할 페이징 정보가 둘 잇아이면 접두사를 사용해서 구분할 수 있다.
  • Pageable의 기본값은 page=0, size=20이다.
    • 만약 기본값을 변경하고 싶으면 @PageableDefault 어노테이션을 사용하면 된다.

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

  • 스프링 데이터 JPA가 제공하는 공통 인터페이스는 SimpleJpaRepository 클래스가 구현한다.
  • 새로운 엔티티를 판단하는 기본 전략은 엔티티의 식별자로 판단하는데 식별자가 객체일 때 null, 자바 기본 타입일 때 0 값이면 새로운 엔티티로 판단한다.
    • 필요하면 엔티티에 Persistable 인터페이스를 구현해서 판단 로직을 변경할 수 있다.

스프링 데이터 JPA와 QueryDSL 통합

QueryDslPredicateExecutor 사용

  • 리포지토리에서 QueryDslPredicateExecutor를 상속받으면 된다.
  • QueryDslPredicateExecutor는 join, fetch 등 사용할 수 있는 기능에 한계가 있다. 따라서 QueryDSL이 제공하는 다양한 기능을 사용하려면 JPAQuery를 직접 사용하거나 스프링 데이터 JPA가 제공하는 QueryDslRepositorySupport를 사용해야 한다.

QueryDslRepositorySupport 사용

  • QueryDslRepositorySupport를 상속받아 사용하면 조금 더 편리하게 QueryDSL을 사용할 수 있다.
  • 커스텀 리포지토리를 만들어서 QueryDslRepositorySupport를 상속받는다.
  • 아래는 QueryDslRepositorySupport의 핵심 기능만 간추려보았다.