개요

JPA를 처음 배울 때, JpaRepository라는 인터페이스를 상속받기만 해주면 기본적으로 제공하는 save나 findById같은 메소드들이 신기했던 경험이 있습니다. findById같은 경우는 기본적으로 제공한다고 치고.. 그럼 Query 어노테이션이 붙은 메소드들은 과연 어떻게 처리될까요?

지금부터는 정확한 내용이 아닐수도 있습니다 😅 중간중간 생략된 메소드들이 있을 수 있습니다. 또한 AOP 관련된 로직들은 모두 뛰어넘겠습니다.

 

실험 메소드

테스트 대상 메소드

해당 메소드를 실행시켰을 때, 어떠한 과정을 거쳐서 실행되는지 한번 쭉 살펴보겠습니다.

 

CrudMethodMetadataPostProcessor

  • 기존에 저장되어 있던 구현 목록에 존재하는 메소드를 호출하는지 확인하고, 존재하지 않는다면 따로 처리합니다.
  • implementations 내부에는 아래와 같은 메소드들이 저장되어 있습니다.

  • 현재 예제에서는, 사전에 등록된 메소드들이 아닌 저희가 직접 Query로 정의하였으므로 조건식에서 걸러지게 됩니다.

 

TransactionInterceptor

  • 트랜잭션을 처리해주는 인터셉터입니다.
  • createTransactionIfNecessary 메서드에서 트랜잭션이 요구된다면 생성시켜 줍니다.
  • completeTransactionAfterThrowing에서는 설정에 따라 예외가 발생한 경우 트랜잭션을 롤백하거나 커밋시켜 줍니다.
  • cleanupTransactionInfo는 ThreadLocal의 정보를 리셋시켜 줍니다. 실제 동작은 oldTransaction으로 변경시켜 주는데, 현재 트랜잭션은 종료시키고 이전 트랜잭션으로 바꿔주는것 같다고 느껴졌습니다. 참고로 ThreadLocal을 통해 트랜잭션이 관리됩니다.
  • commitTransactionAfterReturning 메소드에서 트랜잭션이 종료된 이후 커밋을 담당합니다.
  • 참고로 가운데에 있는 Vavr 관련 처리는 Vavr은 조금 다르게 처리해줘야 해서 따로 처리해준 것 같습니다.

트랜잭션을 걸어주고(실제로는 따로 트랜잭션을 걸지 않았으므로 안걸림) invocation.proceedWithInvocation() 메서드를 타고 넘어가겠습니다.

 

DefaultMethodinvokingMethodInterceptor

  • 메소드가 Default Method인지 체크합니다.
    • 클래스가 인터페이스이면서, 구현 코드가 있는 Static이 아닌 메소드를 말합니다.
  • 이번 예제는 Default Method는 아니므로.. 내부 코드로 넘어갑시다.

 

QueryExecutorMethodInterceptor

어..뭐야 언제..

  • 내부적으로 이미 쿼리 데이터와 파라메터를 객체로 만들어서(SimpleJpaQuery) 가지고 있습니다.
    • 쿼리 뿐 아니라 바인딩된 파라메터까지 가지고 있습니다.
    • 이러한 정보들을 바탕으로 다음 로직을 수행합니다.

  • 리턴 타입에 해당하는 QueryExecutionConverters.ExecutionAdapter을 불러옵니다. 따로 등록해둔 Adapter가 없으므로 null을 가져오게 됩니다.
  • 다음으로 doInvoke() 메서드로 실제 메서드를 실행시켜 줍니다.

  • hasQueryFor() 메서드로 실행시킬 수 있는 쿼리인지를 확인합니다.
  • method와 query를 가지고 invoker를 불러오고, 이를 invocationMetadataCache에 넣은 다음 호출합니다.

 

RepositoryMethodInvoker

찾았다!!%!@%!@

  • 여정의 마무리입니다. invoke 메서드를 호출하여 실제 결괏값을 가져옵니다. 드디어 어디서 호출되는지 진짜로 볼수 있겠군요ㅠㅠㅠㅠㅠㅠㅠㅠ

 

AbstractJpaQuery

  • 가져온 파라메터를 기반으로 JpaParametersParameterAccessor를 얻어옵니다.
  • Accessor에는 다음과 같은 정보들이 들어있습니다.
    • 파라메터의 정보, 대입되는 값 등등

  • Accessor 정보와 AbstractJpaQuery를 넘겨줘서 진짜진짜 메소드를 실행해 봅시다.
  • AbstractJpaQuery에는 실제 실행해야 하는 쿼리가 들어있습니다.

 

JpaQueryExecution

  • 진짜진짜 찐막입니다.
  • doExecute() 메서드로 가져온 JPQL 쿼리를 실행시킵니다.
  • 아래 블록에서는 형변환이 가능한 경우, 결과값을 형변환하여 반환시켜 줍니다.
  • 값을 한개만 반환하므로, SingleEntityExecution의 doExecute 메서드를 호출합니다.

  • JPQL 쿼리를 실행시켜서 결괏값을 얻어옵니다. 이 아래는 쿼리를 실행하는 부분이므로 다루지 않겠습니다.

간단하게만 파보자면 AbstractProducedQuery의 메서드를 호출합니다.(getSingleResult) 이 때, 궁금한 부분중 하나가 JPQL을 실제 SQL문으로 언제 바꿔주냐였습니다. 해당 처리는 HQLQueryPlan에서 처리합니다.

 

compile() 메서드를 실행시키면, QueryTranslatorImpl의 doCompile() 메서드로 넘어갑니다. 해당 메서드에서 파싱을 진행하는데, 최종적으로 파싱이 되는 부분은 다음과 같습니다.

해당 메서드를 실행하면 JPQL이 아닌 실제로 실행하는 SQL문 형태로 변환됩니다.

 

참고

https://stackoverflow.com/questions/38509882/how-are-spring-data-repositories-actually-implemented/38511337#38511337

 

+ Recent posts