이번 프로젝트는 대부분의 Process Layer Component에서 다양한 DB 접근하여 데이터를 조회하고, 조회된 DATA을 요구사항에 따라 조합하여 화면에 보여주는 형태의 프로세스 개발이 많은 프로젝트였다. 다양한 DB을 사용하면서, WAS에서의 Transaction은 One Phase Commit만 지원하도록 설정되어 있었다. 따라서 GLOBAL TRANSACTION을 안됨에 SERVICE/PROVIDER LOGIC 중간 중간에 Transaction을 분리해야만 했다. 우선 Transaction의 속성을 먼저 정리하고, 적용했던 Transaction 분리 했던 방법을 설명하려 한다.

Transaction 속성은 Transaction의 영역을 제어한다. 그림 1은 EJB Bean-1의 method-A가 TX1 Transaction을 가지고 실행 중, EJB Bean-2의 method-B을 호출하는 그림으로써, Bean-2의 methjod-B가 실행할 때, TX1 Transaction을 가지고 실행할 지?, 새로운 TX? Transaction을 가지고 실행할 지?, 또는 Transaction 없이 실행할지? 는 Transaction의 속성에 따라 Transaction의 영역이 달라짐을 보여주고 있다.

그림1. Transaction Scope[3]

Transaction Propagation에는 7가지(REQUIRED, REQUIRESNEW, MADATORY, NOTSUPPORTED, SUPPORTS, NEVER, and NESTED)을 가진다. 7가지 속성은 다음과 같다.

REQUIRED 속성[3]

Bean-2의 b-Method에 Transaction 속성을 REQUIRED로 설정하였다면, 그림2, 3과 같은 경우가 발생한다. 그림 2는 Bean-1의 A-method가 TX1 Transaction을 가지고 있는 경우, B-method 호출 시 B-method은 A-method의 TX1 Transaction을 가지고 수행한다.  따라서, A-method와 B-method는 항상 같이 Commit 또는 Roll-Back 된다. 예를 들어 설정하자면, A-method의 transaction을 REQUIRED/REQUIRESNEW로 하고, B-method의 transaction을 REQUIRED로 설정한 경우이다.

그림2. Bean-1의 A-method가 TX1 Transaction을 가진 경우

그림 3은 만약 Bean-1의 A-method가 Transaction을 가지고 있지 않다면, 컨테이너는 새로운 TX2 Transaction을 생성해서 Bean-2의 B-Method를 실행한다. 예를 들어 설정하자면, A-method의 transaction을 NOTSUPPORTED로 하고, B-method의 transaction을 REQUIRED로 설정한 경우이다.

그림 3. Bean-1의 A-method가 Transaction을 가지지 않는 경우

결론적으로, B-method는 항상 Transaction 속에서 실행된다. B-method를 호출하는 method의 transaction 존재여부에 따라 Transaction을 공유할 지, 새로 생성할지가 결정된다.

참고로, 컨테이너로부터 관리되는 Transaction을 가진 모든 Enterprise Bean Method는 암묵적으로 REQUIRED을 가지고 있다. 따라서 REQUIRED외 다른 Transaction을 사용할 것이 아니면 REQUIRED을 설정하지 않아도 된다.

REQUIRESNEW 속성[3]

Bean-2의 b-Method에 Transaction 속성을 REQUIRESNEW로 설정하였다면, 그림4, 5과 같은 경우가 발생한다. 그림 4는 Bean-1의 A-method가 TX1 Transaction을 가지고 실행 중, Bean-2의 B-method를 호출한 경우를 나타낸다. 예를 들어 설정하자면, A-method의 transaction을 REQUIRED/REQUIRESNEW로 하고, B-method의 transaction을 REQUIRESNEW로 설정한 경우이다.

그림 4. Bean-1의 A-method가 TX1 Transaction을 가진 경우

그림 4의 Transaction의 흐름은 아래 절차에 따른다.

  1. B-method 호출 시, A-method TX1 Transaction을 일시 중지한다.
  2. 컨테이너는 새로운 TX2 Transaction을 시작한다.
  3. 새로운 TX2 Transaction 속에서 B-Method를 호출한다.
  4. B-Method가 완료된 후, A-Method Transaction을 다시 시작한다.

그림 5는 A-method가 Transaction 없이 B-Method를 호출한 것을 나타낸다. A-method가 B-method를 호출하는 시점에, 컨테이너는 새로운 TX2 Transaction을 생성하여, 그 TX2 Transaction 속에서 B-method를 실행한다. 예를 들어 설정하자면, A-method의 transaction을 NOTSUPPORTED로 하고, B-method의 transaction을 REQUIRESNEW로 설정한 경우이다.

그림 5. Bean-1의 A-method가 Transaction을 가지지 않는 경우

결론적으로, method의 Transaction 속성을 REQUIRESNEW로 설정하면, 항상 새로운 Transaction 속에서 실행된다.

참고로,  A-method(호출자)가 Transaction이 없고, B-method(호출받는 자)의 Transaction 속성을 REQUIRED/REQUIRESNEW로 설정하면 동일한 결과를 보인다(그림 3, 그림 5).

MADATORY 속성[3]

Bean-2의 b-Method에 Transaction 속성을 MADATORY로 설정하였다면, 그림6, 7과 같은 경우가 발생한다. 그림 6은 A-method가 Transaction을 가지고 B-method을 호출하는 경우를 나타내며, B-method 호출 시, B-method은 A-method의 TX1 Transaction을 가지고 수행한다. 예를 들어 설정하자면, A-method의 transaction을 REQUIRED/REQUIRESNEW로 하고, B-method의 transaction을 MADATORY로 설정한 경우이다.

그림 6. Bean-1의 A-method가 TX1 Transaction을 가진 경우

그림 7은 A-method가 Transaction 없이, B-method을 호출한 경우를 나타낸다. B-method 호출 시, 컨테이너는 A-method에게 TransactionRequiredException을 던진다. 예를 들어 설정하자면, A-method의 transaction을 NOTSUPPORTED로 하고, B-method의 transaction을 MADATORY로 설정한 경우이다.

그림 7. Bean-1의 A-method가 Transaction을 가지지 않는 경우

이 MADATORY 속성은 호출하는 A-method가 반드시 Transaction을 가지고 수행해야 할 때, 사용한다. 다시말해, B-method가 독립적으로 Transaction을 진행하면 안 되는 경우 사용한다.  예를 들어,  A-method가 계좌이체 서비스이고, B-method는 출금 서비스, C-method 입금 서비스라고 가정해 보자.  A-method는 B-method를 호출한 후, C-method를 호출할 것이다. B, C-method를 MADATORY로 설정하면, A-method는 Transaction을 가질 수 밖에 없게되고, A, B, and C-method는 하나의 Transaction으로 묶이게 된다. 누군가는 A,B, and C-method를 REQUIRED로 설정한다면, 이전과 같은 결과를 가지지 않느냐라고 반문할 수 있을 것이다(동일한 결과를 얻음으로). 그러나 만약 누군가가 실수로 A- method를 REQUIRED에서 NOTSUPPORTED로 변경하였다면, 오류가 발생해도 찾아 내기 쉽지 않을 것이다. 따라서, 호출자와 Transaction을 반드시 묶어야 할 때는 명시적으로 사용하는 것이 낫다고 생각한다.

NOTSUPPORTED 속성[3]

Bean-2의 b-Method에 Transaction 속성을 NOTSUPPORTED로 설정하였다면, 그림8, 9과 같은 경우가 발생한다. 그림 8은 A-method TX1 Transaction을 가지고, B-method을 호출한 경우를 나타낸다. B-method 호출 시, 컨테이너는 B-Method를 호출하기 전에 A-Method의 TX1 Transaction을 일시 중지하고, B-Method가 완료된 후, 컨테이너는 A-Method의 TX1 Transaction을 다시 시작한다. 예를 들어 설정하자면, A-method의 transaction을 REQUIRED로 하고, B-method의 transaction을 NOTSUPPORTED로 설정한 경우이다.

그림 8. Bean-1의 A-method가 TX1 Transaction을 가진 경우

그림 9는 A-method가 Transaction 없이 B-method을 호출하는 것을 나타낸다. B-method 호출 시점에, 컨테이너는 B-Method을 진행하기 전까지 새로운 Transaction을 시작하지 않는다. 예를 들어 설정하자면, A-method의 transaction을 NOTSUPPORTED로 하고, B-method의 transaction을 NOTSUPPORTED로 설정한 경우이다.

그림 9. Bean-1의 A-method가 Transaction을 가지지 않는 경우

이 NOTSUPPORTED 속성은 Transaction이 필요하지 않는 Method을 위해 사용한다. Transaction은 항상 overhead가 따르기 때문에, 이 속성을 사용하면 performance을 개선할 수 있다.

SUPPORTS 속성[3]

Bean-2의 b-Method에 Transaction 속성을 SUPPORTS로 설정하였다면, 그림10, 11과 같은 경우가 발생한다. 그림 10은 A-method가 TX1 Transaction을 가지고, B-method을 호출하는 것을 나타낸다. B-method를 호출 시, A-method의 TX1 Transaction을 가지고 수행한다. 예를 들어 설정하자면, A-method의 transaction을 REQUIRED로 하고, B-method의 transaction을 SUPPORTS로 설정한 경우이다.

그림 10. Bean-1의 A-method가 TX1 Transaction을 가진 경우

그림 11은 A-method가 Transaction 없이 B-method을 호출하는 것을 나타낸다. B-method 호출 시, 이 B-Method는 Transaction 없이 실행한다. 컨테이너는 B-Method를 진행하기 전까지 새로운 Transaction을 시작하지 않는다. 예를 들어 설정하자면, A-method의 transaction을 NOTSUPPORTED로 하고, B-method의 transaction을 SUPPORTS로 설정한 경우이다.

그림 11. Bean-1의 A-method가 Transaction을 가지지 않는 경우

다시 말해, B-method의 Transaction은 A-method의 Transaction 속성을 따라간다. Method의 Transaction 행위가 다양함에 따라 이 SUPPORTS 속성은 주의해서 사용해야 한다.

NEVER 속성[3]

Bean-2의 b-Method에 Transaction 속성을 NEVER로 설정하였다면, 그림12, 13과 같은 경우가 발생한다. 그림 12는 A-method가 TX1 Transaction을 가지고 B-method을 호출하는 것은 나타낸다. B-method 호출 시, 컨테이너는 RemoteExcepiton 발생한다. 예를 들어 설정하자면, A-method의 transaction을 REQUIRED로 하고, B-method의 transaction을 NEVER로 설정한 경우이다.

그림 12. Bean-1의 A-method가 TX1 Transaction을 가진 경우

그림 13은 A-Method가 Transaction 없이, B-Method을 호출하는 것을 나타낸다. B-method 호출 시, 컨테이너는 B-Method을 진행하기 전까지 새로운 Transaction을 시작하지 않는다. 예를 들어 설정하자면, A-method의 transaction을 NOTSUPPORTED로 하고, B-method의 transaction을 NEVER로 설정한 경우이다.

그림 13. Bean-1의 A-method가 Transaction을 가지지 않는 경우

Manatory와 반대로 Transaction이 없이 실행해야 한다.

NESTED 속성[6, 7]

이 속성은 중접된 Transaction을 지원하는 WAS에서만 지원되는 Transaction 속성이다.
Bean-2의 b-Method에 Transaction 속성을 NESTED로 설정하였다면, 그림14, 15과 같은 경우가 발생한다. 그림 14는 A-method가 TX1 Transaction을 가지고, B-method을 나타낸다. 이 경우 컨테이너는 A-method의 TX1 Transaction 내의 nested transaction형태로 TX1’ transaction을 만들어 B-Method을 실행한다. B-Method에서 발생한 변경사항이 commit이 되기 전까지는 A-method의 TX1 Transaction에서 보이지 않는다. 또한 TX1’ transaction은 자체적으로 commit, rollback이 가능하다

그림 14. Bean-1의 A-method가 TX1 Transaction을 가진 경우

A-method의 TX1 Transaction의 상태는 B-method에게 영향을 주고, B-method의 TX1’ Transaction의 상태는 A-method TX1 Transaction에게 영향을 주지 않는다[7].

그림 15는 A-method가 Transaction 없이 B-method을 호출하는 것을 나타낸다. 이 경우 컨테이너는 B-method을 REQUIRED 속성으로 실행한다.

그림 15. Bean-1의 A-method가 Transaction을 가지지 않는 경우

NESTED 속성은 WAS에 따라 지원여부가 결정된다.

  1. 지원하지 않는 WAS는 WebLogic, JEUS, and,  Java EE 5 등 이다. WebLogic Server implements the flat transaction model. Nested transactions are not supported[1]. The Enterprise JavaBeans architecture supports flat transactions. A flat transaction cannot have any child (nested) transactions[2].
  2. 지원하는 WAS는 ODBC, OLE DB, and, SQL Server 등이다. Neither Open Database Connectivity (ODBC), nor Microsoft OLE DB Provider, supports Nested Transactions [4, 5].

Transaction 적용 예

그림 16은 한 Process에서 영업, 인사, 고객, 계약 등 정보를 조회하여 데이터를 조합하는 보여주는 시나리오이고, 여기의 계약 DB, 인사 DB, 고객 DB, 영업 DB가 별도로 존재한다고 가정한다.

그림 16. Transaction 분리 예

이 시나리오는 특정 계약에 가입한 고객이 다른 어떤한 종류의 계약을 가입했는 지 조회하여 목록으로 보여주는 것이다. 이 시나리오를 수행하는 흐름은 1) 로그인 사용자 정보를 기반으로 권한을 체크하고, 2) 계약번호로 계약 정보를 조회한 후, 3) 계약의 고객 번호로 고객 정보를 조회 하고, 4) 이 고객이 가지고 있는 다른 계약을 모두 조회, 5) 영업한 사람이 누구인지 조회하여 데이터를 조회하여 보여준다.

Transaction의 분리 고려사항

필자는 Transaction 분리하기 위해  4가지를 고려하였다.

  1. 이 기능은 주요 목적은 무엇인가?
  2. 어떤 Service를 어느 Transaction에 묶을 것인가?
  3. Transaction을 분리하는 횟수에 따라 overhead는 가중된다.
  4. Overhead는 성능에 영향을 준다.

위 고려 사항에 따라서 그림 16의 시나리오에서는 아래와 같이 설정하였다.

  1. 이 기능은 계약을 조회하는 목적이다.
  2. 주 목적에 따라, A-method의 TX1의 Transaction에 계약 조회 서비스(C, E - methods)를 Requried로써 하나의 Transaction으로 처리하였다.
  3. 인사, 고객, 영업은 이 기능의 주요 목적의 sub 정보임에 따라, RequiresNew로 Transaction을 나누었다.

더군다나 같은 계약 DB를 한 Transaction으로 묶어서, Transation의 Overhead도 약간이나마 줄일 수 있다.

여기에서 RequiresNew 대신에 NotSupported로 설정을 하여도 실행은 될 것이다. 단지 실행하는 method가 Transation을 가지고 실행할 것인가? 아닌가? 하는 선택의 문제이다. 오히려 NotSupported으로 설정하였을 때, Transaction Overhead가 줄어들어 성능을 개선시켜주는 효과를 줄 것이다(단. 한, 두번 테스트가 아닌 스트레스 테스트를 한 경우). 필자는 이 프로젝트가 금융과 관련되어있어 안정성을 위해서 NotSupported보다 RequiresNew를 사용하였다.

Reference

http://docs.oracle.com/cd/E23943_01/web.1111/e13731/trxsvc.htm#i1054388
http://download.oracle.com/otn-pub/jcp/ejb-3_0-fr-eval-oth-JSpec/ejb-3_0-fr-spec-ejbcore.pdf?AuthParam=1386778361_5a64f7580be6d01251ff147c957effbc pp.316
http://docs.oracle.com/javaee/5/tutorial/doc/bncij.html#bncim
http://support.microsoft.com/kb/177138/ko
http://technet.microsoft.com/ko-kr/library/ms189336(v=sql.105).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/ms716985(v=vs.85).aspx
http://docs.oracle.com/cd/E17276_01/html/gsg_xml_txn/java/nestedtxn.html

부록

1. EJB Transaction 설정

Weblogic에서 Transaction의 속성을 weblogic-ejb-jar.xml에 Method 단위로 정의하게 된다.

<transaction-isolation>
<isolation-level>...</isolation-level>
<method>
<description>...</description>
<ejb-name>...</ejb-name>
<method-intf>...</method-intf>
<method-name>...</method-name>
<method-params>...</method-params>
</method>
</transaction-isolation>

2.  Transaction 속성 요약

Transaction 속성 A Transaction B Transaction
Required None TX2
TX1 TX1
RequiresNew None TX2
TX1 TX2
Mandatory None Error
TX1 TX1
NotSupported None None
TX1 None
Supports None None
TX1 TX1
Never None None
TX1 Error
Nested None TX1
TX1 TX1’