@Transactional의 동작 원리

김성길·2023년 5월 27일
0

기술 면접 준비

목록 보기
7/7
post-thumbnail

🤔 개요

면접관님: AOP에 대해 설명해주세요.
나: 관점지향 프로그래밍으로써, 공통적인 부분을 객체를 만들어 중복 코드를 없애주는 것 입니다.
면접관님: AOP를 써본 예시에 대해서 설명해주세요.
나: AOP는 사용해본적 없습니다!

실제로 지난주 목요일 내가 면접을 봤을때 실제로 대답한 말이다.
지금 생각해보니 이불킥을 당장이라도 차고 싶을 만큼 개 뚱단지 같은 소리라고 생각한다.
다시는 이런 상황이 생기지 않게 글로 정리해 공부해볼려고 한다!

💻 AOP란?

위 사진을 보면 노란색의 로직은 Class A, Class B, Class C에서 모두 중복된 로직들이다.
하지만 요구사항이 들어와 노란색 로직을 바꾼다고 가정해보자, 그럼 우린 Class A, Class B, Class C 모든 클래스마다 공통적이게 수정을 해야할것이다.
이렇게 어떤 로직을 기준으로 별도의 객체를 만들어 분리하는 기술을 AOP라 한다.

Spring AOP의 특징

  • Bean에만 AOP 기술 적용 가능하다.
  • Proxy 방식으로 AOP가 동작한다.

🤔 @Transactional의 동작 과정은?

@Transactional는 Spring AOP를 기반으로 동작한다.
그래서 위에 특징으로 설명한것과 같이 Proxy 방식을 통해 동작한다.

위의 사진 처럼 대상 객체(Transaction을 적용할 로직)를 감싸는 Proxy 객체를 만들어 대상 객체의 결과에 따라 commit, rollback 한다. 위 사진을 코드로 바꾼다면 아래 코드 처럼 바뀔것이다.

public class TransactionProxy{
    private final TransactonManager manager = TransactionManager.getInstance();
		...

    public void transactionLogic() {
        try {
            // 트랜잭션 전처리(트랜잭션 시작, autoCommit(false) 등)
			manager.begin();

			// 다음 처리 로직(타겟 비스니스 로직, 다른 부가 기능 처리 등)
			target.logic();
            
			// 트랜잭션 후처리(트랜잭션 커밋 등)
            manager.commit();
        } catch ( Exception e ) {
			// 트랜잭션 오류 발생 시 롤백
            manager.rollback();
        }
    }

💻 Proxy 객체를 만드는 방법은?

Proxy 객체를 만드는 방식으로는 2가지가 있다.

  • JDK Proxy 방식
    Spring에서 사용하는 방식으로 Target 클래스의 인터페이스를 상속하여 Proxy 객체를 만든다.
    하지만 Target 클래스의 인터페이스가 존재하지 않는다면 JDK 방식에서는 Exception을 던져준다.

  • CGLip Proxy
    JDK 방식과는 달리 인터페이스가 없어도 Target 클래스를 상속하여 Proxy 객체를 만든다.
    Target 클래스의 바이트 코드를 조작하여 Proxy 객체를 만들기 때문에 리플렉션 방식의 JDK Proxy보다 성능이 좋아 Spring Boot에서 사용한다.

🚨 @Transactional의 주의점

  1. Target 메서드에 private 사용 불가
    프록시 객체는 Target 객체, 인터페이스를 상속 받아 객체를 만들기 때문에 private로 되어있으면 상속 받을 수 없기 때문에 트랜잭션 관리가 되지 않는다.

  2. Target 메서드에 내부적인 메서드를 사용하지 말자
    트랜잭션이 아닌 메소드에서 트랜잭션이 선언된 내부의 메소드를 호출하면 프록시 객체가 아닌 대상 객체의 메소드를 호출하기 때문에 트랜잭션이 적용되지 않는다.

@Service
@RequiredArgsConstructor
public class PostService {
	private final PostRepository postRepository;

	public void savePostList(List<PostDto> postListDto) {
      	postListDto.forEach(postDto -> addBook(postDto));
    }
  
    @Transactional
    public void savePost(PostDto postDto) {
        Post post = postDto.toEntity();
        postRepository.save(post);
    }

}

구린 코드이지만 설명을 위해서..
위에 코드 처럼 @Transaction 어노테이션을 내부 메서드에 선언하면 프록시 객체가 savePost를 타겟 메서드로 삼기 때문에 이것을 주의하여 @Transactional을 선언하여야 한다.

😁 마무리

스프링 공부하면서 무조건 쓰게 되는게 @Transactional 어노테이션인데 어떻게 돌아가는지도 모르고 썼었던 과거의 나에게 한대 때리고 싶고, 앞으로도 내가 쓰는 기술들의 동작 과정에 대해서 공부해볼려고 한다!

🙇‍♂️ 참고

https://velog.io/@ann0905/AOP%EC%99%80-Transactional%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC#-contents
https://velog.io/@pond1029/transactional
https://mommoo.tistory.com/92

0개의 댓글