스프링으로 프로젝트를 하면서 Service 관련 클래스를 작성할 때 빠짐없이 등장하는 어노테이션이 @Transaction입니다.
해당 어노테이션의 역할은 말 그대로 서비스 레이어에서 db에 접근할 때 트랙잭션 처리를 해주는 것입니다.
그렇다면 Transaction 어노테이션이 등장하게 된 배경과 어떠한 코드를 내부 로직으로 지니고 있을까요?
Transaction Annotation 등장 배경
가장 흔하게 많이 사용하는 어플리케이션 구조는 다음과 같습니다.
Controller (프레젠테이션 계층) - Service(서비스 계층) - Repository(데이터 접근 계층) - DB서버
위 계층 중에 가장 중요한 곳은 핵심 비즈니스 로직이 담겨있는 서비스 계층입니다. 시간이 지나면 컨트롤러와 관련된 UI 부분은 변하고 Repository의 데이터 저장 기술은 다른 기술로 변해도 비즈니스 로직은 최대한 변경없이 유지되야 합니다.
이러한 서비스 계층을 만드려면 최대한 특정 기술에 종족되지 않게 개발해야합니다. 코드를 예로 들어보자면,
위의 코드는 트랙잭션을 적용하지 않은 순수 비즈니스 로직 관련 코드입니다. 특정 기술에 종속되지 않아서 코드가 깔끔
하고 유지보수 하기도 용이합니다. 향후 비즈니스 로직이 변경이 필요하다면 해당 부분만 변경하면 됩니다.
이 상태에서 트랜잭션을 적용한 코드는 어떻게 될까요?
데이터 접근 기술로 JDBC 기술을 활용한다면 다음과 같은 트랜잭션 로직이 만들어집니다. 만약 향후 JPA와 같은 기술로
변경하게 된다면 JDBC 로직이 담겨있는 서비스 코드도 모두 함께 변경해야 하는 불상사가 생기게 됩니다. 다음과 같은
상태는 핵심 비즈니스 로직과 JDBC기술이 섞여 있어 유지보수하기도 어렵습니다. 다음과 같은 문제를 해결하기 위해
스프링에서는 트랙잭션 추상화를 제공합니다.
트랙잭션 추상화
트랜잭션은 시작 단계와 비즈니스 로직이 성공적으로 수행되면 커밋 혹은 실패할 경우 롤백하는 단계로 이루어져 있습
니다. 어떤 데이터 접근 기술을 사용하더라도 내부 구현하는 코드만 다를 뿐 실질적으로 하는 동작은 똑같습니다.
따라서 트랜잭션은 추상화가 가능합니다. 서비스는 특정 트랙잭션 기술에 직접 의존하는 것이 아닌 추상화된 인터페이스
에 의존하도록 설정합니다. 실질적인 구현체는 DI를 통해 주입하면 됩니다. 스프링은 다음과 같은 트랙잭션 추상화 기술을
제공합니다.
트랙잭션 추상화의 핵심은 PlatformTransactionManager 인터페이스입니다.
package org.springframework.transaction;
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
해당 인터페이스에는 트랙잭션의 실질적인 동작과 관련된 것들이 정의되어 있습니다.
스프링이 제공하는 PlatformTransactionManager 을 통하여 특정 데이터 기술에 의존하지 않고 트랙잭션을 추상화 할 수
있게 되었습니다.
하지만 문제가 하나 있습니다. 하나의 트랙잭션에는 여러 작업으로 이루어져있습니다.
예를 들자면, A가 돈을 B에게 송금하는 작업은
1. A라는 아이디로 등록되어 있는 회원이 있는지 Repository에서 찾아본다.
2. B라는 아이디로 등록되어 있는 회원이 있는지 Repository에서 찾아본다.
3. A 회원이 존재하면 A회원의 돈을 뺀다.
4. B회원이 존재하면 A회원에서 뺀 돈을 B회원의 돈에 더한다.
와 같이 여러 작업들로 이루어져있습니다. 해당 작업들을 수행할 때 중요한점은 DB커넥션이 동일해야 한다는 것입니다.
만약 DB커넥션이 동일하지 않다면 000이라는 커넥션으로 A회원의 돈을 뺸 작업이 성공적으로 수행되고
이후 B회원에 돈을 더하는 과정이 111이라는 커넥션으로 시도하는 과정에서 예상치 못한 오류로 실패한다면 전체 작업을 RollBack해야 하는데 커넥션이 다르기 때문에 A회원에게서 돈을 뺀 작업은 RollBack처리가 되지 않는 문제가 생깁니다.
service 레이어에서 repository에 한 트랜잭션에 속하는 작업들을 개별적으로 요청하는 것은 각각 또 다른 db 커넥션을
사용하여 db에 접근하는 것을 뜻합니다. 따라서 커넥션 동기화(같은 커넥션을 사용)를 하기 위해서는 파라미터로
커넥션을 service layer에서 repository layer로 넘겨야 합니다. 코드로 예를 들자면 다음과 같습니다.
코드를 보시면 커넥션을 repository에 con이라는 매게변수로 전달하고 있습니다. 해당 과정을 통해 하나의 트랙잭션에
속하는 작업들은 모두 같은 커넥션을 사용할 수 있게 됩니다. 하지만 위와 같은 방식은 코드가 지저분해지고 커넥션을
넘기는 메서드와 넘기지 않는 메서드를 중복해서 만들어야 하는 단점들이 많습니다.
위 코드를 통해 repository에서 파라미터로 넘어오는 커넥션을 처리하기 위해 동일한 update 로직이 중복된 것을 알 수
있습니다.
스프링은 위와 같은 문제를 해결하기 위해 트랜잭션 동기화 매니저를 제공합니다. 이것은 쓰레드 로컬을 사용하여
커넥션을 동기화 해줍니다. 동작 방식은 다음과 같습니다.
1. 트랙잭션 매니저가 데이터소스를 통해 커넥션을 만들고 트랜잭션을 시작한다.
2. 트랙잭션 매니저는 트랜잭션이 시작된 커넥션을 트랙잭션 동기화 매니저에 보관한다.
3. 리포지토리는 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용한다. (파라미터로 커넥션은 전달하지 않아도 됨)
4. 트랜잭션이 종료되면 트랙잭션 매니저는 트랜잭션 동기화 매니저에 보관된 커넥션을 통해 트랜잭션은 종료하고
커넥션을 닫는다.
다음과 같이 스프링에서 제공하는 트랙잭션 매니저와 트랙잭션 동기화 매니저를 통하여 트랙잭션을 추상화하고 커넥션
동기화를 할 수 있게 되었습니다. 하지만 서비스 로직에는 여전히 트랙잭션 관련 로직이 담겨 있습니다. 트랜잭션 기술을
사용하려면 어쩔수 없이 트랙잭션 코드가 필요하기 때문입니다. 해당 문제를 해결하기 위해 스프링은 트랙잭션 어노테이
션을 활용합니다!
트랙잭션 어노테이션
트랜잭션 어노테이션의 핵심은 프록시와 AOP라고 할 수 있습니다.
AOP는 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 모듈화 하는 것을
뜻합니다. 여기서 모듈화란 어떤 공통된 로직이나 기능을 하나의 단위로 묶는 것을 뜻합니다. 예를 들자면 핵심적인 관점은
서비스 레이어의 비즈니스 로직이고 부가적인 관점은 트랙잭션 관련 처리라고 볼 수 있겠죠.
트랙잭션 관련 처리와 같이 소스 코드상에서 계속 반복해서 사용되는 부분을 흩어진 관심사라고 합니다.
AOP는 이러한 흩어진 관심사를 공통으로 처리할 수 있게 모듈화 하는 것을 뜻합니다.
이때 필요한 것이 프록시입니다. 프록시란 대리자라는 뜻으로, 클라이언트가 사용하려고 하는 실제 대상인 것처럼 위장해
클라이언트의 요청을 받아주는 역할을 수행합니다.
서비스 레이어에 비즈니스 순수 로직들만 적고 트랜잭션 관련 로직은 작성하지 않았다고 가정해봅시다. 이 때 트랙잭션
관련 로직을 AOP로 처리하는 프록시 객체가 어플리케이션이 실행되면 실제 서비스 레이어의 코드를 불러오게 됩니다.
그리고 어플리케이션은 실질적인 서비스 레이어를 실행하는 것이 아닌 프록시 객체를 실행하게 됩니다.
해당 과정을 자동화 해주는 것이 트랜잭션 어노테이션입니다. Service레이어에 @Transaction을 사용하게 되면
어플리케이션은 Service관련 로직이 담긴 프록시 객체를 실행하게 됩니다. 해당 프록시 객체는 트랙잭션은 AOP로 처리합니다.
다음과 같이 @Transaction을 사용한 service 클래스를 테스트화면에서 조회해보면
위와 같이 EnhanceBySpringCGLIB 라는 클래스로 표시된 것을 볼 수 있습니다. 이것은 프록시가 적용된 것입니다.
memberRepository는 트랙잭션 어노테이션을 사용하지 않았기 떄문에 프록시가 적용되지 않은 것을 알 수 있습니다.
이상으로 트랙잭션 어노테이션의 등장 배경과 작동 원리에 대해서 알아봤습니다!
본 게시글은 인프런 스프링 DB접근 기술1을 참고하여 작성되었습니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-1/dashboard
스프링 DB 1편 - 데이터 접근 핵심 원리 - 인프런 | 강의
백엔드 개발에 필요한 DB 데이터 접근 기술을 기초부터 이해하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., - 강의
www.inflearn.com
'Spring' 카테고리의 다른 글
스프링 빈의 쓰레드 안정성에 대하여 (0) | 2023.02.21 |
---|---|
스프링 시큐리티 아키텍처에 대하여 (0) | 2023.01.29 |
스프링 검증 BindingResult에 대하여 (0) | 2022.12.28 |
스프링 빈 스코프란? (0) | 2022.12.23 |
Reflection 이란? (0) | 2022.12.16 |