[LG CNS AM Inspire Camp 1기] SpringBoot (6) - 트랜잭션이란?

정성엽·2025년 2월 7일
1

LG CNS AM Inspire 1기

목록 보기
40/53

INTRO

이번 포스팅에서는 Spring Boot에서 트랜잭션(Transaction)이 어떻게 동작하고, 어떤 방식으로 관리할 수 있는지 알아볼 예정이다 👀


1. 트랜잭션(Transaction)이란?

트랜잭션은 데이터베이스의 상태를 변화시키는 하나의 논리적인 작업 단위를 말한다.

여러 작업들을 하나의 단위로 묶어서 '모두 성공' 또는 '모두 실패'로 처리되어야 하는 경우에 사용된다.

우선 개념적인 부분을 조금 더 살펴보자

💡 트랜잭션의 4가지 특성 (ACID)

Atomicity (원자성)

  • 트랜잭션은 전체가 성공하거나 전체가 실패해야 한다
  • 부분적 성공은 허용되지 않는다

Consistency (일관성)

  • 트랜잭션 실행 전과 후의 데이터베이스는 일관된 상태를 유지해야 한다
  • 모든 제약조건을 만족해야 한다

Isolation (격리성)

  • 동시에 실행되는 트랜잭션들은 서로 영향을 미치지 않아야 한다
  • 각각의 트랜잭션은 독립적으로 실행되어야 한다

Durability (지속성)

  • 성공적으로 완료된 트랜잭션의 결과는 영구적으로 반영되어야 한다
  • 시스템 장애가 발생하더라도 데이터는 보존되어야 한다

사실 이런 개념적인 부분은 그냥 훑고 넘어가도 되지 않을까 싶다.

코드를 통해 이해해보자!

💡 트랜잭션이 필요한 경우

게시판의 상세 조회 기능을 예시로 살펴보자

상세 조회 기능

  • 조회수 증가
  • 게시글 정보 조회

우리는 위 두 작업이 하나의 트랜잭션으로 처리되지 않으면 다음과 같은 문제가 발생할 수 있다.

Sample Code

@Override
public BoardDto selectBoardDetail(int boardIdx) {
    boardMapper.updateHitCnt(boardIdx);
    int i = 10 / 0;  // 예외 발생
    return boardMapper.selectBoardDetail(boardIdx);
}

이 코드에서는 조회수 증가 후 예외가 발생해도 조회수가 증가된 상태로 남게 된다.

즉, 게시물을 정상적으로 조회하지 못했으나 조회수는 올라가는 문제가 발생한다.

이는 데이터 일관성을 해치는 심각한 문제가 될 수 있다!


2. 트랜잭션 관리 방법

SpringBoot에서는 크게 두 가지 방식으로 트랜잭션을 관리할 수 있다.

(트랜잭션을 관리하는 방법은 충분히 더 많을 수 있으나, 필자는 2가지 방법에 대해서 소개할 예정이다)

💡 선언적 트랜잭션 관리 (@Transactional)

Spring에서 가장 많이 사용되는 방식으로, @Transactional 어노테이션을 사용한다.

Sample Code - 클래스 단위

@Service
@Transactional
public class BoardServiceImpl implements BoardService {
    public BoardDto selectBoardDetail(int boardIdx) {
        boardMapper.updateHitCnt(boardIdx);
        return boardMapper.selectBoardDetail(boardIdx);
    }
}

이처럼 @Transactional 어노테이션을 클래스에 추가하여 트랜잭션 관리 대상을 지정할 수 있다.

즉, 해당 어노테이션이 붙어있다면 메서드 실행 중에 오류가 발생할 경우, 전체 실패로 처리하게 된다.

따라서, 오류가 발생하더라도 조회수만 증가하는 문제를 해결할 수 있다.

물론 @Transactional 어노테이션을 클래스 단위가 아닌 메서드 단위에 적용할 수도 있다.

Sample Code - 메서드 단위

@Service
public class BoardServiceImpl implements BoardService {
    @Transactional(rollbackFor = Exception.class)
    public BoardDto selectBoardDetail(int boardIdx) {
        boardMapper.updateHitCnt(boardIdx);
        return boardMapper.selectBoardDetail(boardIdx);
    }
}

이렇게 메서드 위에 어노테이션을 선언할 경우, selectBoardDetail 메서드에 대해서만 트랜잭션이 적용된다!

💡 AOP를 이용한 트랜잭션 관리

사실 AOP를 활용하면 더욱 유연하고 체계적인 트랜잭션 관리가 가능하다.

우선 예제 코드를 살펴보자

Sample Code

package com.Board.Board.aop;

import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.interceptor.*;

import java.util.Arrays;

@Configuration
public class TransactionAspect {
    // 트랜잭션 관리자
    @Autowired
    private PlatformTransactionManager transactionManager;

    // 트랜잭션 인터셉터 정의
    // 트랜잭션 관리자를 사용하여 트랜잭션 시작, 커밋, 롤백 등의 처리를 수행한다.
    @Bean
    TransactionInterceptor transactionAdvice(){
        TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
        transactionInterceptor.setTransactionManager(transactionManager);

        // 모든 메서드에 동일한 트랜잭션 속성을 적용할 때 사용
        MatchAlwaysTransactionAttributeSource source = new MatchAlwaysTransactionAttributeSource();

        // 트랜잭션 속성을 정의 -> 트랜잭션 이름, 롤백 규칙 적용
        RuleBasedTransactionAttribute transactionAttribute = new RuleBasedTransactionAttribute();
        // 트랜잭션 이름 정의
        transactionAttribute.setName("*");
        // 롤백 기준을 정의
   	    transactionAttribute.setRollbackRules(Arrays.asList(new RollbackRuleAttribute(Exception.class)));
        source.setTransactionAttribute(transactionAttribute);

        transactionInterceptor.setTransactionAttributeSource(source);
        return transactionInterceptor;
    };

    // AOP 포인트 컷과 어드바이저 설정이 필요하다.
    @Bean
    Advisor transactionAdviceAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* com.Board.Board.service.*Impl.*(..))");

        return new DefaultPointcutAdvisor(pointcut, transactionAdvice());
    }
}

AOP를 사용한다고 말했으나, 이전에 우리가 작성한 AOP와는 꽤 많이 달라보인다.

이 부분을 조금 더 살펴보자

트랜잭션 AOP는 우리가 일반적으로 사용하는 AOP와는 조금 다르다.

그 이유는 바로 트랜잭션은 이미 Spring이 제공하는 기능을 설정하는 것이기 때문이다.

따라서, @Aspect 어노테이션을 사용했던 일반적인 AOP와는 다르게, @Configuration 으로 트랜잭션 인터셉터만 정의를 해주면 된다.

주요 컴포넌트들을 살펴보면 다음과 같다.

트랜잭션 AOP 주요 컴포넌트
1. 트랜잭션 매니저

  • 실제 트랜잭션을 처리하는 핵심 객체
@Autowired
private PlatformTransactionManager transactionManager;

  1. 트랜잭션 인터셉터
  • 메서드 호출을 가로채서 트랜잭션 처리
TransactionInterceptor interceptor = new TransactionInterceptor();

  1. 트랜잭션 속성
  • 롤백 규칙 등 트랜잭션 동작 방식 정의
RuleBasedTransactionAttribute attribute = new RuleBasedTransactionAttribute();

이처럼 트랜잭션 인터셉터에 트랜잭션 매니저, 그리고 트랜잭션 Attribute를 설정하여 넣어주면 설정한 속성에 맞게 트랜잭션이 동작하게 된다.

다음으로 갖는 차이점은 바로 포인트 컷 정의 방법이다.

포인트 컷 정의 방법
1. 일반 AOP

@Pointcut("execution(* com.Board.Board..controller.*Controller.*(..))")
private void loggerTarget() {}

  1. 트랜잭션 AOP
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.Board.Board.service.*Impl.*(..))");

트랜잭션 AOP는 포인트 컷을 직접 생성하여 주소를 설정하고 빈으로 등록하면 된다.

그 이유는 트랜잭션 AOP가 Spring이 제공하는 TransactionInterceptor를 사용하기 때문에, 일반적인 AOP처럼 Aspect를 직접 정의하지 않고 설정만 수행하면되기 때문이다!

이처럼 AOP를 사용하게 되면 외부 라이브러리에도 트랜잭션을 적용할 수 있다는 점, 그리고 한 파일에서 트랜잭션을 관리할 수 있다는 장점이 존재한다.


OUTRO

이번 포스팅에서는 Spring Boot에서의 트랜잭션 관리 방법에 대해 자세히 알아보았다.

트랜잭션은 데이터의 일관성을 유지하는데 매우 중요한 역할을 하며, 상황에 따라 적절한 관리 방식을 선택하는 것이 중요하다.

일반적으로 @Transaction 어노테이션을 사용하여 관리하지만, AOP를 사용하여 트랜잭션을 관리하는 방법도 코드를 통해 살펴봤다.

사실 아직 필자는 예제 코드가 없다면 혼자서 트랜잭션 AOP를 설정하진 못할 것 같다.

하지만, 차이점과 필요성에 대해서는 기억해두자 👊

profile
코린이

0개의 댓글

관련 채용 정보