HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
✍🏻
Learnary (learn - diary)
/
🧯
[AOP] - transaction Exception
🧯

[AOP] - transaction Exception

progress
Done
Tags
Spring
🏗️ Build Up트랜잭션이란특징트랜잭션은 Exception 타입에 따라 처리하는 방식이 다르다.💨What [스프링에서 제공하는 트랜잭션]⚠️스프링 트랜잭션 사용 주의사항Self - Invocation 문제@Transactional 우선 순위Transactional (readOnly =ture) 가 적용된 메소드에서📌 REFER
 

🏗️ Build Up

트랜잭션이란

  • 일련의 작업을 의미한다.

특징

  • 원자성
    • 트랜잭션의 모두 하나의 단위로 성공과 실패 두가지로 처리되어야 한다.
  • 일관성
    • 트랜잭션 수행 후 데이터는 일관성을 유지해야 한다
  • 독립성
    • 다른 트랜잭션이 끼어들수 없다.
  • 영속성
    • 트랜잭션이 수행된 후 데이터는 영속적이여야 한다.
 
notion image

트랜잭션은 Exception 타입에 따라 처리하는 방식이 다르다.

 
예외의 종류
notion image
  • 예외는 크게 CheckdException과 UncheckedException으로 나눌 수 있다.
    • 이를 구분 짓는 특징은 RuntimeException의 상속 여부로 나뉜다.
      • notion image

💨What [스프링에서 제공하는 트랜잭션]

🌕
기본적으로 UnCheckedException은 롤백이 가능하고 Checked Exception은 롤백 되지 않는다.
 
하지만 @Transactional 에서 rollbackFor 옵션을 이용하고 rollback이 되는 클래스를 지정 가능하다.
그리고 unCheckedException에서 noRollbackFor를 사용하여 해당 예외를 추가하면 롤백이 되지 않도록 할 수 도 있다.
 

⚠️스프링 트랜잭션 사용 주의사항

  • 트랜잭션은 AOP 기반으로 구성되어 있다.
  • 실제 핵심 로직을 수행하면서 발생하는 횡단 관심사를 한데 모아 처리하는 것을 aop 라 한다.
  • 즉, @Transactional 을 통해 프록시 객체를 생성함으로써 트랜잭션을 수행할 때마다, 커밋 또는 롤백 후 트랜잭션을 닫는 등의 부수적인 작업을 프록시 객체에게 위임할 수 있게 된다.
  • 핵심 기능은 메소드가 Invocation 될 때, 이 메소드를 가로채어 부가 기능들을 추가할 수 있도록 지원하는 것이다.
 

Self - Invocation 문제

Q. 정삭적으로 트랜잭션이 적용되어 롤백이 될까?
@Service @RequiredArgsConstructor public class JpaRunner { private final PostRepository postRepository; public void run() { for(int i=0; i<5; i++) { savePost(i); } } @Transactional public void savePost(int i) { // 현재 적용된 트랜잭션 이름을 확인 System.out.println("CurrentTransactionName:"+TransactionSynchronizationManager.getCurrentTransactionName()); postRepository.save(new Post(i)); if(i == 3) throw new RuntimeException(); // 예외 발생 } }
정답은 모두 롤백되지 않는다.
  • 그 이유는 스프링 AOP는 프록시를 기반으로 동작하기 때문이다.
  • AOP의 단점 중 하나인 프록시 내부를 호출 할 때는 부가적인 서비스가 적용되지 않는다.
  • 호출하려는 Target을 감싸고 있는 프록시를 통해야만 부가적인 기능이 적용이 되는데 프록시 내부에서 내부를 호출 할 때는 감싸고 있는 proxy를 호출하지 않고 실제 구현체 영역으로 가기 때문에 그렇다.
    • 실제 코드
      • Service code
        • package com.sample.api.service; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.sample.core.domain.Member; import com.sample.core.repository.MemberRepository; @Service public class MemberService { private MemberRepository memberRepository; public MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; } public void call() { for (int i = 1; i <= 5; i++) { save(i); } } @Transactional public void save(int i) { Member build = Member.builder() .name("module-api 엘리하이해") .build(); Member save = memberRepository.save(build); if (i == 3) { throw new RuntimeException("RuntimeException 이라 롤백이 되겠지 ?? (기대중..)"); } System.out.println("저장중 ... : " + save.getName()); } }
      • Test code
      package com.sample.api.service; import static org.junit.jupiter.api.Assertions.*; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import com.sample.core.domain.Member; import com.sample.core.repository.MemberRepository; @SpringBootTest class MemberServiceTest { @Autowired private MemberService memberService; @Autowired private MemberRepository memberRepository; @Test @DisplayName("트랜잭션 롤백 처리 기대중") void testRollBack(){ try { memberService.call(); } catch (RuntimeException e) { System.out.println(e.getMessage()); List<Member> members = memberRepository.findAll(); System.out.println(members); } } }
       
      • 결과
        • notion image
       
notion image
  • this.save() 도 마찬가지 이다.
    • Spring AOP에서 Proxy란 ?
      이전에 트랜잭션 관련 소스코드를 확인하던 중 트랜잭션을 적용한 메소드를 클래스 내부에서 실행 할때 this.method()가 아닌 전역변수에 self = TransactinoClass()를 생성해두고 self.method()로 호출한 코드를 본적이 있다. 설명을 듣기로는 Transaction은 Proxy라는 클래스가 해당 메소드를 감싸고 있고 이를 호출 하기위해선 Proxy내부에서 호출하면 Transaction이 적용되지 않는다고 하였다. 이때 Proxy란 무엇이고 왜 Trasaction이 적용되지 않는지를 공부해 보려한다.
      Spring AOP에서 Proxy란 ?
      https://velog.io/@gwontaeyong/Spring-AOP%EC%97%90%EC%84%9C-Proxy%EB%9E%80
      Spring AOP에서 Proxy란 ?
 
  • 해결 방안
      1. Transactional 위치를 public void run() 메소드로 바꾼다.
      1. Proxy로 호출 될 수 있도록 만든다.
        1. 다른 클래스에서 호출 할 수 있도록 한다.
          • 다른 클래스로 추가
            • @Service class MemberService2{ private final MemberRepository memberRepository; public MemberService2(MemberRepository memberRepository) { this.memberRepository = memberRepository; } @Transactional public void save(int i) { if (i == 3) { throw new RuntimeException("RuntimeException 이라 롤백이 되겠지 ?? (기대중..)"); } Member build = Member.builder() .name("module-api 엘리하이해") .build(); Member save = memberRepository.save(build); System.out.println("저장중 ... : " + save.getName()); } }
            • 내부 로직 변경 (MemberService)
            • notion image
            • 결과
              • notion image

@Transactional 우선 순위

  1. class Method
  1. Class
  1. Interface Method
  1. Interface
 
JPA 구현체인 SimpleJpaRepository 코드를 살펴보면 클래스 상단에 readOnly = true로 설정되어 있고 deleteById(ID id) 에서는 readOnly가 없는 트랜잭션이 선언된 것을 볼 수 있다.
즉, 전체는 readOnly로 활성화 되어있지만 CUD 될 때에는 false를 선언하면서 이를 우선으로 적용시킨 것이다.
@Repository @Transactional(readOnly = true) public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> { // ... private final EntityManager em; // ... @Transactional @Override public void deleteById(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); delete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException( String.format("No %s entity with id %s exists!", entityInformation.getJavaType(), id), 1))); }
 

Transactional (readOnly =ture) 가 적용된 메소드에서

@Transactional 혹은 @Transactioanl(readOnly=false)가 적용된 메소드를 호출할 경우 무조건 read-only Transactional이 적용된다.
 
트랜잭션이 전파되는 것은 맞지만 그렇지 않은 벤더들도 있기 때문에 예외에 주의 해야 한다.
이와 반대로 readOnly = false가 적용된 메소드에서 readOnly = true 가 적용된 메소드를 호출할 경우 문제가 발생할 수 있다.

📌 REFER

[Spring] 트랜잭션 관리(Transaction)
트랜잭션은 어떤 일련의 작업을 의미 한다. 어떤 일련의 작업들은 모두 에러 없이 끝나야 하며, 작업 중에서 하나라도 잘못되면 이전에 수행한 모든 작업을 취소하고 실행 이전의 상태로 되돌리는데, 이것을 롤백이라고 한다. 즉, 데이터에 대한 무결성을 유지하기 위한 처리 방법을 트랜잭션 처리라고 한다. 트랜잭션은 데이터베이스를 수정하는 작업에는 꼭 사용해야 되는 기능이다.
https://wonyong-jang.github.io/spring/2020/03/20/Spring-Transaction.html