Spring/Transaction

Spring Transaction 과 AOP

hwanguu 2023. 8. 15. 18:17

@Transactional

기본적으로 트랜잭션을 사용할때는 @Transactional 이라는 애노테이션을 사용한다.

@Transactional 을 통한 선언적 트랜잭션 관리 방식을 사용하게 되면 기본적으로 프록시 방식의 AOP가 적용된다.

 

 

프록시 란?

프록시의 사전적 정의는 '대리인'으로, 간단하게 설명하면 내가 어떤 객체를 사용하려고 할 때 해당 객체에 직접 요청하는 것이 아닌 중간에 가짜 프록시 객체(대리인)를 두어서 프록시 객체가 대신해서 요청을 받아 실제 객체를 호출해 주도록 하는 것이다.

  • 프록시 모드를 설정하게 되면, 의존성 주입을 통해 주입되는 빈은 실제 빈이 아닌 해당 빈을 상속받은 가짜 프록시 객체이다.
  • 스프링은 CGLIB이라는 바이트 코드를 조작하는 라이브러리를 사용해서 프록시 객체를 주입해준다.
  • 프록시 객체 내부에는 실제 빈을 요청하는 로직이 들어있어, 클라이언트의 요청이 오면 그때 실제 빈을 호출해준다.(실제 빈의 조회를 필요한 시점까지 지연 처리)
  • 프록시 객체는 원래 빈을 상속받아서 만들어지기 때문에 클라이언트 입장에서는 실제 빈을 사용하는 것과 똑같은 방법으로 사용하면 된다.

 

예를 들면 회사원 백구가 TEST란 회사의 CEO인 황구를 만나기 위해 TEST빌딩의 경호원 흑구가 CEO와의 예약은 잡혀있는지 확인하여 예약이 없다면 돌려보내고, 예약이 있다면 CEO를 만날수 있게 한다.

여기서 프록시는 경호원이 된다.

만일 경호원없이 CEO가 손님이 찾아 왔을때마다 예약은 잡혀있는지 확인한다면 CEO는 자기일을 할 수 없게 될 것이다.

그렇기 때문에 경호원을 두어 손님이 찾아온경우 예약은 잡혀있는지 확인하는 공통적인 처리를 하도록 한다.

 

 

프록시 도입에 따른 트랜잭션 처리 과정

프록시 도입 전

트랜잭션을 처리하기 위해 서비스의 로직에서 직접 트랜잭션을 관리 했다.

그렇기 때문에 트랜잭션이 필요한 메서드에서는 아래 코드와 같이 중복된 트랜잭션코드를 계속 작성하는 문제가 발생한다.

 

//트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(new
DefaultTransactionDefinition());
try {
 	//비즈니스 로직
 	bizLogic(fromId, toId, money);
 	transactionManager.commit(status); //성공시 커밋
} catch (Exception e) {
	transactionManager.rollback(status); //실패시 롤백
 	throw new IllegalStateException(e);
}

 

 

프록시 도입 후

트랜잭션을 처리하기 위한 프록시를 적용하면 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 서비스 객체를 명확하게 분리할 수 있다.

 

트랜잭션 프록시 코드 예시

public class TransactionProxy {
 	private MemberService target;
 	public void logic() {
    
 		//트랜잭션 시작
 		TransactionStatus status = transactionManager.getTransaction(..);
 		try {
 			//실제 대상 호출
 			target.logic();
 			transactionManager.commit(status); //성공시 커밋
 		} catch (Exception e) {
 			transactionManager.rollback(status); //실패시 롤백
 			throw new IllegalStateException(e);
 		}
 	}
}

 

트랜잭션 프록시 적용 후 서비스 코드 예시

public class Service {
 	public void logic() {
 		//트랜잭션 관련 코드 제거, 순수 비즈니스 로직만 남음
 		bizLogic(fromId, toId, money);
 	}
}

 

 

결과

프록시 도입 전: 서비스에 비즈니스 로직과 트랜잭션 처리 로직이 함께 섞여있다.

프록시 도입 후: 트랜잭션 프록시가 트랜잭션 처리 로직을 모두 가져간다. 그리고 트랜잭션을 시작한 후에 실제 서비스를 대신 호출한다. 트랜잭션 프록시 덕분에 서비스 계층에는 순수한 비즈니즈 로직만 남길 수 있다.

 

 

프록시 도입 후 전체 과정

  • 트랜잭션은 커넥션에 con.setAutocommit(false) 를 지정하면서 시작한다.
  • 같은 트랜잭션을 유지하려면 같은 데이터베이스 커넥션을 사용해야 한다.
  • 이것을 위해 스프링 내부에서는 트랜잭션 동기화 매니저가 사용된다.
  • JdbcTemplate 을 포함한 대부분의 데이터 접근 기술들은 트랜잭션을 유지하기 위해 내부에서 트랜잭션 동기화 매니저를 통해 리소스(커넥션)를 동기화 한다.

 

References 및 사진 출처

김영한의 스프링 DB 2편 - 데이터 접근 활용 기술