Spring

jpa를 살펴보자

정한_s 2023. 10. 9. 10:42

jpa에서 쿼리 등록이 어떻게 되고, 어떻게 관리되는 지 궁금했습니다. jpa 내부를 살펴보겠습니다.

JpaRepository가 호출될때 jdkDyamicAopProxy를 통해서 여러개의 interceptor를 체인 형태로 호출하게 됩니다. 체인 형태로 호출 할때 ReflectiveMethodInvocation을 사용합니다.

// ReflectiveMethodInvocation

	@Override
	@Nullable
	public Object proceed() throws Throwable {
		// We start with an index of -1 and increment early.
		// 사용되는 interceptor는 순차적으로 interceptorsAndDynamicMethodMatchers에 등록됩니다
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			return invokeJoinpoint();
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
			// It's an interceptor, so we just invoke it: The pointcut will have
			// been evaluated statically before this object was constructed.
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

사용되는 interceptor는 다음과 같습니다

각 interceptor의 역할은 다음과 같습니다

  1. ExposeInvocationInterceptor:
    • 이 인터셉터는 Spring AOP의 일부로써, 현재의 MethodInvocation을 스레드 로컬에 노출합니다. 이로 인해 중첩된 advice에서 현재 MethodInvocation에 액세스 할 수 있습니다. 다시 말해, 현재 진행 중인 메소드 호출을 가져올 수 있게 합니다.
  2. CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor:
    • 이 인터셉터는 Spring Data의 일부로서, CRUD 메소드 메타데이터를 채워 넣는 기능을 담당합니다. 간단히 말해, 저장소의 CRUD 메소드에 대한 메타데이터를 관리하며, 이 메타데이터는 메소드의 실행 도중 필요한 정보를 제공합니다.
  3. PersistenceExceptionTranslationInterceptor:
    • 이 인터셉터는 Spring의 예외 변환 매커니즘의 일부로써, 특정 영구 저장소 예외를 Spring의 데이터 액세스 예외 계층으로 변환합니다. JPA, JDO 및 기타 영구 저장소 프레임워크에 대한 예외를 Spring의 DataAccessException으로 변환하는 것을 목표로 합니다.
  4. TransactionInterceptor:
    • 이 인터셉터는 Spring의 프로그래밍 모델인 선언적 트랜잭션 관리를 구현하는데 중요합니다. 이는 메소드 호출에 대한 트랜잭션 경계를 정의하며, 필요에 따라 트랜잭션을 시작하거나 기존 트랜잭션에 참여하게 합니다.
  5. DefaultMethodInvokingMethodInterceptor:
    • 이 인터셉터는 Java 8의 인터페이스의 기본 메소드 호출을 지원하기 위해 사용됩니다. Spring Data 저장소에서 사용되며, 사용자가 정의한 인터페이스의 기본 메소드를 호출할 때 이를 처리합니다.
  6. QueryExecutorMethodInterceptor:
    • 이 인터셉터는 사용자 정의 구현의 메서드 호출을 가로채고, 설정된 경우 위임합니다. Spring Data 저장소 인터페이스의 쿼리 메서드 호출을 실제 데이터베이스 쿼리 실행으로 중계하는 역할을 합니다.
  7. $ImplementationMethodExecutionInterceptor:
    • 이 인터셉터는 Spring Data 저장소의 구현 메소드 실행을 담당합니다. 저장소 인터페이스에 정의된 메소드가 실제로 실행될 때 해당 구현을 호출하는 역할을 합니다.

 

그렇다면, 실제 쿼리는 어떻게 만들어 질지 궁금했습니다. Hibernate에서 실제 SQL로 변환하는 주요 클래스는 QueryTranslatorImpl 입니다. 해당 클래스에서 쿼리의 파싱, 분석, 최종적인 SQL 생성 단계를 거칩니다.

// QueryTranslatorImpl 
private void generate(AST sqlAst) throws QueryException, RecognitionException {
		if ( sql == null ) {
			final SqlGenerator gen = new SqlGenerator( factory );
			gen.statement( sqlAst );
			sql = gen.getSQL();
			if ( LOG.isDebugEnabled() ) {
				LOG.debugf( "HQL: %s", hql );
				LOG.debugf( "SQL: %s", sql );
			}
			gen.getParseErrorHandler().throwQueryException();
			if ( collectedParameterSpecifications == null ) {
				collectedParameterSpecifications = gen.getCollectedParameters();
			}
			else {
				collectedParameterSpecifications.addAll( gen.getCollectedParameters() );
			}
		}
	}

다음과 같이 gernerate 함수를 통해 실제 sql을 만들어 사용할 수있습니다.