꿈꾸는 새벽하늘

12장. 스프링 데이터 JPA 본문

🌿 Spring & Spring Boot/📗 자바 ORM 표준 JPA 프로그래밍

12장. 스프링 데이터 JPA

rovemin 2023. 8. 14. 23:50

1. 스프링 데이터 JPA 소개

스프링 데이터 JPA는 스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트다. 이 프로젝트는 데이터 접근 계층 개발 시 반복되는 CRUD 문제를 깔끔하게 해결한다.

CRUD를 처리하기 위한 공통 인터페이스를 제공하고, 리포지토리를 개발할 때 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성해서 주입해준다. 따라서 데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발을 완료하는 것이 가능하다.

 

예시로 회원과 상품 리포지토리를 스프링 데이터 JPA를 사용해서 개발한다면 인터페이스만 작성하면 된다.

public interface MemberRepository extends JpaRepository>Member, Long> {
    Member findByUsername(String username);
}

public interface ItemRepository extends JpaRepository>Member, Long> {
}

위 코드의 MemberRepository.findByUsername(...) 메소드는 스프링 데이터 JPA가 메소드 이름을 분석해서 아래와 같은 JPQL을 실행한다.

select m from Member m where username = :username

2. 스프링 데이터 JPA 설정

  • 스프링 데이터 JPA 메이븐 라이브러리 설정
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-jpa</artifactId>
    <version>1.8.0.RELEASE</version>
</dependency>
  • XML 설정
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:jpa="http://www.springframework.org/schema/data/jpa"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
    
  <jpa:repositories base-package="jpabook.jpashop.repository" />
    
</beans>
  • JavaConfig 설정
@Configuration
@EnableJpaRepositories (basePackages = "jpabook.jpashop.repository")
public class AppConfig {}

3. 공통 인터페이스 기능

스프링 데이터 JPA는 간단한 CRUD 기능을 공통으로 처리하는 JpaRepository 인터페이스를 제공한다.

 

JpaRepository 인터페이스를 상속받으면 사용할 수 있는 주요 메소드

(T: 엔티티, ID: 엔티티의 식별자 타입, S: 엔티티와 그 자식 타입)

  • save(S): 새로운 엔티티를 저장하고 이미 있는 엔티티는 수정한다.
  • delete(T): 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove()를 호출한다.
  • findOne(ID): 엔티티 하나를 조회한다. 내부에서 EntityManager.find()를 호출한다.
  • getOne(ID): 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference를 호출한다.
  • findAll(...): 모든 엔티티를 조회한다. 정렬(Sort)이나 페이징(Pageable) 조건을 파라미터로 제공할 수 있다.

4. 쿼리 메소드 기능

스프링 데이터 JPA가 제공하는 쿼리 메소드 기능

  • 메소드 이름으로 쿼리 생성
  • 메소드 이름으로 JPA NamedQuery 호출
  • @Query 어노테이션을 사용해서 리포지토리 인터페이스에 쿼리 직접 정의

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

메소드 이름만으로 쿼리를 생성하는 기능은 인터페이스에 메소드만 선언하면 해당 메소드의 이름으로 적절한 JPQL 쿼리를 생성해서 실행한다.

 

이메일과 이름으로 회원을 조회하는 메소드이다.

public interface MemberRepository extends Repository<Member, Long> {
    List<Member> findByEmailAndName(String email, String name);
}

findByEmailAndName(...) 메소드를 실행하면 스프링 데이터 JPA는 메소드 이름을 분석해서 JPQL을 생성하고 실행한다.

select m from Member m where m.email = ?1 and m.name = ?2

2) JPA NamedQuery

JPA NamedQuery는 쿼리에 이름을 부여해서 사용하는 방법으로, 어노테이션이나 XML에 쿼리를 정의할 수 있다.

 

  • @NamedQuery 어노테이션으로 Named 쿼리 정의
@Entity
@NamedQuery (
    name = "Member.findByUsername",
    query = "select m from Member m where m.username = :username")
public class Member {
    ...
}
  • 스프링 데이터 JPA로 NamedQuery 호출
public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findByUsername(@Param("username") String username);
}

스프링 데이터 JPA는 선언한 "도메인 클래스 + .(점) + 메소드 이름"으로 Named 쿼리를 찾아서 실행한다.

따라서 위 코드는 Member.findByUsername이라는 Named 쿼리를 실행한다.

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

리포지토리 메소드에 직접 쿼리를 정의하려면 @org.springframework.data.jpa.repository.Query 어노테이션을 사용한다.

이 방법은 실행한 메소드에 정적 쿼리를 직접 작성하므로 이름 없는 Name 쿼리라고 할 수 있다.

그리고 JPA Named 쿼리처럼 애플리케이션 실행 시점에 문법 오류를 발견할 수 있다는 장점이 있다.

 

  • 메소드에 JPQL 쿼리 작성
public interface MemberRepository extends JpaRepository<Member, Long> {
    @Query ("select m from Member m where m.username = ?1")
    Member findByUsername(String username);
}

4) 파라미터 바인딩

스프링 데이터 JPA는 위치 기반 파라미터 바인딩과 이름 기반 파라미터 바인딩을 모두 지원한다.

기본값은 위치 기반 파라미터 바인딩이고, 이름 기반 파라미터 바인딩을 사용하려면 @org.springframework.data.repository.query.Param 어노테이션을 사용한다.

 

  • 위치 기반 파라미터 바인딩: select m from Member m where m.username = ?1
  • 이름 기반 파라미터 바인딩: select m from Member m where m.username = :name

5) 반환 타입

스프링 데이터 JPA는 유연한 반환 타입을 지원하는데 결과가 한 건 이상이면 컬렉션 인터페이스를 사용하고, 단건이면 반환 타입을 지정한다.

List<Member> findByName(String name);	// 컬렉션
Member findByEmail(String email);	// 단건

6) 페이징과 정렬

스프링 데이터 JPA는 쿼리 메소드에 페이징과 정렬 기능을 사용할 수 있도록 2가지 파라미터를 제공한다.

 

  • @org.springframework.data.domain.Sort : 정렬 기능
  • @org.springframework.data.domain.Pageable : 페이징 기능 (내부에 Sort 포함)

파라미터에 Pageable을 사용하면 반환 타입으로 List나 Page를 사용할 수 있다.

반환 타입으로 Page를 사용하면 스프링 데이터 JPA는 페이징 기능을 제공하기 위해 검색된 전체 데이터 건수를 조회하는 count 쿼리를 추가로 호출한다.

// count 쿼리 사용
Page<Memher> findByName(String name, Pageable pageable);

// count 쿼리 사용 안 함
List<Memher> findByName(String name, Pageable pageable);
List<Memher> findByName(String name, Sort sort);

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

스프링 데이터 JPA로 리포지토리를 개발하면 인터페이스만 정의하고 구현체는 만들지 않는다.

그러나 메소드를 직접 구현해야 하는 경우가 생겨서 리포지토리를 직접 구현하게 된다면, 공통 인터페이스가 제공하는 기능까지 모두 구현해야 한다. 스프링 데이터 JPA는 이런 문제를 우회해서 필요한 메소듬나 구현할 수 있는 방법을 제공한다.

 

  • 사용자 정의 인터페이스
public interface MemberRepositoryCustom {
    public List<Member> findMemberCustom();
}
  • 사용자 정의 구현 클래스
public class MemberRepositoryImpl implements MemberRepositoryCustom {
    @Override
    public List<Member> findMemberCustom() {
    	...	// 사용자 정의 구현
    }
}
  • 사용자 정의 인터페이스 상속
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
}

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

  • @Repository 적용
    • JPA 예외를 스프링이 추상화한 예외로 변환한다.
  • @Transactional 트랜잭션 적용
    • JPA의 모든 변경은 트랜잭션 안에서 이루어져야 한다.
    • 스프링 데이터 JPA가 제공하는 공통 인터페이스를 사용하면 데이터를 변경(등록, 수정, 삭제)하는 메소드에 @Transactional로 트랜잭션 처리가 되어 있다.
    • 서비스 계층에서 트랜잭션을 시작하지 않으면 리포지토리에서 트랜잭션을 시작하는데, 서비스 계층에서 트랜잭션을 시작했으면 리포지토리도 해당 트랜잭션을 전파받아서 그대로 사용한다.
  • @Transactional(readOnly = true)
    • 데이터를 조회하는 메소드에는 readOnly = true 옵션이 적용되어 있다.
  • save() 메소드
    • 저장할 엔티티가 새로운 엔티티면 저장(persist)하고 이미 있는 엔티티면 병합(merge)한다.