쌩로그

자바 ORM 표준 JPA 프로그래밍 Ch.12. 스프링 데이터 JPA 본문

Spring/JPA

자바 ORM 표준 JPA 프로그래밍 Ch.12. 스프링 데이터 JPA

.쌩수. 2024. 2. 21. 02:12
반응형

목록

  1. 포스팅 개요
  2. 본론
      2-1. 스프링 데이터 JPA 소개(12장 1절)
      2-2. 공통 인터페이스 소개(12장 3절)
      2-3. 쿼리 메소드 기능(12장 4절)
      2-4. Web 확장(12장 7절)
  3. 요약

1. 포스팅 개요

자바 ORM 표준 JPA 프로그래밍12장 스프링 데이터 JPA를 학습하며 정리한 포스팅이다.

참고로 이전에 스프링 데이터 JPA 강의 포스팅을 했던 것과 정말 많이 겹친다.
따라서 해당 포스팅에서 설명이 부족했던 부분을 좀 더 채우는 느낌의 포스팅이다.

해당 포스팅 이후 14장 일부, 16장 내용이 정리되어 포스팅 될 예정이다.
(정리라곤 하지만, 한 문장 한 문장이 주옥이라... 그냥 받아쓰기가 되고 있는 것이 현실이다.)

2. 본론

대부분의 데이터 접근 계층(Data Access Layer)은 일명 CRUD로 부르는 유사한 등록, 수정, 삭제, 조회 코드를 반복해서 개발해야 한다.
JPA를 사용해서 데이터 접근 계층을 개발할 때도 이 같은 문제가 발생한다.


public class MemberRepository {

   @PersistenceContext
   EntityManager em;

   public void save(Member member) {...}
   public Member findOne(Long id) {...}
   pubilc List<Member> findAll() {...}

   pubilc Member findByUsername(String username) {...}
}

public class ItemRepository {

   @PersistenceContext
   EntityManager em;

   public void save(Item item) {...}
   pubilc Member findOne(Long id) {...}
   pubilc List<Member> findAll() {...}

}

위의 코드를 보면 회원 리포지토리(MemberRepository)와 상품 리포지토리(ItemRepository)가 하는 일이 비슷하다.

제네릭과 상속을 적절히 사용해서 공통 부분을 처리하는 부모 클래스를 만들면 된다.
이것을 보통 GenericDAO라 한다.
하지만 이 방법은 공통 기능을 구현한 부모 클래스에 너무 종속되고 구현 클래스 상속이 가지는 단점에 노출된다.

2-1. 스프링 데이터 JPA 소개(12장 1절)

스프링 데이터 JPA스프링 프레임워크에서 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트다.
이 프로젝트는 데이터 접근 계층을 개발할 때 지루하게 반복되는 CRUD 문제를 세련된 방법으로 해결한다.
우선 CRUD를 처리하기 위한 공통 인터페이스를 제공한다.
리포지토리를 개발할 때 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성해서 주입해준다.
따라서 데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발을 완료할 수 있다.

위의 코드애서 살펴본 회원과 상품 리포지토리를 스프링 데이터 JPA를 사용해서 개발하면 다음 코드와 같이 인터페이스만 작성하면 된다.
CRUD를 처리하기 위한 공통 메소드는 스프링 데이터 JPA가 제공하는 org.springframework.data.jpa.repository.JpaRepository 인터페이스에 있다.
그리고 인터페이스의 구현체는 애플리케이션 실행 시점에 스프링 데이터 JPA가 생성해서 주입해준다.
따라서 개발자가 직접 구현체를 개발하지 않아도 된다.

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

public interface ItemRepository extends JpaRepository<Item, Long> {

}

클래스 다이어그램은 다음 그림과 같다.

일반적인 CRUD 메서드는 JpaRepository 인터페이스가 공통으로 제공하므로 문제가 없다.
그런데 MemberRepository.findByUsername(...)처럼 직접 작성한 공통으로 처리할 수 없는 메서드는 어떻게 해야할까?
놀랍게도 스프링 데이터 JPA는 메서드 이름을 붙석해서 다음 JPQL을 실행한다.

select m from Member m where username = :username

스프링 데이터 프로젝트

스프링 데이터 JPA스프링 데이터 프로젝트의 하위 프로젝트 중 하나다.
다음 그림을 보자.

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

여기서 스프링 데이터 JPA 프로젝트JPA에 특화된 기능을 제공한다.
스프링 프레임워크과 JPA를 함께 사용한다면 스프링 데이터 JPA 사용을 적극 추천한다.

2-2. 공통 인터페이스 소개(12장 3절)

스프링 데이터 JPA는 간단한 CRUD 기능을 공통으로 처리하는 JpaRepository 인터페이스를 제공한다.
스프링 데이터 JPA를 사용하는 가장 단순한 방법은 다음 코드와 같이 JpaRepository 인터페이스를 상속받는 것이다.
그리고 제네릭에 엔티티 클래스와 엔티티 클래스가 사용하는 식별자 타입을 지정하면 된다.

poblic interface MemberRepository extends JpaRepository<Member, Long> {
}

JpaRepository 인터페이스를 상속받으면 사용할 수 있는 주요 메서드 몇 가지를 간단히 소개한다.
(참고로 T는 엔티티, ID는 엔티티의 식별자 타입, S는 엔티티와 그 자식 타입을 뜻한다.)

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

save(S) 메서드는 엔티티에 식별자 값이 없으면(null이면) 새로운 엔티티로 판단해서 EntityManager.persist()를 호출하고 식별자 값이 있으면 이미 있는 엔티티로 판단해서 EntityManager.merge()를 호출한다.
필요하다면 스프링 데이터 JPA의 기능을 확장해서 신규 엔티티 판단 전략을 변경할 수 있다.

2-3. 쿼리 메소드 기능(12장 4절)

메서드 이름으로 쿼리 생성

(부족한 설명을 보완하는 차원에서 살짝 작성한다. 실전! 스프링 데이터 JPA 포스팅을 참고하길 바란다.)

참고로 이 기능은 엔티티의 필드명이 변경되면 인터페이스에 정의한 메소드 이름도 꼭 함께 변경해야 한다.
그렇지 않으면 애플리케이션을 시작하는 시점에 오류가 발생한다.

파라미터 바인딩

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

select m from Member m where m.username = ?1 // 위치 기반
select m from Member m where m.username = :name // 이름 기반

(예시가 없어서 작성했다.)

반환 타입

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

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

만약 조회 결과가 없으면 컬렉션은 빈 컬렉션을 반환하고 단건은 null을 반환한다.
그리고 단 건을 기대하고 반환 타입을 지정했는데 결과가 2건 이상 조회되면
jakarta.persistence.NonUniqueResultException 예외가 발생한다.

참고로 단건으로 지정한 메서드를 호출하면 스프링 데이터 JPA는 내부에서 JPQL의 Query.getSingResult() 메서드를 호출한다.
이 메소드를 호출했을 때 조회 결과가 없으면 jakarta.persistence.NoResultException 예외가 발생하는데 개발자 입장에서 다루기가 상당히 불편하다.
스프링 데이터 JPA는 단건을 조회할 때 이 예외가 발생하면 예외를 무시하고 대신에 null을 반환한다.

힌트

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

2-4. Web 확장(12장 7절)

스프링 데이터 프로젝트는 스프링 MVC에서 사용할 수 있는 편리한 기능을 제공한다.
식별자로 도메인 클래스를 바로 바인딩해주는 도메인 클래스 컨버터 기능과 페이징과 정렬 기능을 제공하도록 확장 기능을 활성화해보자.
(참고로 이후 나올 Bean으로 등록해주는 부분이 없어서 참고 삼아 작성한다.)

설정

스프링 데이터가 제공하는 Web 확장 기능을 활성화하려면 org.sprinframework.data.web.config.SpringDataWebConfiguration을 스프링 빈으로 등록하면 된다.

JavaConfig를 사욯하면 다음과 같이 org.springframework.data.web.config.EnableSpringDataWebSupport 어노테이션을 사용하면 된다.

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
public class WebAppConfig {
   ...
}

설정을 완료하면 도메인 클래스 컨버터페이징과 정렬을 위한 HandlerMethodArgumentResolver가 스프링 빈으로 등록된다.
등록되는 도메인 클래스 컨버터는 다음과 같다.

org.springframework.data.repository.support.DomainClassConverter

(도메인 클래스 컨버터페이징과 정렬을 사용하기 위한 설정 부분이 강의에는 없는 내용이므로 포스팅한다.)

3. 요약

스프링 데이터 JPA는 지루한 데이터 접근 계층의 코드가 상당히 많이 줄어든 것을 알 수 있다.
스프링 데이터 JPA는 버전이 올라가면서 다양한 기능이 추가되고 있다.

영한님 왈 나는 스프링 프레임워크와 JPA를 함께 사용한다면 스프링 데이터 JPA는 선택이 아닌 필수라 생각한다고 하셨다...!!

실전! 스프링 데이터 JPA 포스팅 내용과 정말 비슷하다.
거기에 설명을 조금 덧붙인 포스팅이라고 생각하면 될 거 같다.

728x90
Comments