제육's 휘발성 코딩
article thumbnail
반응형

공통 인터페이스 기능

  • 순수 JPA 기반 리포지토리
  • 스프링 데이터 JPA 공통 인터페이스 소개 및 활용

 

순수 JPA 기반 리포지토리

JPA에서 수정은 변경감지 기능을 사용하면 된다. 트랜잭션 안에서 엔티티를 조회한 다음에 데이터를 변경하면, 트랜잭션 종료 시점에 변경감지 기능이 작동해서 변경사항이 있으면 UPDATE SQL을 실행한다.

package study.datajpa.repository;

import org.springframework.stereotype.Repository;
import study.datajpa.entity.Member;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import java.util.Optional;

@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);
    }
}
  • 기본적인 CRUD를 구현하는 레포지토리를 구현해보자. 순수 JPA 기능은 find(), remove(), createQuery(), persist 등이 있으며 이 기능을 사용하여 CRUD를 구현한다.
package study.datajpa.repository;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
import study.datajpa.entity.Member;

import java.util.List;

import static org.assertj.core.api.Assertions.*;

@SpringBootTest
@Transactional
class MemberJpaRepositoryTest {

    private final MemberJpaRepository memberJpaRepository;

    @Autowired
    public MemberJpaRepositoryTest(MemberJpaRepository memberJpaRepository) {
        this.memberJpaRepository = memberJpaRepository;
    }

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

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

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

        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();

        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);
    }
}
  • 테스트 코드는 다음과 같이 작성할 수 있다. @Transactional 은 트랜잭션 종료 시 롤백한다.

 

공통 인터페이스

@Configuration
@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository")
public class AppConfig {}
  • 스프링에선 다음과 같이 Config 설정이 필요하지만, 스프링 부트에선 생략 가능하다. 위치가 달라진다면, @EnableJpaRepositories 설정이 필요하다.

image

  • 인터페이스를 주입 받았음에도 구현체처럼 사용할 수 있는 원리는 다음과 같다.
    • MemberRepository(Data JPA 인터페이스)가 JpaRepository를 상속 받으면 스프링에서 class.com.sun.proxy.$ProxyXXX 와 같이 구현 프록시 객체를 생성해서 주입해준다.
    • 상속 받는 과정에서 컴포넌트 스캔의 대상으로도 등록해주기 때문에 @Repository를 생략해도 된다.

image

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

JpaRepository는 대부분의 공통 메서드를 제공한다.

공통 기능이 아닌 별도의 커스텀 기능을 만들 때 쿼리 메서드 기능을 사용한다. 해당 내용은 다음 포스팅에서 정리해보자.


본 포스팅은 인프런 - 김영한님의 '실전! 스프링 데이터 JPA' 편을 정리한 내용입니다.

반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^