본문 바로가기
Dev/Spring Data JPA

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

by dev_jsk 2021. 11. 29.
728x90
반응형

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

순수 JPA를 기반으로 레포지토리를 만들어 추후 스프링 데이터 JPA에서는 어떻게 간단해지는지 알아보자

Member 레포지토리

package study.datajpa.repository;

@Repository
public class MemberJpaRepository {
    
    @PersistenceContext
    private EntityManager em;

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

    public void delete(Member member) {
        em.remove(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();
    }

    public Member find(Long id) {
        return em.find(Member.class, id);
    }
}
  • JPQL은 객체를 대상으로 쿼리하는 것으로 SQL과 비슷한 모습과 문법을 갖고 있다.
  • Optional<T> : NULL이 올 수 있는 값을 감싸는 Wrapper 클래스로 NullPointerException이 발생하지 않도록 도와주며 다양한 함수를 제공한다. (참고 : https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)
  • 기본 CRUD를 구성하며 수정은 변경감지 기능을 사용하기 때문에 별도 구성이 필요없다.

Team 레포지토리

package study.datajpa.repository;

@Repository
public class TeamJpaRepository {
    
    @PersistenceContext
    private EntityManager em;

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

    public void delete(Team team) {
        em.remove(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();
    }

}

동작확인

package study.datajpa.repository;

@SpringBootTest
@Transactional
@Rollback(value = false)
public class MemberJpaRepositoryTest {
    
    @Autowired
    MemberJpaRepository memberJpaRepository;
    @PersistenceContext
    EntityManager em;

    @Test
    public void testMember() {
        Member member = new Member("memberA");
        Member savedMember = memberJpaRepository.save(member);

        Member findMember = memberJpaRepository.find(savedMember.getId());

        Assertions.assertThat(findMember.getId()).isEqualTo(member.getId());
        Assertions.assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
        Assertions.assertThat(findMember).isEqualTo(member);
    }

    @Test
    public 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();
        Assertions.assertThat(findMember1).isEqualTo(member1);
        Assertions.assertThat(findMember2).isEqualTo(member2);
        
        // 리스트 검증
        List<Member> all = memberJpaRepository.findAll();
        Assertions.assertThat(all.size()).isEqualTo(2);
        
        // 카운트 검증
        long count = memberJpaRepository.count();
        Assertions.assertThat(count).isEqualTo(2);
        
        // 삭제 검증
        memberJpaRepository.delete(member1);
        memberJpaRepository.delete(member2);

        // 삭제 후 카운트 검증
        long deletedCount = memberJpaRepository.count();
        Assertions.assertThat(deletedCount).isEqualTo(0);
        
    }
    
}
  • @Rollback(value = "false")를 사용하여 테스트 후에도 롤백하지 않도록 지정

공통 인터페이스 설정

Java Config 설정을 통해 공통 인터페이스 설정이 필요하지만 스프링 부트 사용시 생략이 가능하다.

@Configuration
@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository")
public class AppConfig {}
  • @SpringBootApplication이 사용된 클래스 위치부터 하위 경로를 다 스프링 데이터 JPA가 인식한다.
  • 패키지가 다를 경우 @EnableJpaRepositories 어노테이션을 사용하여 경로를 잡아줘야 한다.

스프링 데이터 JPA 동작

  • 구현 클래스에서 JpaRepository 또는 CrudRepository를 상속받으면 스프링 데이터 JPA가 자동으로 구현체를 생성하여 주입해준다.
  • 스프링 데이터 JPA가 자바의 기본 프록시 기술을 사용하여 프록시 객체를 주입해준다.
  • @Repository 어노테이션을 생략 할 수 있다.
    - 컴포넌트 스캔을 스프링 데이터 JPA가 자동으로 처리
    - JPA 예외를 스프링 예외로 변환하는 과정도 자동으로 처리

공통 인터페이스 적용

구현할 레포지토리 인터페이스에 extends JpaRepository<Entity Type, Entity key Type> 으로 사용한다.

※ Entity key Type : 엔티티 내 @Id 어노테이션이 선언되어 있는 필드의 타입

Member 레포지토리

package study.datajpa.repository;

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

Member 레포지토리 테스트

package study.datajpa.repository;

@SpringBootTest
@Transactional
@Rollback(value = false)
public class MemberRepositoryTest {
    
    @Autowired
    MemberRepository memberRepository;
    @Autowired
    TeamRepository teamRepository;
    @PersistenceContext
    EntityManager em;

    @Test
    public void testMember() {
        Member member = new Member("memberA");
        Member savedMember = memberRepository.save(member);

        Member findMember = memberRepository.findById(savedMember.getId()).get();

        Assertions.assertThat(findMember.getId()).isEqualTo(member.getId());
        Assertions.assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
        Assertions.assertThat(findMember).isEqualTo(member);

    }

    @Test
    public 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();
        Assertions.assertThat(findMember1).isEqualTo(member1);
        Assertions.assertThat(findMember2).isEqualTo(member2);

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

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

        // 삭제 검증
        memberRepository.delete(member1);
        memberRepository.delete(member2);

        // 삭제 후 카운트 검증
        long deletedCount = memberRepository.count();
        Assertions.assertThat(deletedCount).isEqualTo(0);

    }

}
  • 순수 JPA 레포지토리 테스트를 스프링 데이터 JPA를 적용한 후에 테스트해도 동일하게 동작한다.

Team 레포지토리

package study.datajpa.repository;

public interface TeamRepository extends JpaRepository<Team, Long> {}

공통 인터페이스 분석

공통 인터페이스

  • JpaRepository 인터페이스 : 공통 CRUD 기능 제공
public interface JpaRepository<T, ID extends Serializable>
 extends PagingAndSortingRepository<T, ID>
{
 ...
}

공통 인터페이스 구성

  • T findOne(ID) -> Optional<T> findById(ID)
  • T : 엔티티, ID : 엔티티의 식별자 타입, S : 엔티티와 그 자식 타입

공통 인터페이스 주요 함수

  • save(S) : 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합한다.
  • delete(T) : 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove() 호출
  • findById(ID) : 엔티티 하나를 조회한다. 내부에서 EntityManager.find() 호출
  • getOne(ID) : 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference() 호출
    EntityManager.getReference() : 실제 엔티티가 아닌 프록시를 이용하여 가짜 호출, 이후 프록시 초기화 시 실제 엔티티 호출
  • findAll(...) : 모든 엔티티를 조회한다. 정렬이나 페이징조건을 파라미터로 제공할 수 있다.
728x90
반응형

댓글