Spring/Spring

[Spring][Data Access] 1-4. Transaction Manager: Declarative Transaction Management - Understanding the Spring Framework’s Declarative Transaction Implementation (1)

noahkim_ 2024. 8. 11. 11:12

1. Declarative Transaction Management with AOP Proxies

@Transactional

  • 해당 에노테이션을 메소드 또는 클래스에 사용하여 적용하는 방식
항목 설명
Propagation 트랜잭션이 존재할 때 기존 트랜잭션을 사용할지, 새로 생성할지 결정
Rollback Rules 특정 예외 발생 시 트랜잭션을 롤백할지 여부 지정
Read-only 트랜잭션 내 작업이 읽기 전용임을 명시 → 성능 최적화 힌트
Isolation 동시에 실행되는 트랜잭션 간에 데이터 접근 충돌을 어떻게 처리할지 결정
Timeout 트랜잭션이 제한 시간 내에 완료되지 않으면 롤백
No rollback 특정 예외 발생 시에도 롤백하지 않도록 설정

 

 

예제

더보기
@Service
public class ProductService {

    // 기본 트랜잭션 설정: REQUIRED 전파 수준, 기본 격리 수준
    @Transactional
    public void addProduct(Product product) {
        // 트랜잭션이 시작됩니다.
        saveProduct(product);
        updateInventory(product);
        // 트랜잭션이 정상적으로 완료되면 커밋됩니다.
    }

    // 트랜잭션을 새로운 트랜잭션으로 시작 (REQUIRES_NEW)
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveProduct(Product product) {
        // 새로운 트랜잭션이 시작됩니다.
        // 데이터베이스에 제품 저장
    }

    // 특정 예외 발생 시 트랜잭션 롤백 (예: OutOfStockException)
    @Transactional(rollbackFor = OutOfStockException.class)
    public void updateInventory(Product product) throws OutOfStockException {
        // 재고 업데이트
        if (product.getStock() <= 0) {
            throw new OutOfStockException("Out of stock");
        }
    }

    // 읽기 전용 트랜잭션
    @Transactional(readOnly = true)
    public Product findProductById(Long id) {
        // 데이터베이스에서 제품 조회 (읽기 전용)
        return productRepository.findById(id).orElse(null);
    }
}

 

AOP Proxy

  • @Transactional이 붙은 메서드를 호출하면, 프록시 객체를 통해 해당 메서드를 감쌈
  • 호출 전-후에 트랜잭션 관련 코드를 삽입

 

TransactionInterceptor
  • AOP의 Advice 역할 수행
  • 트랜잭션 경계 설정 및 흐름 제어

 

예제) 설정

더보기
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private CustomInterceptor customInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 인터셉터 등록
        registry.addInterceptor(customInterceptor)
                .addPathPatterns("/api/**")  // 특정 경로에만 인터셉터 적용
                .excludePathPatterns("/api/public/**");  // 특정 경로는 인터셉터에서 제외
    }
}

 

예제) 등록

더보기
@Component
public class CustomInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("Pre Handle method - Intercepting the Request");

        // true를 반환하면 컨트롤러로 요청이 전달됩니다.
        // false를 반환하면 요청이 중단됩니다.
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        // 컨트롤러에서 요청 처리 후, 뷰 렌더링 전 실행될 로직
        System.out.println("Post Handle method - Request processing is done");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // 뷰 렌더링 후에 실행될 로직
        System.out.println("After Completion method - Request is complete");
    }
}

 

AspectJ 모드

항목 설명
자기 호출 문제 프록시 기반 AOP는 자기 자신의 메서드를 호출할 경우 트랜잭션이 적용되지 않음
해결 방법
AspectJ 모드를 사용하면, 프록시가 아닌 바이트코드 조작 방식으로 동작하여 자기 호출에도 트랜잭션 적용 가능

 

예제) AspectJ 모드

더보기
@Configuration
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
public class AppConfig {
}
@Service
public class MyService {

    @Transactional
    public void outerMethod() {
        System.out.println(">>> OUTER method called");
        innerMethod(); // 자기 호출!
    }

    @Transactional
    public void innerMethod() {
        System.out.println(">>> INNER method called");
        // 실제 DB 작업 or 테스트용 예외 발생
        throw new RuntimeException("예외 발생! 롤백되나요?");
    }
}

 

출처