스프링 데이터 JPA 소개
- 스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트
- CRUD를 처리하기 위한 공통 인터페이스 제공
- 레포지토리를 개발할 때 엔터피에스만 작성하면 실행 시점에 스프링 데이터 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와 같은 다양한 데이터 저장소에 대한 접근을 추상화해서 개발자 편의를 제공하고 지루하게 반복하는 데이터 접근 코드를 줄여줌
스프링 데이터 JPA 설정
이 책이 집필된 지 10년이 넘었기 때문에, 책에서 언급된 라이브러리 버전은 더 이상 최신이 아닙니다.
1. 필요 라이브러리
2. 환경 설정
- 스프링 설정에 JavaConfig를 사용하면 다음과 같이 `org.springframework.data.jpa.repository.config.EnableJpaRepositories` 어노테이션을 추가하고 basePackages에는 레포지토리를 검색할 패키지 위치를 기입
- 스프링 데이터 JPA는 애플리케이션을 실행할 때 basePackage에 있는 레포지토리 인터페이스를 찾아서 해당 인터페이스를 구현한 클래스를 동적으로 생성한 뒤 스프링 빈으로 등록
- 이에 따라 개발자가 직접 구현 클래스를 생성하지 않아도 됨
공통 인터페이스 기능
- 스프링 데이터 JPA는 간단한 CRUD 기능을 공통으로 처리하는 JpaRepository 인터페이스를 제공
- 스프링 데이터 JPA를 사용하는 가장 단순한 방법은 해당 인터페이스를 상속받은 후 제네릭에 엔티티 클래스와 엔티티 클래스가 사용하는 식별자 타입을 지정하는 것
부연 설명
- 윗부분에 스프링 데이터 모듈이 있고 그 안에 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 프로그래밍 - 김영한 저
'DB > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
[14장] 컬렉션과 부가 기능 (0) | 2025.03.19 |
---|---|
[13장] 웹 애플리케이션과 영속성 관리 (0) | 2025.03.18 |
[10장] 객체지향 쿼리 언어 (0) | 2025.03.09 |
[9장] 값 타입 (0) | 2025.03.04 |
[8장] 프록시와 연관관계 관리 (0) | 2025.03.01 |