AOP Sample - Transactions

본 문서에서 다룰 UserService의 upateUserList 메소드는 첫번째 인자로 등록된 사용자를 신규 등록하고 두번째 인자로 등록된 사용자 정보를 수정하는 역할을 수행하며, 해당 메소드 로직 수행을 위해서 트랜잭션 관리가 요구된다.

일반적으로 프로그램적인 방법으로 트랜잭션을 관리하는 경우 다음과 같이 try~catch 구문 내에서 Transaction Manager를 이용하여, 적절히 begin, commit, rollback을 수행한다.
public void updateUserList(UserVO newUser, UserVO updateUser) throws EmpException {
	String userName = "";
	try {
		transactionManager.begin();
		userName = newUser.getUserName();
		userDAO.addUser(newUser);
		
		userName = updateUser.getUserName();
		userDAO.updateUser(updateUser);
		transactionManager.commit();
	}
	catch (Exception e) {
		transactionManager.rollback();
		// 중략
	}
}

본 문서에서는 SpringAOP에서 제공하는 트랜잭션 관리 Aspect를 이용하여 선언적으로 트랜잭션을 관리할 수 있는 방법에 대해 살펴보기로 하자.

Annotation 이용하기

  1. 트랜잭션 관리 대상 정의

  2. 다음과 같이 Spring Framework에서 제공하는 org.springframework.transaction.annotation.Transactional annotation을 이용하여 UserServiceImplWithAnnotation의 updateUserList 메소드에 대해 트랜잭션 관리 여부를 정의한다.
    // 메소드 실행중 EmpException이 발생한 경우 rollback을 수행하지 않음.
    @Transactional(noRollbackFor={EmpException.class})
    public void updateUserList(UserVO newUser, UserVO updateUser) throws EmpException {
    	String userName = "";
    	try {
    		userName = newUser.getUserName();
    		userDAO.addUser(newUser);
    		
    		userName = updateUser.getUserName();
    		userDAO.updateUser(updateUser);
    	}
    	catch (Exception e) {
    		// 중략
    	}
    }
    위 샘플 코드에서처럼 @Transactional annotation에는 다음과 같은 상세 속성 정보를 부여할 수 있다.
    Parameter Description
    isolation 트랜잭션의 isolation 레벨 정의하는 요소. 별도로 정의하지 않으면 DB의 Isolation 레벨을 따름.
    noRollbackFor 정의된 Exception 목록에 대해서는 rollback을 수행하지 않음.
    noRollbackForClassname Class 객체가 아닌 문자열을 이용하여 rollback을 수행하지 않아야 할 Exception 목록 정의
    propagation 트랜잭션의 propatation 유형 정의
    readOnly 해당 트랜잭션을 읽기 전용 모드로 처리
    rollbackFor 정의된 Exception 목록에 대해서는 rollback 수행
    rollbackForClassName Class 객체가 아닌 문자열을 이용하여 rollback을 수행해야 할 Exception 목록 정의
    timeout 지정한 시간 내에 해당 메소드 수행이 완료되지 않은 경우 rollback 수행. -1일 경우 no timeout

  3. TransactionManager 정의

  4. Spring 속성 정의 XML(applicationContext-transaction-datasource.xml) 파일 내에 트랜잭션을 관리하는 실질적인 역할을 수행하는 TransactionManager를 정의한다. 다음에 보이는 샘플 코드는 DataSource Transaction Service를 사용하고 있다.
    <bean id="transactionManagerDataSource" 
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    	<property name="dataSource"><ref bean="common_datasource"/></property>
    </bean>
  5. 서비스 속성 정의 XML에 annotation 속성 추가

  6. 해당 서비스가 정의된 속성 정의 XML applicationContext-user-aop-annotation.xml 내에 다음과 같이 속성 정보를 추가한다. 이것은 런타임시 Proxy 클래스를 생성하여 해당 서비스를 대체시키는 역할을 수행한다. 따라서, 서비스 메소드를 호출하면 Proxy에서 Transactionmanager를 통해 트랜잭션을 시작된 후 해당 메소드가 호출될 수 있게 된다.
    <tx:annotation-driven transaction-manager="transactionManagerDataSource"/>
  7. 테스트 클래스 실행

  8. 테스트 클래스 UserServiceWithAnnotationTest는 UserService의 updateUserList를 호출한다. updateUserList 메소드의 첫번째 입력 인자는 신규 사용자로 두번째 입력 인자는 수정할 사용자로 인식된다. 따라서 첫번째 입력 인자를 신규 사용자 정보로 하고 두번째 입력 인자를 존재하지 않는 사용자 ID를 가진 사용자 정보로 전달하였을 경우 신규 사용자를 성공적으로 등록하고 두번째 특정 사용자 정보를 수정하려고 했을 때 해당 ID를 가진 사용자가 존재하지 않아 수정에 실패하게 된다. 그러나 UserServiceImplWithAnnotation의 updateUserList 메소드에 정의한 트랜잭션 속성에 의해 신규로 등록한 사용자 정보가 rollback되지 않고 commit된다. 다음은 테스트 클래스 UserServiceWithAnnotationTest의 testUpdateUserWithNotExistUser 메소드의 로직이다.
    public void testUpdateUserWithNotExistUser() throws EmpException {
    	UserService userService = null;
    	try {
    		userService = (UserService) context.getBean("UserServiceWithAnnotation");
    
    		// 1. update user list
    		UserVO newUser = new UserVO();
    		newUser.setUserId("testuser");
    		// 중략
    		newUser.setRegDate(null);
    
    		UserVO updateUser = userService.getUser("woos41");
    		// 존재하지 않는 사용자 ID로 변경
    		updateUser.setUserId("woos");
    		String name = "woostest";
    		updateUser.setUserName(name);
    
    		// 신규 사용자 정보와 수정 대상 사용자 정보를 인자로 셋팅
    		userService.updateUserList(newUser, updateUser);
    		// 해당 메소드 수행시 에러가 발생하지 않으면 fail
    		throw new EmpException("fail to get user.");
    	} catch (EmpException e) {
    		try {
    			// 신규 사용자의 ID로 사용자 정보 조회
    			userService.getUser("testuser");
    		} catch (EmpException ee) {
    			// 사용자 정보 수정에 실패하였으나 Annotation 정의에 따라 
    			// 신규 등록된 사용자 정보는 commit되었어야 함.		
    			throw new EmpException("fail to manage transaction.");
    		}
    	}
    }
    

XML 정의 이용하기

  1. Aspect 정의

  2. Spring 속성 정의 XML(applicationContext-user-aop.xml) 파일 내에 Advice와 Pointcut을 정의한다.
    <tx:advice id="txAdvice" transaction-manager="transactionManagerDataSource">
    	<tx:attributes>
    		<!-- 메소드 실행중 EmpException이 발생한 경우 rollback을 수행하지 않음 -->
    		<tx:method name="*" no-rollback-for="com.sds.emp.common.EmpException"/>
    	</tx:attributes>
    </tx:advice>
    
    <aop:config>
    	<!-- pointcut 정의 : UserServiceImplWithXML 클래스의 updateUserList 메소드 호출시 -->
    	<aop:pointcut id="updateUserList" 
    	     expression="execution(* com.sds.emp.user.services.impl.UserServiceImplWithXML.updateUserList(..))"/>
    	<!-- advice 정의 : 위 tx 태그를 이용하여 정의한 advice 참조 -->
    	<aop:advisor advice-ref="txAdvice" pointcut-ref="updateUserList"/>
    </aop:config>
    위 XML에서처럼 tx:method에는 다음과 같은 상세 속성 정보를 부여할 수 있다.
    Parameter Description
    name 메소드명. 와일드카드 사용 가능
    isolation 트랜잭션의 isolation 레벨(DEFAULT, READ_COMMITTED, READ_UNCOMMITTED, REPEATABLE_READ, SERIALIZABLE) 정의하는 요소. 별도로 정의하지 않으면 DB의 Isolation 레벨을 따름.
    no-rollback-for 정의된 Exception 목록에 대해서는 rollback을 수행하지 않음.
    propagation 트랜잭션의 propatation 유형(MANDATORY, NESTED, NEVER, NOT_SUPPORTED, REQUIRED, REQUIRES_NEW, SUPPORTS) 정의. 기본값은 REQUIRED
    read-only 해당 트랜잭션을 읽기 전용 모드로 처리. 기본값은 false
    rollback-for 정의된 Exception 목록에 대해서는 rollback 수행
    timeout 지정한 시간 내에 해당 메소드 수행이 완료되지 않은 경우 rollback 수행. -1일 경우 no timeout. 기본값은 -1

  3. TransactionManager 정의

  4. Spring 속성 정의 XML(applicationContext-transaction-datasource.xml) 파일 내에 트랜잭션을 관리하는 실질적인 역할을 수행하는 TransactionManager를 정의한다. 다음에 보이는 샘플 코드는 DataSource Transaction Service를 사용하고 있다.
    <bean id="transactionManagerDataSource" 
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    	<property name="dataSource"><ref bean="common_datasource"/></property>
    </bean>
  5. 테스트 클래스 실행

  6. 테스트 클래스 UserServiceWithXMLTest는 UserService의 updateUserList를 호출한다. updateUserList 메소드의 첫번째 입력 인자는 신규 사용자로 두번째 입력 인자는 수정할 사용자로 인식된다. 따라서 첫번째 입력 인자를 신규 사용자 정보로 하고 두번째 입력 인자를 존재하지 않는 사용자 ID를 가진 사용자 정보로 전달하였을 경우 신규 사용자를 성공적으로 등록하고 두번째 특정 사용자 정보를 수정하려고 했을 때 해당 ID를 가진 사용자가 존재하지 않아 수정에 실패하게 된다. 그러나 applicationContext-user-aop.xml에 정의한 트랜잭션 속성에 의해 신규로 등록한 사용자 정보가 rollback되지 않고 commit된다. 다음은 테스트 클래스 UserServiceWithXMLTest의 testUpdateUserWithNotExistUser 메소드의 로직이다.
    public void testUpdateUserWithNotExistUser() throws EmpException {
    	UserService userService = null;
    	try {
    		userService = (UserService) context.getBean("UserServiceWithAnnotation");
    
    		// 1. update user list
    		UserVO newUser = new UserVO();
    		newUser.setUserId("testuser");
    		// 중략
    		newUser.setRegDate(null);
    
    		UserVO updateUser = userService.getUser("woos41");
    		// 존재하지 않는 사용자 ID로 변경
    		updateUser.setUserId("woos");
    		String name = "woostest";
    		updateUser.setUserName(name);
    
    		// 신규 사용자 정보와 수정 대상 사용자 정보를 인자로 셋팅
    		userService.updateUserList(newUser, updateUser);
    		// 해당 메소드 수행시 에러가 발생하지 않으면 fail
    		throw new EmpException("fail to get user.");
    	} catch (EmpException e) {
    		try {
    			// 신규 사용자의 ID로 사용자 정보 조회
    			userService.getUser("testuser");
    		} catch (EmpException ee) {
    			// 사용자 정보 수정에 실패하였으나 트랜잭션 속성 정의에 따라 
    			// 신규 등록된 사용자 정보는 commit되었어야 함.		
    			throw new EmpException("fail to manage transaction.");
    		}
    	}
    }
    

UserService

다음은 Aspect 적용 대상이 되는 UserService가 제공하는 상세 기능 목록이다.
Method Name Return Type Parameters
getUserList com.sds.emp.common.Page com.sds.emp.user.services.SearchVO
getUser com.sds.emp.user.services.UserVO java.lang.String
addUser void com.sds.emp.user.services.UserVO
updateUser void com.sds.emp.user.services.UserVO
checkDuplication boolean java.lang.String
updateUserList void com.sds.emp.user.services.UserVO, com.sds.emp.user.services.UserVO

Resources

  • 다운로드
  • 샘플 테스트 코드를 포함하고 있는 anyframe-aoptest-transaction-src.zip 파일을 다운받은 후 위에서 제시한 예제 코드를 실행해 볼 수 있다.
    Name
    Download
    anyframe-aoptest-transaction-src.zip
    Download