2022. 8. 23. 17:51ㆍLanguage`/JPA
일반적으로 Spring Data JPA를 활용하면 인터페이스만 정의하고 그에 대한 구현체는 정의하지 않는다
왜냐하면 Spring의 ProxyFactory에 의해서 정의한 인터페이스에 대한 "프록시"를 생성하고 이 프록시 내부적으로 실제 JpaRepository의 구현체인 "SimpleJpaRepository"를 호출하도록 설계되었기 때문이다
private final MemberRepository memberRepository;
@Test
@DisplayName("Spring Data JPA 인터페이스 프록시 확인")
void proxyTest() {
System.out.println(memberRepository.getClass());
assertThat(AopUtils.isAopProxy(memberRepository)).isTrue();
assertThat(AopUtils.isJdkDynamicProxy(memberRepository)).isTrue();
assertThat(AopUtils.isCglibProxy(memberRepository)).isFalse();
}
인터페이스이므로 ProxyFactory에서 프록시를 생성할 때 "JDK 동적 프록시 기술"에 의해서 Repository Interface에 대한 프록시를 생성함을 확인할 수 있다
<구체 클래스만 존재한다면 ProxyFactory에서는 "CGLIB"에 의해서 프록시를 생성한다>
하지만 이렇게 인터페이스만 정의하는 방법도 한계가 있다. 분명히 개발을 할 때 메소드를 직접 구현할 일이 생기기 때문이다
이를 위해서 Spring Data JPA에서는 필요한 메소드만 구현할 수 있도록 "사용자 정의 Repository"라는 개념을 도입하였다
사용자 정의 Repository
1. 직접 구현할 메소드를 위한 "사용자 정의 인터페이스" 작성
일단 사용자 정의 Repository를 위해서는 사용자 정의 인터페이스를 먼저 설계해야 한다
public interface MemberRepositoryCustom {
List<Member> findByFetchJoinTeam();
List<Member> findByAge();
}
2. 사용자 정의 인터페이스를 구현한 클래스 작성
과정 (1)에서 사용자 정의 인터페이스를 작성했으면 이제 해당 인터페이스를 구현하는 클래스를 작성해야 한다
여기서 클래스 네이밍 규칙이 정해져 있다
default = <사용자 정의 인터페이스 이름> + Impl
이렇게 사용자 정의 인터페이스 이름 뒤에다가 "Impl"을 붙이는 것이 default 속성으로 지정되어 있다
만약 이 설정을 변경하고 싶다면 위의 사진에서 밑줄친 부분을 애노테이션상에서 재정의하면 된다
@RequiredArgsConstructor
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom {
private final EntityManager em;
@Override
public List<Member> findByFetchJoinTeam() {
return em.createQuery(
"SELECT m FROM Member m JOIN FETCH m.team",
Member.class
).getResultList();
}
@Override
public List<Member> findByAge(int age) {
return em.createQuery(
"SELECT m FROM Member m JOIN FETCH m.team WHERE m.age = :age",
Member.class
).setParameter("age", age)
.getResultList();
}
}
3. 레포지토리 인터페이스에 사용자 정의 인터페이스 상속
이렇게 사용자 정의 인터페이스에 대한 클래스까지 구현을 완료했다면 "Origin Repository Interface"에 사용자 정의 인터페이스를 상속하면 된다
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
}
// 사용
memberRepository.findByFetchJoinTeam();
memberRepository.findByAge(30);
※ 사용자 정의 인터페이스에 대한 클래스 네이밍 규칙
위에서 설명한대로 @EnableJpaRepositories의 "repositoryImplementationPostfix"를 재정의하면 재정의한대로 네이밍 규칙을 따르면 된다
(1) Default (Impl)
@SpringBootApplication
public class QueryPracticeApplication {
public static void main(String[] args) {
SpringApplication.run(QueryPracticeApplication.class, args);
}
}
@RequiredArgsConstructor
public class MemberRepositoryCustomHandler implements MemberRepositoryCustom {
...
...
}
@EnableJpaRepositories에 대한 어떠한 재정의없이 "사용자 정의 구현체"의 Postfix를 Handler로 만든 모습이다
여기서 ApplicationContext가 제대로 loading되는지 간단한 테스트를 통해서 확인해보자
ApplicationContext를 Load하는데 실패했다는 로그가 발생하였다
(2) Handler
그러면 이제 @EnableJpaRepositories의 repositoryImplementationPostfix의 값을 "Handler"로 변경하고 다시 실행해보자
@SpringBootApplication
@EnableJpaRepositories(repositoryImplementationPostfix = "Handler")
public class QueryPracticeApplication {
public static void main(String[] args) {
SpringApplication.run(QueryPracticeApplication.class, args);
}
}
@RequiredArgsConstructor
public class MemberRepositoryCustomHandler implements MemberRepositoryCustom {
...
...
}
ApplicationContext가 정상적으로 load가 되었고 그에 따라 테스트도 통과했다는 것을 확인할 수 있다
(3) MyRepo
그러면 이제 Handler가 아닌 "MyRepo"를 Postfix로 설정해보자
@SpringBootApplication
@EnableJpaRepositories(repositoryImplementationPostfix = "MyRepo")
public class QueryPracticeApplication {
public static void main(String[] args) {
SpringApplication.run(QueryPracticeApplication.class, args);
}
}
@RequiredArgsConstructor
public class MemberRepositoryCustomMyRepo implements MemberRepositoryCustom {
...
...
}
이것 또한 repositoryImplementationPostfix의 재정의를 통해서 "MyRepo"로 설정하였기 때문에 ApplicationContext가 정상적으로 load됨을 확인할 수 있다
@EnableJpaRepositories의 repositoryImplementationPostfix의 value를 설정함으로써 사용자 정의 클래스의 Postfix Naming Rule을 변경할 수 있다
이렇게 유연하게 변경할 수 있지만 많은 개발자들의 공통 규약이라고 할 수 있는 default(Impl)로 Postfix Naming Rule을 잡는 것이 보편적으로 낫다고 생각한다