해당 포스팅은 [ 자바 ORM 표준 JPA 프로그래밍 - 김영한 저 ]를 학습한 내용을 바탕으로 정리한 글입니다.
순수 JPA 기반 리포지토리로 개발 시 문제
@Repository
public class MemberJpaRepository {
@PersistenceContext
private EntityManager em;
public Member save(Member member) { ... }
public Member find(Long id) { ... }
public void delete(Member member) { ... }
public List<Member> findAll() { ... }
public long count() { ... }
public List<Member> findByUsername(String username) { ... }
public List<Member> findByPage(int age, int offset, int limit) { ... }
public long totalCount(int age) { ... }
public int bulkAgePlus(int age) { ... }
}
- 순수 JPA 기반 각 도메인의 레포지토리를 만들면 중복되는 코드들이 많이 생김(save, find, findAll, delete, count, findByUsername 등)
- 회원 엔티티의 Repository나 팀 엔티티의 Repository나 중복되는 공통 기능들을 일일히 코드를 짜야하는 문제가 있음
스프링 데이터 JPA
스프링 데이터 JPA는 JPA를 더 편리하게 사용할 수 있도록 스프링 프레임워크가 지원하는 프로젝트이다. 데이터 접근 계층을 개발할 때 지루하게 반복되는 CRUD 문제를 해결 해준다.
우선 CRUD를 처리하기 위한 공통 인터페이스를 제공한다. 그리고 Repository 개발 시 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현 객체를 동적으로 생성해서 주입해준다. 즉 데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 마법같이 개발을 완료할 수 있다.
일반적인 CRUD 메소드(save, find, delete 등)는 JpaRepository 인터페이스만 상속 받아도 공통으로 제공한다. 그리고 아래 코드처럼 findByUsername(String username)처럼 관례에 맞게 메소드 이름을 지어주면 자동으로 스프링 데이터 JPA가 메소드 이름일 분석해 적절한 JPQL을 실행한다.
public interface MemberRepository extends JpaRepository<Member, Long>{
public List<Member> findByUsername(String username);
}
- 실행되는 JPQL:
select m from Member m where username = :username
이제 이 스프링 데이터 JPA가 제공하는 놀라운 마법이 어떻게 이루어지는지 알아보자.
스프링 데이터 JPA 설정
의존성 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
- Spring Boot를 이용하면 위와 같이 의존성을 추가하면 버전을 포함한 기본적인 설정을 모두 자동으로 해준다.
- 자동 설정에 의해 @SpringBootApplication 어노테이션이 붙은 클래스를 기점으로 모든 해당 패키지와 하위 패키지를 모두 스캔하여 Spring 데이터 JPA 관련 로직들을 모두 빈 등록한다.
- 따라서 Repository 인터페이스를 구현하면 해당 인터페이스를 구현한 클래스를 동적으로 생상한 다음 스프링 빈으로 등록한다. 개발자가 직접 구현한 클래스를 만들지 않아도 된다.
JavaConfig 설정- 스프링 부트 사용시 생략 가능
@Configuration
@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository")
public class AppConfig { }
- Spring Boot가 제공하는 자동 설정 방법을 사용하지 않으려면, @EnableJpaRepositories 어노테이션을 사용하여 basePakage를 지정 하면 된다.
스프링 데이터 JPA가 구현 클래스 대신 생성
org.springframework.data.repository.Repository
를 구현한 클래스는 스캔 대상- MemberRepository 인터페이스가 동작한 이유
- 실제 출력해보기(Proxy)
- memberRepository.getClass() class com.sun.proxy.$ProxyXXX
→ 스프링 데이터 JPA가 인터페이스 구현체를 프록시로 구성하여 빈 등록함
@Repository
애노테이션 생략 가능- 컴포넌트 스캔을 스프링 데이터 JPA가 자동으로 처리
- JPA 예외를 스프링 예외로 변환하는 과정도 자동으로 처리
이제 JpaRepository 인터페이스가 제공하는 기능들을 알아보자.
공통 인터페이스 기능
공통 인터페이스 적용
스프링 데이터 JPA 기반 MemberRepository
public interface MemberRepository extends JpaRepository<Member, Long> {
}
- Generic
- T: 엔티티 타입
- ID: 식별자 타입(PK)
JpaRepository를 상속 받으며 제네릭에 회원 엔티티와 회원 엔티티의 실별자 타입을 지정한다. 이 구성만 갖춰도 JpaRepository가 제공하는 다양한 기능을 사용할 수 있다. 이제 JpaRepository가 어떤 기능들을 제공하는 지 알아보겠다.
JpaRepository 인터페이스의 계층 구조
- 주의!
T findOne(ID)
가 →Optional<T> findById(ID)
로 변경 - 제네릭 타입
- T : 엔티티
- ID : 엔티티의 식별자 타입
- S : 엔티티와 그 자식 타입
Repository, CrudRepository, PagingAndSortingRepository는 스프링 데이터 프로젝트가 공통으로 지원하는 인터페이스이다. 스프링 데이터 JPA가 제공하는 JpaRepository는 추가로 JPA에 특화된 기능을 제공한다.
주요 메소드
- save(S) : 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합한다.
→ 엔티티에 식별자 값이 없으면(null이면) em.persist()를 호출, 식별자 값이 있으면 이미 있는 엔티티라 판단하여 em.merge() 호출 - delete(T) : 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove() 호출
- findById(ID) : 엔티티 하나를 조회한다. 내부에서 EntityManager.find() 호출
- getOne(ID) : 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference() 호출
- findAll(…) : 모든 엔티티를 조회한다. 정렬( Sort )이나 페이징( Pageable ) 조건을 파라미터로 제공할 수 있다.
위와 같은 기능들을 제공하기 때문에 JpaRepository 인터페이스를 사용하면 일반적인 CRUD를 따로 직접 구현하지 않아도 해결할 수 있다.
'Study > spring' 카테고리의 다른 글
QueryDSL(1)- QueryDSL과 설정 방법 (0) | 2021.01.25 |
---|---|
Spring Data JPA(2) - JpaRepository 쿼리 메소드 기능 (0) | 2021.01.20 |
JPA - OSIV(Open Session In View) 정리 (18) | 2021.01.18 |
MockMvc를 이용한 REST API의 Json Response 검증 (0) | 2021.01.14 |
자바 ORM 표준 JPA 프로그래밍(14) - 경로 표현식, 페치 조인, 다형성 쿼리, Named 쿼리, 벌크 연산 (0) | 2021.01.10 |