Server/Spring JPA

Spring - JpaRepository가 아닌 Repository를 사용해야 하는 이유!

JaeHoney 2022. 7. 18. 08:49

Spring Data JPA를 사용하면 일반적으로 Repository에서 다음의 인터페이스 중 하나를 상속하여 사용하게 된다.

  • JpaRepository
  • CrudRepository
  • Repository

해당 인터페이스들은 어떤 부분이 다르며 Repository를 사용하는 것을 권장하는 이유가 무엇인지 알아보자.

JpaRepository

아래의 그림은 Spring Data Jpa에서 제공하는 인터페이스 사이의 상속관계를 정리한 것이다.

그림에서 아래로 갈 수록 저수준 모듈이며 기능 구현이 많음을 알 수 있다.

 

CrudRepository의 경우 메서드를 정의하지 않아도 간단한 CRUD 사용이 가능하다.

public interface AccountRepository extends CrudRepository<Account, Long> {

}

가령, 위와 같이 인터페이스가 비어 있어도 아래의 메서드 호출이 가능하다.

  • <S extends T> S save(S entity);
  • Optional<T> findById(ID id);
  • Iterable<T> findAll();
  • long count();
  • void deleteById(ID id);
  • ...

 

JpaRepository에서는 간단한 CRUD 이외에도 Paging 관련 기능, Batch 사용 등 더 많은 기능을 메서드를 선언하지 않고 사용할 수 있다.

 

이는 개발할 때 편리함을 제공하기 위한 기능으로 보인다. 문제는 이게 정말 좋은 것이 맞는 지이다.

단점

우리가 정의하지 않은 기능을 인터페이스로 제공하면 문제가 발생할 수 있다.

 

예를 들어, 우리는 삭제 메서드는 제공하지 않기를 바란다. 하지만 JpaRepository는 삭제 메서드를 제공하고 필요 없는 다른 메서드들에 대한 인터페이스도 제공한다.

 

우리가 필요한 메서드만 정의한다면 시스템이 훨씬 간단명료해진다. 인터페이스로 제공하는 메서드 수가 훨씬 적어진다. 필요한 메서드는 만들면 된다.

 

추가로 정말 필요한 메서드만 선언해서 사용하면 메서드의 재사용성이 높아진다. 이는 쿼리 캐싱에 성공할 확률이 높아지는 효과를 가져올 수 있다.

Repository

Repository를 상속하게 되면 기본으로 구현되는 메서드가 전혀 없다. 직접 선언한 메서드에 대해서만 외부에 개방된다.

이는 Application 계층에서 Repository(Domain 계층)에 어떤 메시지를 보낼 수 있는 지 한눈에 파악할 수 있게 된다.

 

구현의 차이를 알아보자. JpaRepository를 상속하면 메서드를 선언하지 않아도 다양한 기능을 외부로 개방한다.

public interface AccountRepository extends JpaRepository<Account, Long> {

}

반면 Repository를 상속하면 선언한 메서드만 외부로 개방한다.

public interface AccountRepository extends Repository<Account, Long> {
    void save(Account account);
    Optional<Account> findById(Long id);
}

직접 선언한 메서드만 사용하므로 해당 Repository의 책임을 한 눈에 알아볼 수 있다.

 

그래서 JpaRepository나 CrudRepository가 아닌 Repository를 상속해서 더 깔끔한 시스템 설계를 만들 수 있다.

TIP!

테스트 코드를 작성할 때 각 테스트가 독립적으로 실행되는 환경을 구성하기 위해 다음과 같은 setup을 사용한다.

@BeforeEach
void setup() {
    this.userRepository.deleteAll();
}

해당 Repository는 JpaRepository를 상속하면 가능하지만, Repository를 상속하면 deleteAll 메서드를 선언하지 않는 이상 불가능하다.

 

프로덕트 코드에서 테스트 코드를 위해 deleteAll() 메서드를 선언하는 것은 좋지 않다. 프로덕트 코드가 테스트 코드에 의존한 것은 바람직한 설계가 아니다. 프로덕트 코드에서는 테스트 코드와 별개로 정말 필요한 메서드만 선언해야 한다.

 

이는 다음과 같이 해결할 수 있다.

@Autowired
private JdbcTemplate jdbcTemplate;

@BeforeEach
void setup() {
    jdbcTemplate.update("truncate table user");
}

Spring JDBC는 classpath에 JdbcTemplate이 존재하면 빈으로 등록된 DataSource를 이용해서 JdbcTemplate을 생성하고 빈으로 등록한다.

 

해당 JdbcTemplate을 사용하면 truncate문으로 setup 처리를 할 수 있다.

 

Spring JDBC에 대한 의존성은 걱정하지 않아도 된다! Spring JDBC 의존성은 Spring Data JPA에 이미 포함되어 있다.

 


Reference