> For the complete documentation index, see [llms.txt](https://leehyokyun.gitbook.io/openbook/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://leehyokyun.gitbook.io/openbook/2-5.-spring-core-1-module-transaction.md).

# 2-5. Spring Core #1 : Module/Transaction

Spring framework의 core - 모듈과 트랜잭션에 대하여

### 1. @EnableConfigurationProperties와 @Import의 목적 및 구조적 차이점

* @EnableConfigurationProperties: 프레임워크의 적용 범위를 확장하는 것이 목적이 아니며, Configuration Properties 설정 객체를 의도적으로 필요한 시점에 스프링에 등록하고 데이터 바인딩을 활성화하는 것이 주 목적이다. 이를 통해 application.yml 등의 외부 설정값을 POJO 객체에 자동으로 바인딩할 수 있으며, 스프링 부트 환경에서 외부 라이브러리 구조를 강제하는 현상을 회피할 수 있다. 주로 라이브러리나 커스텀 Starter를 설계할 때 핵심적으로 활용된다.
* @Import: 단순히 임의의 클래스를 스프링 빈(Bean)으로 등록하는 기능을 수행하며, 주로 Configuration 클래스나 ImportBeanDefinitionRegistrar 등의 설정 클래스를 그 대상으로 삼는다. 스프링 컨텍스트(Spring Context)에 해당 객체 또는 특정 기능 단위를 강제로 포함시키고 활성화하는 것이 핵심 목적이다.
* 자바 코드 기반 선언 구조 예시: @EnableConfigurationProperties(KafkaProperties.class) @Import(KafkaConfig.class) 위 명세와 같이 설정값을 담는 POJO 객체를 활성화할 때는 전자를, 특정 기능 단위의 설정을 컨텍스트에 강제 포함할 때는 후자를 사용한다.

### 2. 스프링 트랜잭션 전파 속성의 정의와 기본 경계 규칙

* 트랜잭션 전파(Transaction Propagation)란 이미 기존 트랜잭션이 존재하는 상태에서 또 다른 트랜잭션으로 진입할 때, 두 트랜잭션 컨텍스트 간의 충돌 규칙을 지정하고 경계를 중재하는 시스템 규칙을 의미한다.
* 개발자가 비즈니스 메서드 위에 @Transactional 애노테이션을 부여하면, 스프링은 이를 기반으로 트랜잭션의 시작, 커밋, 롤백 경계를 제어하게 된다.
  * 트랜잭션의 전파란 결국 현재 실행중인 Thread에 바인딩된 Transaction Conext를 유지하거나 새로 생성하거나의 차이이다다.
  * Transactional 어노테이션이 붙은 Service는 Controller에서 Service가 아닌 Proxy 객체를 대신 호출한다.
    * ```
      ┌────────────────────────────────────────────┐
      │ DbAdmissionAspect                          │
      │                                            │
      │ acquire()                                  │
      │     │                                      │
      │     ↓                                      │
      │   proceed()                                │
      │     │                                      │
      │     ↓                                      │
      │ ┌────────────────────────────────────────┐ │
      │ │ TransactionInterceptor                 │ │
      │ │                                        │ │
      │ │ transaction begin                      │ │
      │ │       │                                │ │
      │ │       ↓                                │ │
      │ │   proceed()                            │ │
      │ │       │                                │ │
      │ │       ↓                                │ │
      │ │ OrderService.update()                  │ │
      │ │       │                                │ │
      │ │       ↓                                │ │
      │ │ flush                                  │ │
      │ │       │                                │ │
      │ │       ↓                                │ │
      │ │ commit                                 │ │
      │ │                                        │ │
      │ └────────────────────────────────────────┘ │
      │                                            │
      │ release()                                  │
      └────────────────────────────────────────────┘
      ```
    * ```
      Thread
       └── TransactionSynchronizationManager
             └── 현재 Transaction
                   └── Connection
      ```
  * 이후 proceed()로 트랜잭션 실행 제어권을 다음 Chain으로 넘긴다. 트랜잭션 완료 후 다시 자신의 영역으로 복귀하여 commit을 하는 것.
  * Spring의 Transaction은 현재 실행중인 Thread에 바인딩된 트랜잭션 상태를 파악한다(Propagation 기준), 이때 Propagation에 따라 REQUIRED일 경우, 새로운 Transaction을 생성하거나 존재할 경우 기존 트랜잭션 참여하는 방식이다.

### 3. 동일 클래스 내부 호출(Self-Invocation) 시 전파 속성 적용 배제 현상

* 코드 구동 예시: @Transactional public void a() { b(); }
* 위 코드 구조와 같이 a() 메서드 내부에서 동일 클래스 안에 존재하는 b() 메서드를 직접 호출하는 경우, b() 메서드 상단에 @Transactional 애노테이션과 특정 전파 속성이 명시되어 있더라도 해당 전파 속성은 실제 런타임에서 절대 적용되지 않는다.
* 전파 속성이 적용되지 않는 이유는 b() 메서드가 @Transactional의 적용 대상일지라도, 동일한 빈 객체(클래스) 내부에서의 직접적인 자바 메서드 호출은 스프링의 AOP 프록시 메커니즘을 거치지 않고 타깃 객체의 원본 메서드를 직접 실행하기 때문이다.

### 4. @EnableTransactionManagement에 의한 트랜잭션 핵심 인프라 구축 프로세스

* 애플리케이션 구동 시 @EnableTransactionManagement 애노테이션이 선언되어 있으면, 클래스 로더 동작 이후 스프링 컨텍스트가 실행되고 Configuration 파싱이 진행되는 시점에 트랜잭션 관리 인프라 빈들이 등록된다.
* 인프라 구축을 위해 내부적으로 @Import(TransactionManagementConfigurationSelector.class) 과정이 실행되며, 이를 통해 트랜잭션 설정 클래스들이 컨텍스트에 포함된다.
* 이 파싱 및 등록 시점에 스프링 트랜잭션 처리를 위한 실질적인 핵심 AOP 인프라 구조가 아래와 같은 주요 빈 객체 생성을 통해 완성된다.
  * TransactionAttributeSource: 애노테이션 기반 트랜잭션 속성을 읽어오는 소스 역할을 하며, 구체적으로 AnnotationTransactionAttributeSource 객체가 빈으로 등록된다.
  * TransactionInterceptor: 트랜잭션의 시작과 종료, 프록시 체인을 제어하는 실질적인 Advisor의 Advice 엔진 역할을 수행한다.
  * BeanFactoryTransactionAttributeSourceAdvisor: 등록된 TransactionAttributeSource(포인트컷 감지 소스)와 TransactionInterceptor(Advice)를 상호 유기적으로 연결하여 스프링 AOP가 실질적으로 동작할 수 있도록 중재하는 핵심 Advisor 빈 객체다.

### 5. 빈 수명 주기와 AutoProxyCreator에 의한 프록시 객체 생성 매커니즘

* 인프라 빈 등록 완료 후, 스프링 컨텍스트는 애플리케이션의 일반 비즈니스 빈 객체들을 순차적으로 생성한다.
* 빈 객체가 생성된 직후, 컨텍스트 내부의 InfrastructureAdvisorAutoProxyCreator가 동작하여 해당 빈이 AOP 적용 대상인지를 전수 조사한다.
* 이때 AnnotationTransactionAttributeSource를 통해 대상 서비스 클래스나 메서드에 @Transactional 애노테이션이 부착되어 있는지를 감지한다.
* 감지 결과 트랜잭션 AOP 적용 대상이 맞다고 판단되면, 원본 객체 대신 이를 래핑한 프록시(Proxy) 객체를 동적으로 생성하여 스프링 컨텍스트에 최종 등록한다. 이 단계에서 실질적인 트랜잭션 Advice 가동 엔진이 물리적으로 완성된다.

### 6. 런타임 메서드 호출 시 Interceptor Chain 동작 프로세스

* 클라이언트나 타 빈에 의해 @Transactional이 마킹된 메서드가 호출되면, 컨텍스트에 등록되어 있던 프록시 객체가 전면에 개입하여 요청을 접수한다.
* 프록시 객체는 내부에 구성된 Interceptor Chain을 순차적으로 실행하며, 이 과정에서 트랜잭션 전담 인터셉터인 TransactionInterceptor가 본격적으로 동작을 개시한다.
* TransactionInterceptor는 실제 데이터베이스 대상의 트랜잭션을 동적으로 시작(Begin)한 뒤 물리적인 타깃 오브젝트(Target Object)의 비즈니스 로직으로 연산을 위임한다.
* 타깃 메서드의 실행이 예외 없이 정상 종료되면 TransactionInterceptor가 커밋(Commit)을 수행하고, 실행 중 런타임 예외가 발생하면 롤백(Rollback)을 수행하여 최종 트랜잭션 처리를 완수한다.

### 7. @Transactional은 Spring의 트랜잭션 정책/설계에 대한 반영본

#### Spring의 transaction은 Interceptor 및 이에 내재된 정책의 조합으로 이루어진다.

* Transaction이 발생하였을때, Spring은 Java의 Exception에 관심이 없다. 다만, Spring의 트랜잭션 정책/설계를 TransactionInterceptor가 감지하여 커밋할지, 롤백할지 판단하는 것 뿐이다.
* 보통 UnCheckedException, RuntimeException은 호출자가 예상할 수 없는 오류라 판단하여 rollback을 진행하며, 그 이외의 CheckedException의 경우 호출자의 판단 및 재시도 등의 부가 정책을 진행하도록 한다.
* 다만 실무에서는 CheckedException 및 UnCheckedException에 따른 정책을 나누기보다, RuntimeException이나 CustomizedException 모두 롤백을 진행하여 일관된 수행, 간편함을 취하는 것이 대부분이다.
