간혹 스프링 트랜잭션을 적용하였는데 예외 발생 시 롤백이 되지 않을 때가 있다.
안되는 이유야 여러 가지가 있겠지만 난 그 중 한 가지 문제에 대해서 작성하려고 한다.
일단 테스트하는 스프링 애플리케이션 컨텍스트의 트랜잭션 AOP 설정은 다음과 같이 선언적 트랜잭션을 사용하였다.
service 패키지 하위에 있는 모든 클래스 중 insert*, delete*, update* 이름에 매칭되는 메소드에 트랜잭션 설정
<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에 대한 메소드를 생성하였다.
@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을 강제 발생하였다.
userDAO.insertUser(user);userDAO.insertUser(user1);// checked Exception 강제로 발생
throw new FileNotFoundException();
}
위와 같이 코드를 작성한 후 테스트를 돌리면 어떻게 될까?
아마 두 번 실행된 insertUser 트랜잭션에 대해 정상적으로 롤백이 되어야 한다고 생각한다.
하지만, 스프링 트랜잭션 AOP는 default 옵션으로 unchecked Exception인 RuntimeException에 대해서만 롤백을 해준다.
즉, 설정이 다음과 같이 rollback-for 옵션이 지정된 것 같다.
결과적으로 insert* 메소드에서 RuntimeException 발생 시에만 자동 롤백을 해준다는 것이다.
만약 FileNotFoundException 발생 시에도 롤백을 지원하고 싶다면 rollback-for=”Exception” 와 같이 설정하면 된다.
하지만 난 이 방법은 추천하고 싶지 않다.
Checked Exception인 FileNotFoundException 발생 시 try catch 블록을 이용하여 RuntimeException 으로 포장하는 편이
더 깔끔한 코드를 유지할 수 있기 때문이다.
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에 대한 예외 처리를 하지 않아도 되기 때문에 코드의 가독성이 좋아진다.