AOP: 스프링의 3대 기반기술

6.1 트랜잭션 코드의 분리

현재 코드의 문제점: 비즈니스 로직이 주가 아니라, 트랜잭션 코드가 더 큰 자리를 차지하고 있음

// 비즈니스 로직과 트랜잭션 경계설정의 분리

public void upgradeLevels() throws Exception {
	TransactionStatus status = this.transactionManager.getTransaction(
		new DefaultTransactionDefinition());
	try {
		upgradeLevelsInternal();
		this.transactionManager.commit(status);
	} catch (Exception e) {
		this.transactionManager.rollback(status);
		throw e;
	}
}

// 비즈니스 로직 코드
private void upgradeLevelsInternal(){
	List<User> users = userDao.getAll();
	for (User user : users) {
		if (canUpgradeLevel(user)) {
			upgradeLevel(user);
		}
	}
}

분리는 했으나, 아예 트랜잭션 코드를 숨기려면? → 트랜잭션 코드를 클래스 밖으로 뽑아내기

UserService를 인터페이스로 만들고 기존 코드는 UserService인터페이스의 구현 클래스를 만들어넣기

// 기존의 UserService 클래스를 UserServiceImpl로 이름 변경
// 클라이언트가 사용할 로직을 담은 핵심 메소드만 UserService 인터페이스로 만든 후
// UserServiceImpl이 구현하도록 함

// 트랜잭션 코드를 제거한 UserService
public class UserServiceImpl implements UserService {
	UserDao userDao;
	MailSender mailSender;

	public void upgradeLevels() {
		Lise<User> users = userDao.getAll();
		for (User user : users) {
			if (canUpgradeLevel(user)) {
				upgradeLevel(user);
			}
		}
}
// 비즈니스 트랜잭션 처리
// UserServiceTx 클래스

public class UserServiceTx implements UserService {
// UserService를 구현한 다른 오브젝트를 DI 받음
	UserService userService;
	
	public void setUserService(UserService userService) {
		this.userService = userService;
	}

// DI 받은 UserService 오브젝트에 모든 기능을 위임
	public void add(User user) {
		userService.add(user);
	}

	public void upgradeLevels() {
		userService.upgradeLevels();
	}
}

// 트랜잭션이 적용된 UserServiceTx
public class UserServiceTx implements UserService {
// UserService를 구현한 다른 오브젝트를 DI 받음
	UserService userService;
// 추상화된 트랜잭션 구현 오브젝트를 DI 받을 수 있도록 프로퍼티 추가
	PlatformTransactionManager transactionManager;

	public void setTransactionManager(PlatformTransactionManager transactionManager) {
		this.transactionManger = transactionManager;
	}	

	public void setUserService(UserService userService) {
		this.userService = userService;
	}

// DI 받은 UserService 오브젝트에 모든 기능을 위임
	public void add(User user) {
		userService.add(user);
	}

	public void upgradeLevels() {
		TransactionStatus status = this.transactionManager.getTransaction(new
				DefaultTransactionDefinition());
		try {
			userService.upgradeLevels();
			this.transactionManager.commit(status);
		} catch (RuntimeException e) {
			this.transactionManager.rollback(status);
			throw.e;
		}
	}
}

트랜잭션 분리에 따른 테스트 수정

목 오브젝트를 사용해 수동 DI를 적용하는 테스트 → 어떤 클래스의 오브젝트인지 분명하게 알 필요가 있음

⇒ @Autowired UserServiceImpl userServiceImpl;

로 해당 클래스로 만들어진 빈을 주입받도록함(@Autowired 지정)

// 목 오브젝트 설정이 필요한 테스트 코드수정
@Test
public void upgradeLevels() throws Exception {
	MockMailSender mockMailSender = new MockMailSender();
	userServiceImpl.setMailSender(mockMailSender);

트랜잭션 경계설정 코드 분리의 장점