Programming/Spring Boot

repository, repositoryCustom, repositoryImpl 구조를 사용하자

Jan92 2023. 11. 4. 11:04

jpa 공식 같은 repository, repositoryCustom, repositoryImpl 구조

repository, repositoryCustom, repositoryImpl 구조

 

해당 포스팅에서는 하나의 Repository를 통해 JpaRepository의 기능과 Querydsl의 기능을 사용하는 CustomRepository를 모두 사용할 수 있는, 일종의 공식처럼 쓰이는 'repository, repositoryCustom, repositoryImpl 구조'에 대해서 살펴보겠습니다.

 


repository 상속 구조

repository 상속 구조

해당 구조를 코드로 보면 아래와 같은데요.

크게 JpaRepository를 extends 하는 Repository 인터페이스에 CustomRepository 인터페이스를 추가로 extends 하는데, CustomRepository 인터페이스는 Querydsl의 기능을 사용하는 RepositoryImpl 클래스를 구현체로 가지고 있습니다.

 

즉, Repository 하나로 기본적인 JpaRepository의 기능과 Querydsl을 통한 디테일한 기능을 모두 사용할 수 있기 때문에 Repository를 사용하는 Service 단에서의 코드가 깔끔해진다는 장점을 가지게 됩니다.

 

 

//Repository
public interface ExampleRepository extends JpaRepository<Example, Long>, ExampleRepositoryCustom {
    ...
}

//RepositoryCustom
public interface ExampleRepositoryCustom {
    ...
}

//RepositoryImpl
public class ExampleRepositoryImpl implements ExampleRepositoryCustom {
    ...
}

 

***

해당 구조를 사용할 때 주의할 점으로는 CustomRepository 인터페이스의 구현 클래스의 네이밍 규칙이 있는데요.

'~RepositoryImpl'이라는 규칙을 지키지 않으면 아래와 같은 오류를 만나게 될 수 있습니다.

 

UnsatisfiedDependencyException: Error creating bean with name 'productServiceImpl' defined in file

 

 

repositoryImplementationPostfix default "Impl"

다음은 @EnableJpaRepositories의 코드 중 일부로, repositoryImplementationPostfix의 default 값이 'Impl'로 설정되어 있는 것을 확인할 수 있습니다.

(해당 값을 변경하여 postfix 값을 Impl이 아닌 다른 명칭으로도 사용할 수 있지만, 변경하여 사용할 만큼의 필요성은 잘 모르겠습니다.)

 


@DataJpaTest를 통한 테스트 코드

public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
    Optional<User> findById(Long id);
}

(UserRepository interface)

 

public interface UserRepositoryCustom {
    List<User> getUsers();
}

(UserRepositoryCustom interface)

 

@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepositoryCustom {

    private final JPAQueryFactory queryFactory;

    @Override
    public List<User> getUsers() {
        return queryFactory
                .selectFrom(user)
                .fetch();
    }
}

(UserRepositoryImpl class)

 

테스트에 사용될 repository, repositoryCustom, repositoryImpl 코드입니다.

코드 내용은 중요하지 않고 테스트에서 userRepository 하나로 impl 클래스에 구현된 기능까지 호출할 수 있다는 점이 중요한 부분입니다.

 

 

@Slf4j
@DataJpaTest
@Import(TestConfig.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class CustomRepositoryTests {

    @Autowired
    private UserRepository userRepository;

    @Test
    void jpaRepositoryFunctionTest() {
        User user = userRepository.findById(1L)
                .orElseThrow(() -> new RuntimeException("User not found!"));
    }

    @Test
    void querydslFunctionTest() {
        List<User> users = userRepository.getUsers();
    }
}

(CustomRepositoryTests class)

 

@TestConfiguration
public class TestConfig {

    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}

(TestConfig class)

 

@DataJpaTest 테스트에서는 JPAQueryFactory Bean을 스캔하지 않기 때문에 다음과 같이 TestConfig 클래스를 통해 JPAQueryFactory를 빈으로 등록하여 @Import 해주는 과정이 필요합니다.

 

추가로 @AutoConfigureTestDatabase 어노테이션의 Replace 옵션 값을 NONE으로 지정하여 자동 구성된 Test DB가 DataSource Bean을 대체하지 않도록 설정하였습니다.

 

 

Test Results

 

 

< 참고 자료 >

https://velog.io/@wwe221/Spring-Data-Jpa-Custom-Repository-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0
https://umanking.github.io/2019/04/12/jpa-custom-repository/