////
Search
Duplicate
🎲

12장. 스프링 데이터 JPA

대부분의 데이터 접근 계층은 CRUD를 반복해서 개발해야 한다. JPA를 사용해서 데이터 접근 계층을 개발할 때도 이 같은 문제가 발생한다.
이는 제네릭과 상속을 적절히 사용해서 공통 부분을 처리하는 부모 클래스를 만들면 된다. 이것을 보통 GenericDAO라 한다. 하지만 이는 공통 기능을 구현한 부모 클래스에 너무 종속되고 상속이 가는 단점들이 노출된다.

1. 스프링 데이터 JPA 소개

스프링 데이터 JPA는 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트다.
우선 CRUD를 처리하기 위한 공통 인터페이스를 제공하며 레포지터리를 개발할 때 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성하여 주입해준다.
따라서 데이터 접근 계층을 개발할 때 구상 클래스 없이 인터페이스만으로도 개발이 완료된다.
public interface MemberRepository extends JpaRepository<Member, Long> { Member findByUsername(String username); }
Java
복사
위 코드에서 findByUsername 메소드의 경우, 스프링 데이터 JPA가 메소드 이름을 분석하여 JPQL을 실행한다.

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

스프링 데이터 JPA는 스프링 데이터 프로젝트의 하위 프로젝트 중 하나로 스프링 데이터 프로젝트는 JPA, 몽고DB, NEO4j, REDIS, HADDOP 같은 다양한 데이터 저장소에 대한 접근을 추상화하여 편의를 제공한다.

2. 스프링 데이터 JPA 설정

필요 라이브러리
스프링 데이터 JPA는 spring-data-jpa 라이브러리가 필요하다.
spring-data-commons에 의존한다.

3. 공통 인터페이스 기능

스프링 데이터 JPA는 간단한 CRUD 기능을 공통으로 처리하는 JpaRepository 인터페이스를 제공한다.
JpaRepository<Member, Long> 부분을 보면 제네릭으로 회원 엔티티와 회원 엔티티의 식별자 타입을 지정한다.
주요 메소드는 다음과 같다.
save(S): 새로운 엔티티를 저장하고 이미 존재하는 엔티티는 수정한다.
delete(T): 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove()를 호출한다.
findOne(ID): 엔티티 하나를 조회한다. 내부에서 EntityManager.find()를 호출한다.
getOne(ID): 엔티티 하나를 조회한다. 내부에서 EntityManager.getReference()를 호출한다.
findAll(…): 모든 엔티티를 조회한다. 정렬(Sort)이나 페이징(Pageable) 조건을 매개변수로 제공할 수 있다.
save(S)의 경우 엔티티에 식별자 값이 없으면 새로운 엔티티로 판단하여 persist()를 호출하고 식별자 값이 있으면 이미 있는 엔티티로 판단해서 merge()를 호출한다.

4. 쿼리 메소드 기능

쿼리 메소드 기능은 스프링 데이터 JPA가 제공하는 기능으로 대표적으로 메소드 이름만으로 쿼리를 생성하는 기능이 있다.
스프링 데이터 JPA가 제공하는 쿼리 메소드는 크게 3가지가 있다.
메소드 이름으로 쿼리 생성
메소드 이름으로 JPA NamedQuery 호출
@Query 어노테이션을 사용해서 레포지터리 인터페이스에 쿼리 직접 정의
이 기능들을 활용하면 인터페이스만으로 피룡한 대부분의 쿼리 기능을 개발할 수 있다.

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

만약 회원의 속성 중 이름과 이메일로 조회하고 싶다면 다음처럼 메소드를 정의하면 된다.
List<Member> findByEmailAndName(String email, String name);
물론 정해진 규칙에 따라서 메소드 이름을 지어야 한다. 스프링 데이터 JPA 공식문서를 확인하면 쉽게 사용할 수 있다.
이 기능은 엔티티의 속성 명이 변경되면 인터페이스에 정의한 메소드 이름도 꼭 함께 변경해야 한다. 그렇지 않으면 애플리케이션을 시작하는 시점에 오류가 발생한다.

2. JPA NamedQuery

스프링 데이터 JPA는 메소드 이름으로 JPA Named 쿼리를 호출하는 기능을 제공한다.
JPA Named 쿼리는 이름 그대로 쿼리에 이름을 부여해서 사용하는 방법으로 어노테이션이나 XML에 쿼리를 정의해두고 사용할 수 있다.

3. @Query, 레포지터리 메소드에 쿼리 정의

레포지터리 메소드에 직접 쿼리를 정의하려면 @org.springframework.data.jpa.repository.Query 어노테이션을 사용한다.
이 방법은 실행할 메소드에 정적 쿼리를 직접 작성하므로 이름 없는 Named 쿼리라고 할 수 있다.
네이티브 쿼리를 사용하고 싶다면 nativeQuery 옵션을 사용하면 된다.

4. 파라미터 바인딩

스프링 데이터 JPA는 위치 기반 파라미터 바인딩과 이름 기반 파라미터 바인딩을 모두 지원한다.
select m from Member m where m.username = ?1; select m from Member m where m.username = :name;
Java
복사
기본값은 위치 기반으로 파라미터 순서로 바인딩한다. 이름 기반 바인딩을 사용하려면 org.springframework.data.repository.query.Param() 어노테이션을 사용하면 된다.

5. 벌크성 수정 쿼리

스프링 데이터 JPA에서 벌크성 수정, 삭제 쿼리는 org.springframework.data.jpa.repository.Modifying 어노테이션을 사용한다.
벌크성 쿼리 실행 후 영속성 컨텍스트를 초기화하고 시픙면 clearAutomatically 옵션을 true로 설정하면 된다. 기본값은 false다.

6. 반환 타입

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

7. 페이징과 정렬

스프링 데이터 JPA는 쿼리 메소드에 페이징과 정렬 기능을 사용할 수 있도록 2가지 특별한 파라미터를 제공한다.
org.springframework.data.domain.Sort: 정렬 기능
org.springframework.data.domain.Pageable: 페이징 기능
파라미터에 Pageable을 사용하면 반환 타입으로 Listorg.springframework.data.domain.Page를 사용할 수 있다.
Page를 사용하는 경우 스프링 데이터 JPA는 페이징 기능을 제공하기 위해 검색된 전체 데이터 건수를 조회하는 count 쿼리를 추가로 호출한다.

8. 힌트

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

9. Lock

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

5. 명세

스프링 데이터 JPA는 JPA Criteria로 명세라는 개념을 사용할 수 있도록 지원한다.
명세를 이해하기 위한 핵심 단어는 predicate인데 이것은 단순히 참이나 거짓으로 평가된다.
이 술어는 스프링 데이터 JPA에서 org.springframework.data.jpa.domain.Specification 클래스로 정의된다.
이 기능을 사용하려면 org.springframework.data.jpa.repository.JpaSpecificationExecutor 인터페이스를 상속받으면 된다.

6. 사용자 정의 레포지터리 구현

스프링 데이터 JPA로 레포지터리를 개발하면 인터페이스만 정의하고 구현체는 만들지 않는다.
하지만 다양한 이유로 메소드를 직접 구현해야할 때도 있는데, 그렇다고 레포지터리를 직접 구현하면 공통 인터페이스가 제공하는 기능까지 모두 구현해야 한다.
스프링 데이터 JPA는 이런 문제를 우회해서 필요한 메소드만 구현할 수 있는 방법을 제공한다.
먼저 직접 구현할 메소드를 위한 사용자 정의 인터페이스를 작성해야 한다.
다음으로 사용자 정의 인터페이스를 구현한 클래스를 작성해야 하는 데, 이때 클래스 이름을 레포지터리 인터페이스 이름 + Impl로 지어야 한다.
이후 레포지터리 인터페이스에서 사용자 정의 인터페이스도 상속받으면 된다.

7. Web 확장

스프링 데이터 프로젝트는 스프링 MVC에서 사용할 수 있는 편리한 기능들을 제공한다.
식별자로 도메인 클래스를 바로 바인딩해주는 도메인 클래스 컨버터 기능
페이징과 정렬 기능

1. 설정

스프링 데이터가 제공하는 Web 확장 기능을 사용하려면 SpringDataWebConfiguration을 빈으로 등록하면 된다.
JavaConfig를 작성하는 경우 @EnableSpringDataWebSupport 어노테이션을 사용하면 된다.
설정을 완료하면 도메인 클래스 컨버터와 페이징과 정렬을 위한 HandlerMethodArgumentResolver가 빈으로 등록된다.
등록되는 도메인 클래스 컨버터는 org.springframework.data.repository.support.DomainClassConverter다.

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

도메인 클래스 컨버터는 HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩해준다.

3. 페이징과 정렬 기능

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

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

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

9. JPA 샵에 적용

앞선 예제에 적용하는 실습으로 생략하였습니다.

10. 스프링 데이터 JPA와 QueryDSL 통합

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

1. QueryDslPredicateExecutor 사용

public interface ExempleRepository extends JpaRepository<Example, Long>, QueryDslPredicateExecutor<Example> {}
Java
복사
이렇게 사용하면 예제 레포지터리에서 QueryDSL이 사용 가능해진다.
다만 이 경우 join, fetch를 사용할 수 없으므로 QueryDSL이 제공하는 다양한 기능을 사용하려면 JPAQuery를 직접 사용하거나 스프링 데이터 JPA가 제공하는 QueryDslRepositorySupport를 사용해야 한다.

2. QueryDslRepositorySupport 사용

QueryDSL의 모든 기능을 사용하려면 JPAQuery 객체를 직접 생성해서 사용하면 된다.
이때 스프링 데이터 JPA가 제공하는 QueryDslRepositorySupport를 상속받아 사용하면 조금 더 편리하게 사용할 수 있다.
public class ExampleRepositoryImpl extends QueryDslRepositorySupport implements CustomRepository {}
Java
복사