간혹 스프링 트랜잭션을 적용하였는데 예외 발생 시 롤백이 되지 않을 때가 있다.
안되는 이유야 여러 가지가 있겠지만 난 그 중 한 가지 문제에 대해서 작성하려고 한다.
일단 테스트하는 스프링 애플리케이션 컨텍스트의 트랜잭션 AOP 설정은 다음과 같이 선언적 트랜잭션을 사용하였다.
service 패키지 하위에 있는 모든 클래스 중 insert*, delete*, update* 이름에 매칭되는 메소드에 트랜잭션 설정
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="find*" read-only="true" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="servicePublicMethod" expression="execution(public * com.incross.svc.component..service..*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePublicMethod" />
</aop:config>
테스트 코드는 다음과 같다.
문제가 발생되는 원인에 대해서 보여주려고 실패 case에 대한 메소드를 생성하였다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/test-application-context.xml"})
@ActiveProfiles("dev")
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void 트랜잭션롤백테스트_실패case() throws Exception {
User user = new User();
user.setUserId("abc1111");
user.setPassword("1111");
user.setUserName("kyu");
User user1 = new User();
user1.setUserId("abc2222");
user1.setPassword("2222");
user1.setUserName("kyu");
userService.insertUser(user, user1);
}
}
서비스 클래스의 insertUser 메소드 내부 코드이다.
다음과 같이 insert를 두 번 한 후 FileNotFoundException을 강제 발생하였다.
public void insertUser(User user, User user1) throws FileNotFoundException {
userDAO.insertUser(user);
userDAO.insertUser(user1);
// checked Exception 강제로 발생
throw new FileNotFoundException();
}
위와 같이 코드를 작성한 후 테스트를 돌리면 어떻게 될까?
아마 두 번 실행된 insertUser 트랜잭션에 대해 정상적으로 롤백이 되어야 한다고 생각한다.
하지만 스프링 트랜잭션 AOP는 default 옵션으로 unchecked Exception인 RuntimeException에 대해서만 롤백을 해준다.
즉, <tx:method name="insert*" propagation="REQUIRED" /> 설정이 다음과 같이 rollback-for 옵션이 지정된 것 같다.
<tx:method name="insert*" propagation="REQUIRED" rollback-for="RuntimeException" />
결과적으로 insert* 메소드에서 RuntimeException 발생 시에만 자동 롤백을 해준다는 것이다.
만약 FileNotFoundException 발생 시에도 롤백을 지원하고 싶다면 rollback-for="Exception" 와 같이 설정하면 된다.
하지만 난 이 방법은 추천하고 싶지 않다.
checked Exception인 FileNotFoundException 발생 시 try catch 블록을 이용하여 RuntimeException 으로 포장하는 편이 더 깔끔한 코드를 유지할 수 있기 때문이다.
public void insertUser(User user, User user1) {
userDAO.insertUser(user);
userDAO.insertUser(user1);
try {
// checked Exception 강제로 발생
throw new FileNotFoundException();
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
위의 코드를 보면 throws FileNotFoundException이 사라졌다.
결국 insertUser 메소드를 호출하는 쪽에서 FileNotFoundException에 대한 예외 처리를 하지 않아도 되기 때문에 코드의 가독성이 좋아진다.
'프로그래밍' 카테고리의 다른 글
스프링 시큐리티 적용하기 (properties 인증 방법) (2) | 2013.04.20 |
---|---|
스프링 MVC에서 SimpleMappingExceptionResolver 선언 시 주의 사항 (0) | 2013.04.18 |
알고리즘 문제 풀이 방법에 대해서. (0) | 2013.03.08 |
enum 으로 생성한 클래스 객체가 싱글톤인 이유? (0) | 2013.03.05 |
스프링 interceptor afterCompletion 메소드 이용 시 참고할 사항 (0) | 2013.02.26 |
스프링 CGLib 클래스 프록시 사용 (0) | 2013.02.26 |
spring MVC 404 처리 방법? (3) | 2013.02.26 |
개방 폐쇄 원칙 OCP (Open-Closed Principle) (2) | 2013.02.26 |