Spring, Transaction 처리과정
이미 한번의 면접에서
<br/>
"Spring 트랜잭션이 어떻게 동작하는지 아시나요?" 라는 질문을 받았습니다.
<br/>
답변을 못하였으니, 공부를 해야합니다.
<br/>
답변은 "Spring은 트랜잭션은 AOP Proxy 통해 처리됩니다. " 입니다
<br/>
트랜잭션 설정 전에는 메소드 호출에, 다음과 같이 동작합니다.
<br/>
<img alt="pasteImage.png" pathname="46M10S190222203457.PNG" src="https://static.podo-dev.com/blogs/images/2019/07/10/origin/46M10S190222203457.PNG" style="border-style:solid; border-width:1px; width:394px" title="pasteImage.png">
<br/>
@Service
public class AccountServiceImpl implements AccountService {
//…
<br/>
//Not specifying a transaction policy here!
public void create(Account account) {
entityManager.persist(account);
}
}
<br/>
하지만 트랜잭션을 설정하였다면, 다음과 같이 프록시 클래스가 Impl 클래스를 감싸게 됩니다.
<br/>
<img alt="pasteImage.png" pathname="Q3NZGK190222204715.PNG" src="https://static.podo-dev.com/blogs/images/2019/07/10/origin/Q3NZGK190222204715.PNG" style="border-style:solid; border-width:1px; width:316px" title="pasteImage.png">
<br/>
그리고 다음과 같이 동작합니다.
<br/>
- Caller에서 proxy로 create메소드 호출
- proxy에서 transaction 시작( autoCommit(false) )
- proxy에서 accountServiceImpl로 create 메소드 호출
- proxy에서 transaction 후 처리 ( commit 또는 rollback)
<br/>
<br/>
지금 까지의 내용은 JDK Dynamic Proxy 경우 입니다.
<br/>
Spring은 JDK Dynamic Proxy, CGLIB 두 가지 프록시 타입을 사용합니다.
<br/>
인터페이스를 구현한 타겟에서는 JDK Dynamic Proxy를, 인터페이스를 구현하지 않은 타겟은 CGLIB를 사용합니다.
<br/>
설정을 통해 모두 CGLIB 프록시를 사용 할 수도 있습니다.
<br/>
CGLIB에 따라 약간의 처리가 달라집니다. 다음 그림은 CGLIB에 따른 Proxy 동작 과정입니다.
<br/>
인터페이스가 없기때문에, 타겟 클래스를 상속받아 Proxy를 정의하고, super 메소드를 호출함으로써 핵심기능을 수행합니다.
<br/>
<img alt="pasteImage.png" pathname="I8ECVM190222205849.PNG" src="https://static.podo-dev.com/blogs/images/2019/07/10/origin/I8ECVM190222205849.PNG" style="border-style:solid; border-width:1px; width:417px" title="pasteImage.png">
<br/>
<br/>그럼 Spring이 AOP Proxy를 통해서 동작하는 과정을 이해하였으니, 어떻게 구현해야하는지 알아야합니다. 매우 간단합니다.
<br/>
<br/>
<br/>
<br/>XML 설정 파일에서 다음과 같이 설정합니다.
<br/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceSpied" data-tomark-pass data-tomark-pass data-tomark-pass data-tomark-pass data-tomark-pass data-tomark-pass data-tomark-pass data-tomark-pass data-tomark-pass data-tomark-pass data-tomark-pass data-tomark-pass />
</bean>
<tx:annotation-driven /></code>
<br/>
그리고 트랜잭션이 필요한 메소드 위에 @Transactional 어노테이션을 추가하면 됩니다.
<br/>
@Transactional
public BlogVo doView(Set<Integer> isVisitBlogs, int seq) {
//...
}
<br/>
또한 클래스 위에 @Transactional을 설정한다면 해당 클래스의 모든 메소드에 대해서 적용됩니다.
<br/>
@Transactional
public class A{
//...
}
<br/>
MVC구조에 의하여, @Transactional 어노테이션은 Service 단에서 추가하는 것이 권장됩니다.
트랜잭션은 여러 CRUD 작업을 하나의 동작으로 하기 위함이고, 비즈니스 로직을 처리하는 Service단에서 트랜잭션을 수행하는 것이 권장됩니다.
<br/>
"이상적으로, 서비스 계층 (Manager)은 비즈니스 로직을 나타내므로 @Transactional로 주석 처리되어야합니다.서비스 계층은 다른 DAO를 호출하여 DB 작업을 수행 할 수 있습니다. 서비스 메소드에 3 가지 DAO 연산이있는 상황을 가정합니다. 첫 번째 DAO 작업이 실패하면 다른 두 작업이 계속 전달 될 수 있으며 일관성없는 DB 상태가 종료됩니다. Annotating Service 레이어는 이러한 상황에서 당신을 구할 수 있습니다."
<br/>
출처 : <a href="https://stackoverflow.com/questions/3886909/where-should-transactional-be-place-service-layer-or-dao">https://stackoverflow.com/questions/3886909/where-should-transactional-be-place-service-layer-or-dao</a>
<br/>
<br/>
@Transactional 어노테이션에 여러 옵션값들을 가지고 있습니다.
<br/>
<br/>
<br/>
isolation (격리레벨)
앞서 공부한, 여러 트랜잭션 동시처리로 인한 문제 발생에, 트랜잭션별 격리레벨을 다음과 같이 설정 할 수 있습니다.
<br/>
설정 예: @Transactional(isolation=Isolation.DEFAULT)
<br/>
- DEFAULT: DB 설정, 기본 격리 수준(기본설정)
- SERIALIZABLE : 가장 높은 격리, 성능 저하의 우려가 있음
- READ_UNCOMMITED : 커밋되지 않는 데이터에 대한 읽기를 허용
- READ_COMMITED : 커밋된 데이터에 대해 읽기 허용
- REPEATEABLE_READ : 동일 필드에 대해 다중 접근 시 모두 동일한 결과를 보장
<br/>
MySQL에서 트랜잭션 격리레벨은 다음 SQL문을 통해 확인 할 수 있습니다.
<br/>
SHOW VARIABLES LIKE 'tx_isolation';
<br/>
<br/>
<br/>propagation (전파속성)
트랜잭션 전파속성에 대한 처리입니다.
<br/>
예를 들어 현재 트랙잭션이 설정되어있는 A메소드가 호출 되었을때,
A메소드를 호출한 B메소드가 이미 트랜잭션을 가지고 있으면 어떻게 처리 할 것인가 대한 옵션값입니다.
<br/>
설정 예: @Transactional(propagation=Propagation.REQUIRED)
<br/>
- PROPAGATION_MANDATORY : 작업은 반드시 특정한 트랜잭션이 존재한 상태에서만 가능
- PROPAGATION_NESTED : 기존에 트랜잭션이 있는 경우, 포함되어서 실행
- PROPAGATION_NEVER : 트랜잭션 상황에 실행되면 예외 발생
- PROPAGATION_NOT_SUPPORTED : 트랜잭션이 있는 경우에는 트랜잭션이 끝날 때까지 보류된 후 실행
- <b>PROPAGATION_REQUIRED : 트랜젝션이 있으면 그 상황에서 실행, 없으면 새로운 트랜잭션 실행(기본설정)</b>
- PROPAGATION_REQUIRED_NEW : 대상은 자신만의 고유한 트랜잭션으로 실행
- PROPAGATION_SUPPORTS : 트랜젝션을 필요료 하지 않으나, 트랜잭션 상황에 있다면 포함되어서 실행
<br/>
! 우아한형제들 블로그에, 전파속성과 관련된 재밌는 글이 있습니다.
<br/>
<a href="http://woowabros.github.io/experience/2019/01/29/exception-in-transaction.html">http://woowabros.github.io/experience/2019/01/29/exception-in-transaction.html</a>
<br/>
<br/>readOnly 속성
<br/>
설정 예: @Transactional(readOnly = true)
<br/>
true인 경우 insert, update, delete 실행 시 예외 발생, 기본 설정은 false
<br/>
<br/>rollbackFor 속성
<br/>
트랜잭션의 롤백은 Error, Unchecked Exception에 대해서만 롤백 처리됩니다.
따라서 CheckedException에 대해서 롤백을 수행할려면, rollbackFor 옵션을 이용해야 합니다.
설정 예: @Transactional(rollbackFor=Exception.class)
<br/> - 특정 예외가 발생 시 강제로 Rollback
<br/>
<br/>
<br/>noRollbackFor 속성
<br/>
설정 예: @Transactional(noRollbackFor=Exception.class)
<br/> - 특정 예외의 발생 시 Rollback 처리되지 않음
<br/>
<br/>
<br/>timeout 속성
<br/>
설정 예: @Transactional(timeout=10)
<br/> - 지정한 시간 내에 해당 메소드 수행이 완료되이 않은 경우 rollback 수행. -1일 경우 no timeout(Default=-1)
<br/>
참고
<br/>
<a href="https://spring.io/blog/2012/05/23/transactions-caching-and-aop-understanding-proxy-usage-in-spring">https://spring.io/blog/2012/05/23/transactions-caching-and-aop-understanding-proxy-usage-in-spring</a>
<br/>
<a href="http://egloos.zum.com/aretias/v/708477">http://egloos.zum.com/aretias/v/708477</a>
<a href="https://mozi.tistory.com/201">https://mozi.tistory.com/201</a>