먼저 아래와 같은 두 개의 서비스가 있다.
ExceptionService의 throwException() 메소드는 NullPointerException을 발생시키는 메소드이다.
UserService의 addUser 메소드는 ExceptionService.throwException()을 호출하고 user를 add하는 메소드이다.
다만, exception을 처리하기 위해 try-catch 문으로 해당 exception을 처리한 상태이다.
아래와 같은 상황에서 userService.addUser()를 호출하면 어떻게 될까?
- UserServiceImpl.java
@Service @Transactional @Slf4j public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Autowired private ExceptionService exceptionService; @Override public User addUser(String name) { try { exceptionService.ThrowException(); } catch (Exception e) { log.info("Exception occured"); } return userRepository.save(new User(name)); } }
- ExceptionServiceImpl.java
@Transactional @Service public class ExceptionServiceImpl implements ExceptionService { @Override public void ThrowException() throws Exception { throw new NullPointerException(); } }
아래와 같이 장황한 RollbackException이 발생하게 된다.
왜 이런 상황이 발생되는 지 순서대로 알아보자.
- Output
javax.persistence.RollbackException: Transaction marked as rollbackOnly at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:58) ~[hibernate-entitymanager-5.0.9.Final.jar:5.0.9.Final] at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517) ~[spring-orm-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761) ~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730) ~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:483) ~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:290) ~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655) ~[spring-aop-4.3.2.RELEASE.jar:4.3.2.RELEASE] at org.blog.test.user.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$dcb2eb86.addUser() ~[main/:na] at org.blog.test.user.controller.UserController.addUser(UserController.java:18) ~[main/:na]
우리는 두 서비스 모두 @transactional 이 선언되어 있다는 것에 주목할 필요가 있다.
실제 transaction의 흐름을 살펴보자~!!
1. userService.addUser() 가 호출되면서, transaction A가 시작됨
2. 해당 메소드 내의 exceptionService.throwException() 메소드가 호출됨
3. throwException 메소드 역시 @transactional이 선언되어 있기 때문에, 현재 transaction을 확인
4. @transactional의 기본값은 Propagation.REQUIRED 이기 때문에, 기존에 실행된 transaction A를 그대로 사용
- public static final Propagation REQUIRED
Support a current transaction, create a new one if none exists. Analogous to EJB transaction attribute of the same name.
This is the default setting of a transaction annotation.
5. NullPointerException이 발생하면서, 해당 transaction A를 rollbackOnly로 설정 후, userService.addUser()로 돌아감
6. userRepository.save(new User(name)); 가 실행됨.
7. userService.addUser()에서는 해당 NullPointerException이 try-catch 문으로 처리가 되었기 때문에 transaction을 commit 시도.
8. transaction이 rollbackOnly로 설정되었기 때문에 commit 실패하면서, RollbackException 발생
이에 대한 해결 방법은 여러가지가 있다.
1. ExceptionService 를 async로 실행
2. ExceptionService 의 @transactional 제거
3. ExceptionService 의 transaction 의 propagation 변경
그 중 3번에 대해 알아보면,
아래와 같이 transaction의 propagation을 REQUIRES_NEW로 바꾸면 새로운 transaction을 시작하기 때문에,
위와 같은 에러가 발생하지는 않는다.
하지만, 이 방법이 전체 method의 flow에 적합한지 검토 후, 적용하는 것이 좋을 것 같다.
@Transactional(propagation = Propagation.REQUIRES_NEW) @Service public class ExceptionServiceImpl implements ExceptionService {
- Output
2017-04-08 20:13:42.193 INFO 13552 --- [nio-8080-exec-8] o.b.t.user.service.impl.UserServiceImpl : Exception occured 2017-04-08 20:13:42.198 INFO 13552 --- [nio-8080-exec-8] jdbc.sqltiming : insert into user (name) values ('femeo') {executed in 2 msec}
댓글 없음 :
댓글 쓰기