스프링 Data JPA : 사용자 정의 Repository 만들기

    이 글은 인프런의 영한님의 강의를 보고 정리한 글입니다. 


    사용자 정의 Repository 필요성

    Query DSL을 사용하는 사람이라면 사용자 정의 Repository를 만들어야 하는 시점이 다가온다. 왜냐하면 스프링 Data JPA에서 지원하는 JpaRepository는 인터페이스이기 때문이다. 인터페이스에서 Query DSL의 Query를 작성해서 넣을 수 없다. 따라서 스프링 Data JPA Repository가 제공하는 자동 쿼리 생성만으로 기능이 부족해지는 때가 온다. 간단한 예를 들면 동적 쿼리 같은 경우다. 

    Query DSL로 동적 쿼리를 만들어 사용하기 위해 사용자가 구현한 Repository를 구현하고, 이걸 스프링 data Jpa가 제공하는 Interface 기반 Repository에서도 사용할 수 있도록 하는 방법이 있다.

     


    사용자 정의 Repository 만드는 방법

    1. 사용자 정의 인터페이스 만들기 (아무 이름 OK, 아무것도 상속 안 받으면 됨)
    2. 사용자 정의 인터페이스에 구현할 메서드를 정의함.
    3. 1에서 구현한 인터페이스 구현. 반드시 이름은 "1번 인터페이스 이름 + Impl"로 해야함
    4. 스프링 데이터 JPA가 제공하는 인터페이스 Repository에서 1번을 상속 받는다

    사용자 정의 Repository를 만들고, 이것을 JPA repository Interface에서 사용하려면 위 단계를 따라서 작성하면 된다. 4번처럼 스프링 데이터 JPA가 제공하는 인터페이스 Repository에서 사용자가 만든 Repository 인터페이스를 상속하고, 사용자 정의 인터페이스 구현체의 이름이 Impl이면 스프링 데이터 JPA가 자동으로 인식해서 스프링 빈으로 등록해준다. 

    따라서 1~4번 과정만 해두면, 내가 스프링 빈을 등록하는 고생 없이 기존 JPA 인터페이스 Repository에서 내가 구현한 기능을 사용할 수 있게 된다. 

    클래스 다이어그램은 다음과 같이 된다. 어렵게 생각할 필요가 없다.

     

    MemberRepository 인터페이스는 JpaRepository 인터페이스를 상속받아서, JpaRepository의 부모 클래스들이 사용하는 메서드를 잘 사용한다. 이 때, JpaRepository 인터페이스의 구현체는 SimpleJpaRepository라는 구현체다. 즉, 인터페이스를 상속받아 다른 구현체의 기능을 잘 사용하는 것이다. 

    사용자 정의 리포지토리도 동일한 결로 볼 수 있다. 사용자 정의 리포지토리 인터페이스를 상속받는다. 그리고 그 사용자 정의 리포지토리 구현체가 있다. 그러면 위에서 JpaRepository의 구현체인 SimpleJpaRepository의 기능을 내려받아 사용하는 것으로 쉽게 이해할 수 있다. 

     


    사용자 정의 리포지토리 만들기 예제

    1. 사용자 정의 인터페이스 만들기 + 2. 구현할 메서드 선언

    // 1. 사용자 정의 인터페이스 작성
    public interface MemberRepositoryCustom {
    
    	//2. 사용자 정의 인터페이스에 구현할 메서드 선언
        List<Member> findMemberCustom();
    }
    • 사용자 정의 리포지토리 인터페이스를 선언하고, 인터페이스 내부에 구현할 메서드를 선언한다. 

     

    3. 사용자 정의 인터페이스 구현체 만들기

    // 3. 사용자 정의 리포지토리 구현체 만들기
    // 구현체 이름은 "사용자 정의 인터페이스 + Impl"로 해야 스프링 데이터 JPA가 인식
    @RequiredArgsConstructor
    public class MemberRepositoryCustomImpl implements  MemberRepositoryCustom{
    
        private final EntityManager em;
    
        @Override
        public List<Member> findMemberCustom() {
            return em.createQuery("select m from Member m").getResultList();
        }
    }
    • 사용자 정의 리포지토리의 구현체를 만든다.
    • 구현체의 이름은 반드시 "사용자 정의 리포지토리 + Impl"로 해야 스프링 데이터 JPA가 인식한다.
    • 메서드를 오버라이딩 해준다

     

    4. JpaRepository 인터페이스 상속받은 인터페이스에 같이 상속해주기 

    // "extends MemberRepositoryCustom" 으로 사용자 정의 리포지토리를 상속 시켜줌
    public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom{}
    • JpaRepository 인터페이스를 상속받은 인터페이스에 extends로 사용자 정의 리포지토리인 MemberRepositoryCustom을 추가시켜준다.
    • 인터페이스는 여러 번 상속이 가능함.

     


    생각해볼 것

    사용자 정의 리포지토리를 기존 Jpa Repository에 추가할 수 있다고 해서 이 기능에 목메일 필요는 없다. Query DSL이 필요한 경우는 복잡한 쿼리일 수 있다. 복잡한 쿼리라는 것은 간단한 비즈니스 로직이 아닌 화면에 매몰된 쿼리일 가능성이 있다. 

    비즈니스 로직과 화면에 매몰된 쿼리는 라이프 싸이클이 다르다. 비즈니스 로직은 거의 바뀌지 않는데, 화면에 매몰된 쿼리는 화면이 바뀌게 되면 이 쿼리 역시 바뀌어야한다. 이처럼 화면에 매몰된 쿼리가 같은 엔티티를 다룬다고 해서 같은 리포지토리에 몰려있을 경우, 특정 리포지토리의 복잡성이 기하급수적으로 상승할 수 있다.

    JPA 인터페이스 Repository의 복잡도가 올라가고 있는 경우라면, Query Dsl을 사용하는 리포지토리와 JPA 인터페이스 Repository를 분리하는 것도 고려해볼 필요가 있다. 복잡성이 낮아지고, 분류가 잘 되어 유지보수 및 코드 가독성에서 이점이 있을 수 있다. 

     


    정리

    • 동적 쿼리 작성이 필요할 경우 Query DSL이 필요하다.
    • Jpa 인터페이스 Repository에서는 Query Dsl을 사용할 수 없기 때문에 사용자 정의 리포지토리를 만들어야 Query DSL을 사용할 수 있다. 
    • 사용자 정의 리포지토리를 만들고, 그 리포지토리를 JPA 인터페이스 리포지토리에서 사용하기 위해서는 다음과 같이 하면 된다.
      1. 사용자 정의 인터페이스 만들기 (아무 이름 OK, 아무것도 상속 안 받으면 됨)
      2. 사용자 정의 인터페이스에 구현할 메서드를 정의함.
      3. 1에서 구현한 인터페이스 구현. 반드시 이름은 "1번 인터페이스 이름 + Impl"로 해야함
      4. 스프링 데이터 JPA가 제공하는 인터페이스 Repository에서 1번을 상속 받는다
    • 반드시 사용자 정의 리포지토리가 JPA 인터페이스 리포지토리에 포함될 필요는 없다. 복잡성이 증가하기 때문이다.
    • 사용자 정의 Repository의 구현체는 스프링 빈으로 등록할 필요가 없다. 스프링 데이터 JPA가 자동으로 해준다.

    댓글

    Designed by JB FACTORY