INTRO
트랜잭션을 처리하기 위해 트랜잭션 추상화, 트랜잭션 동기화, 트랜잭션 템플릿 등을 도입했다. 덕분에 트랜잭션을 처리하는데 있어서 반복되는 코드를 해결할 수 있었지만, 아직 Application 구조상의 서비스 계층 순수함은 충족시키지 못했다. 이를 해결하기 위해 스프링 AOP를 통한 프록시를 활용해 보겠다.
프록시를 통한 문제 해결
[그림. 1]프록시 도입전- 프록시를 도입하기 전에는 그림과 같이 서비스 로직에서 트랜잭션을 직접 시작한다.
[그림. 2]프록시 도입후- 프록시를 이용한다면 이처럼 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 서비스 객체를 명확하게 분리 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| 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);
}
}
}
|
[코드. 1]트랜잭션 프록시 코드 예1
2
3
4
5
6
| public class Service {
public void logic() {
//트랜잭션 관련 코드 제거, 순수 비즈니스 로직만 남음
bizLogic(fromId, toId, money);
}
}
|
[코드. 2]트랜잭션 프록시 적용 후 서비스 코드 예- 프록시를 도입하고 나면 트랜잭션 프록시가 처리 로직을 모두 가져가 트랜잭션을 시작한 후에 실제 서비스를 대신 호출한다.
- 덕분에 서비스 계층에는 순수한 비즈니스 로직만 남길 수 있게 됐다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| import hello.jdbc.domain.Member;
import hello.jdbc.repository.MemberRepositoryV3;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import java.sql.SQLException;
/**
* 트랜잭션 - @Transactional AOP */
@Slf4j
@RequiredArgsConstructor
public class MemberServiceV3_3 {
private final MemberRepositoryV3 memberRepository;
/**
* @Transactional 어노테이션을 통해 순수 비즈니스 로직만 남기고 트랜잭션 코드는 모두 생략 가능 해진다.
* - 클래스에 붙이면 외부에서 호출 가능한 public 메서드가 AOP 적용 대상이 된다
*/
@Transactional
public void accountTransfer(String fromId, String toId, int money) throws SQLException {
Member fromMember = memberRepository.findById(fromId);
Member toMember = memberRepository.findById(toId);
int fromMemberMoney = fromMember.getMoney() - money;
int toMemberMoney = toMember.getMoney() + money;
memberRepository.update(fromId, fromMemberMoney);
validation(toMember);
memberRepository.update(toId, toMemberMoney);
}
private void validation(Member toMember) {
if (toMember.getMemberId().equals("ex")) {
throw new IllegalStateException("이체중 예외 발생");
}
}
}
|
[코드. 3]트랜잭션 프록시 적용 후 서비스 코드- 순수한 비즈니스 로직만 남기고, 트랜잭션 관련 코드는 모두 제거했다.
- 스프링이 제공하는 트랜잭션 AOP를 적용하기 위해
@Transactional
애노테이션을 추가했다. @Transactional
애노테이션은 메서드에 붙여도 되고, 클래스에 붙여도 되지만, 클래스에 붙이면 외부에서 호출 가능한 public
메서드가 AOP 적용 대상이 된다.
트랜잭션 AOP 의 적용 흐름
[그림. 3]트랜잭션 AOP 적용 전체 흐름
선언적 트랜잭션 관리 (Declarative Transaction Management)
@Transactional
애노테이션 하나만 선언해서 매우 편리하게 트랜잭션을 적용하는 것을 선언적 트랜잭 션 관리라 한다.- 선언적 트랜잭션 관리가 프로그래밍 방식에 비해서 훨씬 간편하고 실용적이기 때문에 실무에서는 대부분 선언적 트랜잭션 관리를 사용한다.
- 스프링이 제공하는 선언적 트랜잭션 관리 덕분에 드디어 트랜잭션 관련 코드를 순수한 비즈니스 로직에서 제거할 수 있게 됐다.
- 트랜잭션이 필요한 곳에
@Transactional
애노테이션 하나만 추가하면, 스프링 트랜잭션 AOP가 자동으로 처리해준다.