공통 인터페이스 기능

LeeKyoungChang·2022년 4월 25일
0
post-thumbnail

실전! 스프링 데이터 JPA을 듣고 정리한 내용입니다.

 

📚 1. 순수 JPA 기반 리포지토리 만들기

순수 JPA 기반 리포지토리를 만들어보고 스프링 데이터 JPA를 적용해보자!

✏️ 기본 CRUD

  • 저장
  • 변경 → 변경감지 사용
  • 삭제
  • 전체 조회
  • 단건 조회
  • 카운트

 

💡 참고

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

 

📖 A. MemberJpaRepository - 회원

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);
    }
}
  • getSingleResult() : 단일인 경우
  • getResultList() : List형인 경우

 

(1) Create (저장)

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

 

(2) Read (조회)

//전체 조회
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);
}

 

(3) Update (변경) → 변경감지 사용

JPA는 기본적으로 엔티티 변경감지로 인해 데이터를 바꾼다!

트랜잭션 안에서 엔티티를 조회한 다음에 데이터를 변경하면, 트랜잭션 종료 시점에 변경 감지 기능이 작동해서 변경된 엔티티를 감지하고 UPDATE SQL을 실행한다.
즉, 따로 UPDATE문을 구현할 필요가 없다.

 

(4) DELETE (삭제)

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

 

(5) Count (카운트)

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

 

📖 B. TeamJpaRepository - 팀

package study.datajpa.repository;

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

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

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

 

(1) Create (저장)

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

 

(2) Read (조회)

//전체 조회
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);
}

 

(3) Update (변경) → 변경 감지 사용
A.에서 언급했듯이, Update문을 구현하지 않아도 된다!

 

(4) Delete (삭제)

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

 

(5) Count (카운트)

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

 

💡 참고

  • MemberTeamRepository 코드가 매우 유사하다는 것을 알 수 있다.
  • 이러한 중복을 스프링 데이터 JPA를 사용하면 해결할 수 있다.

 

📖 C. 순수 JPA 기반 리포지토리 테스트

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

        findMember1.setUsername("member!!!!");

        // 리스트 조회 검증
        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 deleteCount = memberJpaRepository.count();
        assertThat(deleteCount).isEqualTo(0);
    }

 

📚 2. 공통 인터페이스 설정

스프링 부트 사용시 @SpringBootApplication 위치를 지정하면, 해당 패키지와 하위 패키지를 인식한다.

✔️ 스프링 데이터 JPA가 구현 클래스 대신 생성

스크린샷 2022-04-22 오후 6 03 24 스크린샷 2022-04-22 오후 6 21 53

org.springframework.data.repository.Repository 를 구현한 클래스는 스캔 대상이다.

  • 이는 MemberRepository 인터페이스가 동작한 이유이다.

    • 개발자가 interface만 선언해주면 스프링 데이터 JPA가 자동으로 구현 클래스를 만들어준다.
  • memberRepository.getClass()class com.sun.proxy.$ProxyXXX
    스크린샷 2022-04-22 오후 6 18 55

  • 인터페이스 구현체는 스프링 데이터 jpa가 만들어서 memberRepository에 inject 해준다.

 

@Repository	//생략 가능
public interface TeamRepository extends JpaRepository<Team, Long> {
}

@Repository 애노테이션은 생략 가능하다.

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

 

📚 3. 공통 인터페이스 적용

순수 JPA로 구현한 MemberJpaRepository 대신에 스프링 데이터 JPA가 제공하는 공통 인터페이스 사용한다.

@Repository	//생략 가능
public interface TeamRepository extends JpaRepository<Team, Long> {
}

JpaRepository<(1), (2)>

  • (1) : 엔티티를 구현한 클래스
  • (2) : 엔티티에 pk로 매핑된 id type(자료형)

 

MemberRepositoryTest에 추가

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

        findMember1.setUsername("member!!!!");

        // 리스트 조회 검증
        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 deleteCount = memberRepository.count();
        assertThat(deleteCount).isEqualTo(0);
    }

MemberJpaRepositoryTest에서 사용한 basicCRUD 복사해서 memberRepositoryTest에 붙여넣는다.
MemberJpaRepositoryMemberRepository와 거의 유사하게 구현되어 있다.

 

실행 결과
스크린샷 2022-04-25 오후 4 06 35

 

📚 4. 공통 인터페이스 분석

왠만한 공통 기능은 다 제공한다.

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

 

✔️ JpaRepository 공통 기능 인터페이스

스크린샷 2022-05-02 오후 1 38 04

 

✔️ PagingAndSortingRepository
스크린샷 2022-05-02 오후 1 37 16

  • 공통 인터페이스 제공한다.
  • 페이징 작업할 때 많이 사용한다.

 

✔️ CrudRepository

스크린샷 2022-05-02 오후 1 43 35
  • Create, Read, Update, Delete관련 메서드들이 모여있다.

 

✔️ Repository

스크린샷 2022-05-02 오후 1 43 51
  • 최상위 부모 인터페이스

 

📖 A. 공통 인터페이스 구성

스크린샷 2022-04-25 오후 4 21 56

스프링 데이터는(기본적인 기능) 어디에서든지 공통으로 사용된다. (SQL, NoSQL 상관없이)

 

💡 참고
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 ) 조건을 파라미터로 제공할 수 있다.

 

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

 

✔️ 만약 공통 메서드(기능)이 아닌, 다른 특수의 메서드(기능)을 사용하고 싶을 때

findByUsername메서드를 구현하고 싶다.

  • 약간 특성을 바꾸어서 구현해보려고 한다.
스크린샷 2022-05-02 오후 1 56 25
  • findByUsername메서드를 구현하고 싶다.

 

스크린샷 2022-05-02 오후 1 55 43
  • MemberRepositoryImpl에서 MemberRepository를 상속 후
  • 오버라이딩을 하려고 했는데, 상속 받은 클래스으로부터 구현해야할 메서드가 엄청나게 많다.

이걸 해결하는 방법은 나중에 배운다.
이와 유사하게 구현하는 방법이 있는데 그것을 커스텀 구현이라고 한다.

 

✔️ 그런데?

스크린샷 2022-05-02 오후 1 56 25

그런데, 이와 같이 interface에 메서드 이름만 새로 추가하고 (상속받은 클래스에서)본체를 직접 구현하지 않고 메소드를 바로 사용해도 동작을 한다!

➡ 이것은 무슨 기능일까? 쿼리메소드 기능이다.

 

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글