thumbnail

이미 한번의 면접에서
<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>

CommentCount 0
이전 댓글 보기
등록
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
TOP