Spring Data란?
Spring Data란, Spring 프레임워크가 제공하는 추상화된 데이터 접근 기술이다.
데이터베이스나 ORM프레임워크의 종류에 따라 데이터접근방식은 다양하게 구현되지만... 사실, 기능은 거의 동일하다. 데이터를 CRUD하고 페이징 및 정렬과 같이, 기능은 동일하지만 In-Memeory DB이냐 Non-Relational DB이냐 Relational DB이냐 Cloud 기반이냐 혹은 JPA framework이냐 Map-Reduce Framework이냐에 따라 구체적인 구현만 다를뿐이다.
그래서 구체적인 구현은 구현체로 넘기고 기능은 인터페이스로 추상화한 모듈을 스프링이 제공하는데, 그것이 Spring Data이다. Spring Data를 활용하면, 데이터 엑세스 환경이 다르더라도 동일한 API로 데이터엑세스기술을 구현할 수 있다. 그러므로 Spring Data는 여러가지 환경에 맞는 하위 프로젝트를 제공한다. 그중 하나가 SpringDataJPA이다.
SpringDataJPA는 Spring Data의 하위프로젝트로, 데이터 엑세스 API를 JPA Framework에 맞게 구현한 모듈을 제공한다.
Spring Data JPA
대부분의 데이터엑세스기술은 위 그림과 같이 이루어진다. 데이터엑세스로직이 담긴 Repository가 있고 데이터가 담긴 DAO(Data Acess Object)가 있다. Repository는 DAO에서 데이터를 받아 SQL을 완성하고 이를 DB와의 커넥션을 통해 실행한다.
JPA는 DAO가 Entity이고 Repository는 EntityManager에 의존한다. EntityManager는 영속성컨텍스트에 접근하여 SQL쿼리문을 자동생성한다. 이로써 개발자는 SQL문 중심이 아닌 객체지향 중심 프로그래밍을 할 수 있게 되었다.
스프링데이터JPA는 여기서 한발 더 나아갔다.
개발자는 SQL문 작성을 더이상 안해도 되었지만 수많은 엔티티에 맞는 Repository를 매번 생성해야 했다. CRUD는 어느 Repository나 구현이 대부분 동일하다. 이런 소모적인 반복을 줄이고자 스프링데이터JPA는 공통적인 기능을 담은 Repository 구현체를 제공한다.
스프링데이터JPA가 제공하는 Repository 인터페이스를 상속한 MemberRepository
public interface MemberRepository extends JpaRepository<Member,Long> {
}
스프링데이터JPA는 JpaRepository 인터페이스를 제공한다. 제네릭으로 다양한 엔티티를 받아 구현체로 제공할 수 있다. MemberRepository는 Member 엔티티 전용 리포지토리로 JpaRepository 인터페이스를 상속하면 스프링데이터JPA가 제공하는 구현체를 사용할 수 있다. 개발자는 MemberRepository에 어떤 데이터엑세스 기능도 구현하지 않아도 기본적인 리포지토리 기능을 사용할 수 있다.
Test 코드
@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberRepositoryTest {
@Autowired
MemberRepository memberRepository; // 스프링데이터JPA 구현체 주입
@Test
public void testMember(){
Member member = new Member("memberA");
Member saveMember = memberRepository.save(member); // 저장기능
Member findMember = memberRepository.findById(saveMember.getId()).get();// 조회기능
assertThat(findMember.getId()).isEqualTo(member.getId());
assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
assertThat(findMember).isEqualTo(member);
}
}
위 테스트 코드를 보자. @Autowired로 스프링데이터JPA가 제공하는 리포지토리를 주입받을 수 있다. 그럼 save 메소드나 findById 메소드 등 리포지토리가 공통적으로 사용하는 기능을 사용할 수 있다. 이처럼 스프링데이터JPA는 개발자가 반복적인 소모성 작업을 하지 않도록 이미 자동완성된 리포지토리 구현체를 제공한다.
JpaRepository
그럼 JpaRepository를 자세히 보자.
JpaRepository 인터페이스
@NoRepositoryBean
public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
// 중략...
}
JpaRepository 인터페이스는 3가지 인터페이스를 상속한다. CrudRepository, PagingAndSortingRepository 그리고 QueryByExampleExecutor. QueryByExampleExecutor는 동적쿼리생성과 관련 있는 부분으로 일단 제외하겠다. 그럼 기본으로 CRUD 담당 리포지토리와 페이징및정렬 리포지토리를 상속한다. CRUD와 페이징, 정렬은 어떤 형태의 데이터엑세스 환경에서도 공통으로 적용되는 기능이다.
그래서 CrudRepository, PagingAndSortingRepository는 스프링데이터영역에서 제공한다.
( 이전에는 PagingAndSortingRepository가 CrudRepository를 상속하였으나 Spring Data 3.0 M2 버전 이후부터 두 Repository가 분리되었다. )
스프링 데이터 영역에서 제공하는 API를 가져와 JPA 방식으로 Override한 기능을 제공하는 인터페이스가 바로, JpaRepository이다. 개발영역의 리포지토리 인터페이스가 JpaRepository를 상속하면 스프링프레임워크에 의해 JpaRepository 구현체를 주입받을 수 있다.
JpaRepository 구현체는 SimpleJpaRepository이다.
SimpleJpaRepository
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
// 중략 ....
@Override
public Page<T> findAll(Pageable pageable) {
if (pageable.isUnpaged()) {
return new PageImpl<>(findAll());
}
return findAll((Specification<T>) null, pageable);
}
// 중략 ....
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
// 중략 ....
@Transactional
@Override
public void deleteById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
findById(id).ifPresent(this::delete);
}
}
SimpleJpaRepository는 @Repository 어노테이션을 가지고 있어 컴포넌트 스캔으로 컨테이너에 등록된다. 그리고 기본적으로 트랜잭션은 ReadOnly로 설정되어 있으며 save나 delete 같이 데이터 수정이 필요한 경우, @Transactional 어노테이션을 메소드에 선언하여 재정의 한다.
SimpleJpaRepository 구현체는 JpaRepository 인터페이스의 구현체이다. JpaRepository를 상속한 MemberRepository에 주입되는 구현체는 SimpleJpaRepository를 타겟으로 하는 '프록시(Proxy)' 객체이다.
Proxy 객체는 JpaRepository가 제공하는 기능과 더불어, MemberRepository에 정의된 기능까지 모두 사용 가능하다. 또한 스프링데이터JPA의 장점 중 하나인 메소드 이름으로 쿼리생성도 프록시 객체이기에 가능하다. 이런 원리로 스프링데이터JPA는 공통으로 사용되는 기능을 가진 리포지토리 구현체를 제공하여, 동일한 기능을 반복 작성하는 소모적인 작업을 줄일 수 있게 도와준다.
참고자료
'JPA > Spring Data JPA' 카테고리의 다른 글
[SpringDataJPA] @EntityGraph (0) | 2023.07.13 |
---|---|
[SpringDataJPA] 벌크성 수정쿼리 ( @Modifying ) (0) | 2023.07.13 |
[SpringDataJPA] 페이징 ( Pageable, Page, Slice ) (0) | 2023.07.12 |
[SpringDataJPA] @Query (0) | 2023.07.11 |
[SpringDataJPA] 메소드 이름으로 쿼리생성하기 (0) | 2023.07.11 |