본문 바로가기
Spring/Spring Data JPA

[Spring Data JPA] 2. 공통 인터페이스 기능

by yoon_seon 2023. 5. 17.

공통 인터페이스 기능

  1. 순수 JPA 리포지토리 만들기
  2. 공통 인터페이스 설정
  3. 공통 인터페이스 적용
  4. 공통 인터페이스 분석

📌 순수 JPA 기반 리포지토리 만들기

기본 CRUD 기능을 구현한다.

Member 엔티티의 CRUD는  MemberJpaRepository 클래스, Team 엔티티의 CRUD는 TeamJpaRepository 클래스에 구현.

 

1. Create(저장)

Member 저장

public Member save(Member member) {
     em.persist(member);
     return member;
}

Team 저장

public Team save(Team team){
    em.persist(team);
    return team;
}

 

2. Read(조회)

Member 조회

public List<Member> findAll() {
    return em.createQuery(
            "select m from Member m "
                    , Member.class)
            .getResultList();
}

public Optional<Member> findById(Long id) {
    Member member = em.find(Member.class, id);
    return Optional.ofNullable(member);
}

public long count() {
    return em.createQuery(
            "select count(m) from Member m"
                    ,Long.class)
            .getSingleResult();
}

Team 조회

public List<Team> findAll() {
    return em.createQuery(
            "select t from Team t"
            , Team.class)
            .getResultList();
}

public Optional<Team> findById(Long id) {
    Team team = em.find(Team.class, id);
    return Optional.ofNullable(team);
}

public long count() {
    return em.createQuery(
                    "select count(t) from Team t"
                    ,Long.class)
            .getSingleResult();
}

 

3. Update(변경)

  • JPA에서의 변경은 메서드를 사용하지 않고 변경감지를 이용한다.
    변경감지를 이용하면 트랜잭션 내에서 변경된 객체의 속성이 트랜잭션 종료 시점에 데이터베이스에 UPDATE 된다.

4. Delete(삭제)

Member 삭제

public void delete(Member member) {
    em.remove(member);
}

Team 삭제

public void delete(Team team) {
    em.remove(team);
}

 

순수 JPA를 이용한 CRUD 테스트

@Test
void basicCRUD() {
    Member member1 = new Member("member1");
    Member member2 = new Member("member2");
    memberJpaRepository.save(member1);
    memberJpaRepository.save(member2);

    // 단건 조회 검증
    Member findMember1 = memberJpaRepository.findById(member1.getId()).get();
    Member findMember2 = memberJpaRepository.findById(member2.getId()).get();
    assertThat(findMember1).isEqualTo(member1);
    assertThat(findMember2).isEqualTo(member2);

    // 리스트 조회 검증
    List<Member> all = memberJpaRepository.findAll();
    assertThat(all.size()).isEqualTo(2);

    // 카운트 검증
    long count = memberJpaRepository.count();
    assertThat(count).isEqualTo(2);

    // 삭제 검증
    memberJpaRepository.delete(member1);
    memberJpaRepository.delete(member2);
    long deletedCount = memberJpaRepository.count();
    assertThat(deletedCount).isEqualTo(0);
}

Member와 Team의 코드를 보면 순수 JPA로 구현한 CRUD 로직은 거의 동일하다. 이 문제를 해결하기 위해 Spring Data JPA는 공통 인터페이스를 포함한 많은 기능을 제공한다.

 

 

📌 공통 인터페이스 설정

1. JavaConfig 설정 - Spring Boot 사용 시 생략가능

@Configuration
@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository")
public class AppConfig {}
  • 스프링 부트 사용 시 @SpringBootApplication 가 작성된 설정파일의 위치의 해당 패키지와 하위 패키지에 Spring Data JPA를 사용할 수 있다.(@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository" 생략가능)
  • 스프링 부트를 사용하더라도 만약 위치가 달라지면 @EnableJpaRepositories 필요하다.

 

2. Spring data JPA가 구현 클래스 대신 생성

✔️JpaRepository를 상속받은 Interface가 구현클래스가 없는데도 동작하는 이유

이 전 포스팅에서 JpaRepository를 상속받은 MemberRepository 인터페이스는 구현 클래스가 없지만 save(), findById(), findAll()등의 메서드를 사용할 수 있었다.
실제로 JpaRepository를 상속받은 MemberRepository 인터페이스를 출력해 보면 프록시 객체인 것을 확인할 수 있다.

 Spring Data JPA가 런타임 시점에 구현 클래스를 만들어서 Injection 해준 것이다. 그렇기 때문에 개발자는 Interface만 선언하여 많은 기능들을 이용할 수 있다.

 

✔️@Repository 생략 가능

Spring Data JPA가 컴포넌트 스캔을 자동으로 처리하고 JPA예외를 스프링 예외로 변환하는 과정도 자동으로 처리한다.

 

 

📌 공통 인터페이스 적용

순수 JPA로 구현한 MemberJpaRepository 대신 Spring Data JPA가 제공하는 공통 인터페이스를 사용

 

Spring Data JPA 기반 MemberRepository

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

 

MemberRepository 테스트

@Test
void basicCRUD() {
    Member member1 = new Member("member1");
    Member member2 = new Member("member2");
    memberRepository.save(member1);
    memberRepository.save(member2);

    // 단건 조회 검증
    Member findMember1 = memberRepository.findById(member1.getId()).get();
    Member findMember2 = memberRepository.findById(member2.getId()).get();
    assertThat(findMember1).isEqualTo(member1);
    assertThat(findMember2).isEqualTo(member2);

    // 리스트 조회 검증
    List<Member> all = memberRepository.findAll();
    assertThat(all.size()).isEqualTo(2);

    // 카운트 검증
    long count = memberRepository.count();
    assertThat(count).isEqualTo(2);

    // 삭제 검증
    memberRepository.delete(member1);
    memberRepository.delete(member2);
    long deletedCount = memberRepository.count();
    assertThat(deletedCount).isEqualTo(0);
}

기존 순수 JPA 기반 테스트에서 사용했던 코드를 그대로 스프링 데이터 JPA 리포지토리 기반 테스트로 변경해도 동일한 방식으로 동작한다.

 

 

📌 공통 인터페이스 분석

  • JpaRepository 인터페이스: 공통 CRUD 제공
  • 제네릭은 <엔티티 타입, 식별자 타입> 설정

공통 인터페이스 구성

주의사항

  • T findOne(ID) → Optional<T> findById(ID)로 변경되었다.(과거에 사용했던 사람들만 해당)

제네릭 타입

  • T : 엔티티
  • ID : 엔티티의 식별자 타입
  • S : 엔티티와 그 자식 타입

주요 메서드

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

 


해당 글은 인프런의 [실전! 스프링 데이터 JPA] 강의를 정리한 내용입니다.

 

실전! 스프링 데이터 JPA - 인프런 | 강의

스프링 데이터 JPA는 기존의 한계를 넘어 마치 마법처럼 리포지토리에 구현 클래스 없이 인터페이스만으로 개발을 완료할 수 있습니다. 그리고 반복 개발해온 기본 CRUD 기능도 모두 제공합니다.

www.inflearn.com

댓글