태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

 

정리가 힘들었던 포스트였다. AOP에 대한 부분을 연수 전에 작성한 것이라 리마인드를 위해서 그것까지 다시 읽어야했고 읽다 보니 그곳에서도 수정해야 할 부분도 있었다. 어떻게 정리해야 하나 고민이 많았던 부분인데 쌈박하지는 못한 것 같다. 토요일부터 시작해서 오늘에야 끝난다.

 

이 포스트에서는 Spring이 어떻게 트랜잭션 기능을 제공하는지를 이해할 것이다. 그리고 트랜잭션 기능을 이용하기 위해서 어떻게 설정하는지도 알아본다. “트랜잭션 관리자”, “트랜잭션용 AOP 프락시”, “트랜잭션 어드바이저”, “어드바이스 즉 인터셉터”, “포인트컷”같은 용어를 이해할 필요가 있을 것이다. “AOP 프락시”, “어드바이저”, “어드바이스”, “포인트컷”등은 Spring의 트랜잭션용 용어가 아니라 AOP 용어이다. 지난 포스트를 참조할 수 있다 ( AOP 프로그래밍 개념 I, AOP 프로그램 개념 II )

 

  • 트랜잭션 관리자

 

앞 포스트에서 말한 대로 Spring에서는 트랜잭션을 구현하는 코드를 제공하지는 않는다. 다만 기존의 트랜잭션 관리자들에서 시스템 환경에 적절한 것을 사용할 수 있도록 strategy 패턴을 이용해서 추상화했을 분이다. Spring에서는 다음과 같은 전략 인터페이스를 정의하고 있다.

public interface IPlatformTransactionManager

{

    ITransactionStatus GetTransaction(ITransactionDefinition definition);

    void Commit(ITransactionStatus transactionStatus);

    void Rollback(ITransactionStatus transactionStatus);

}

이 전략 인터페이스를 구체적으로 트랜잭션 전략의 구현체로는 다음과 같은 것을 제공하고 있다.

AdoPlatformTransactionManager

ADO.NET기반의 로컬 트랜잭션 제공

ServiceDomainPlatformTransactionManager

Enterprise Services기반의 분산 트랜잭션 제공

TxScopePlatformTransactionManager

System.Transactions 사용한 로컬 / 분산 트랜잭션 관리자

HibernatePlatformTransactionManager

NHiberate기반 또는 ADO.NET&NHibernate를 함께 사용해서 로컬 트랜잭션 제공

 

이 트랜잭션 처리 방법들은 각각의 특성이 있다. Spring.NET 문서 17.2절을 보면 잘 요약되어 있다. : 각 트랜잭션 관리자와 관련해서 로컬 트랜잭션, 글로벌 트랜잭션의 지원 여부 및 관리자의 특성등을 살펴볼 필요가 있겠다.  TxScopePlatformTransactionManager가 가장 유연할 것으로 보인다.

여튼 이런 트랜잭션 관리자는 외부에서 제공( configuration 또는 프로그램적으로 제공)되는 정보를 이용해서 적절한 트랜잭션을 생성, 관리하는 역할을 할 것이다.

트랜잭션을 개념적으로 설명할때는 어떤 “영역(바운더리) 또는 공간”으로 표현한다.  “하나의 트랜잭션 공간안에서 실행되는 코드로부터 영향을 받는 리소스( 데이터베이스 데이터)는 모두가 커밋되거나 또는 모두가 롤백된다”는 식으로 표현한다. 상당히 개념적인 표현이다. 트랜잭션에 참여하는 객체들에는 트랜잭션 성격을 정의하는 하나의 객체들이 달라붙어 있다. 객체와 트랜잭션 객체가 쌍으로 존재한다는 의미이다. 이 트랜잭션 객체의 속성이 같은 객체들은 동일한 트랜잭션 공간에 존재한다고 볼 수 있다. 만약 그 공간이 제공하는 트랜잭션 옵션들이 마음에 안들면 객체는 다른 트랜잭션 옵션을 갖는 객체를 만들어서 새로운 공간을 만들 수도 있다.

여튼 트랜잭션 공간을 생성하기위해서는 트랜잭션을 성격을 결정할 수 있는 몇 가지 옵션들을 정의해줘야 한다는 것인데, 다음과 같은 옵션들이 있다.

Isolation값

Propagation값

Timeout 값

Read-only 여부

트랜잭션은 데이터의 ACID (Atomicity, Consistency, Isolation, Durability) 개념을 보장할 수 있어야 한다. 이런 개념들을 보장하기 위해서 이와 같은 값들의 설정이 필요하다. 이런 값을 어떻게 설정하느냐에 따라서 그 트랜잭션 공간이 보장할 수 있는 성격이 조금씩 변경될 수 있다는 것이다.

이것들을 설명할 기력이 없다. 구글링해보자. “acid, transaction”로 검색하면 안 나올라나? “acid”만으로 검색하면 “산성비”에 대한 결과가 나올 것도 같은데…^^.

여튼 트랜잭션 공간을 생성하기 위해서는 이런 값을 지정해줄 필요가 있는데, 앞의 인터페이스의 메소드 GetTransaction()의 인자로 넘겨주는 객체 ITransactionDefinition을 통해서 그것이 가능하다. 이런 트랜잭션 옵션을 지정하는 것을 “트랜잭션을 정의”하는 것으로 표현하고 있다.

ITransactionDefinition 객체를 GetTransaction()을 호출하면 하나의 트랜잭션 공간이 생긴다고 보면 된다. 이 메소드로부터 반환받는 객체 ITransactionStatus를 통해서 트랜잭션 작업을 수행할 수 있다. 달봉이가 얼른 생각할때는 이 반환되는 객체의 이름을 왜 ITransaction이라고 짓지 왜 끝에 Status를 붙여놨을까 했다. 달봉이도 모른다. 이름만으로 판단해보면 트랜잭션의 상태에 대한 정보도 가지고 있는듯한데, 매뉴얼을 보면 이 녀석이 하는 일은 실제로 트랜잭션에 대한 상태를 제공한다. 그리고 이 녀석을 통해서 트랜잭션을 실행시킬 수도 있단다. 생각해보면 이해도 갈 수 있는 부분이다. Spring 입장에서는 트랜잭션을 처리하는 로직을 구현하고 있는 것이 아니다. 그냥 필요한 옵션들을 받아서 트랜잭션을 직접 생성하고 트랜잭션 처리를 로우 레벨에서 구현한 것은 각각의 트랜잭션 전략 객체들이다. Spring은 녀석들에게 줄 것 주고 “트랜잭션 시작하세요, 트랜잭션 현재 상태는 어때요? 좋아요? 그럼 커밋하세요” 라는 명령을 내리거나 상태를 조회하는 일만 하면 될 것 같다. Spring을 설계한 사람들이 잘 알아서 했겠는가? 우선은 그들의 설계를 이해하는 차원에서 공부하자.

그러나 사실 Spring 프레임워크를 사용하면 앞의 인터페이스의 메소드를 개발자가 직접 호출할 일은 거의 없을 것이다. 이런 API보다는 트랜잭션 관리자의 개념적인 의미에 집중하도록 하자.

기업에서 업무를 구현하는 개발자들을 위한 최종 개발 프레임워크라면 이런 트랜잭션 처리 API에 접근할 필요가 없도록 지원해줘야 한다. 물론 최종 트랜잭션에서 정의(ITransactionDefinition)한 것이 적합하지 않은 특별한 업무가 발생하는 경우 프로그램적으로 새로운 트랜잭션을 생성해야 한다면 직접 개발자가 이런 API를 호출할 수도 있겠다.

트랜잭션 관리자는 내부적으로 트랜잭션을 생성, 관리한다고 했다. 그러나 구체적으로 어떤 관리자를 사용해서 트랜잭션을 관리할 지는 설정을 해 줘야 한다.

 

  • 사용할 트랜잭션 관리자 설정

 

<objects xmlns='http://www.springframework.net'

xmlns:db="http://www.springframework.net/database">

  <db:provider id="DbProvider"

               provider="SqlServer-1.1"

               connectionString="Data Source=(local);Database=Spring;...">

  </db:provider>

  <object id="TransactionManager"

          type="Spring.Data.AdoPlatformTransactionManager, Spring.Data">

    <property name="DbProvider" ref="DbProvider"/>

  </object>

  . . . other object definitions . . .

</objects>

 

<object/>를 이용해서 일반 객체를 설정하듯 하면 된다. 이 설정은 AdoPlatformTransactionManager를 사용하겠다는 것이고 DbProvider 속성을 이용해서 데이터베이스 연결 설정을 함께 하고 있다.

참고로 앞의 설정에서 트랜잭션 관리자(AdoPlatformTransactionManager)를 정의할때 DbProvider 속성이 있는 것에 주목해보자.
데이터베이스 연결을 트랜잭션 관리자가 관리하고 있다는 이 사실은 중요한 부분이다.
트랜잭션 관리 기술중에서 TransactionScope을 사용하는 가장 큰 이유를 알고 있을 것이다. 그런가?
하나의 트랜잭션 내부에서 접근하는 데이터베이스 서버의 수가 한대인 경우 로컬 트랜잭션으로 작업을 하다가 만약 2대 이상의 데이터베이스 작업을 하게 되면 자동으로 전역 트랜잭션( 분산 트랜잭션)으로 자동 전환( promotion)된다는 이점이 있다.
로컬 트랜잭션으로 작업을 하는 것이 당연 성능 측면에서 유리하다.
Enterprise Services의 MS-DTC를 이용하게 되면 모든 트랜잭션을 전역으로 수행한다. 그리고 ADO.NET은 로컬 트랜잭션만한 지원한다.
따라서 트랜잭션 관리에는 TransactionScope을 주로 이용하게 되는데 이 녀석의 단점이 있다. MS-SQL 서버로만 구성된 분산 환경에서라면 이 기능을 사용하는데 문제가 없다. 그러나 오라클 데이터베이스가 포함된 분산 환경에서는 전역 트랜잭션으로의 자동 전환( promotion)이 일어나지 않는다.
이곳에서 말하고 싶은 이것이 아니다. 더 중요한 단점이 있다. 데이터베이스 서버가 한대인 경우에도 전역 트랜잭션으로 자동전환되는 경우가 있다. 하나의 트랜잭션에서 첫번째 연결이 열리는 순간까지는 로컬 트랜잭션으로 작업을 하다가, 동일한 데이터베이스로의 연결이 두번째 열리는 경우 전역 트랜잭션으로 자동전환되어 버린다. 설령 데이터베이스 연결 문자열이 동일하다 해도, 두번째 데이터베이스 연결이 열리는 순간 트랜잭션 관리자는 분산 환경으로 판단한다는 것이다.
그렇다면 하나의 트랜잭션 안에서 하나의 데이터베이스에 작업을 할때는 동일한 데이터베이스 연결을 사용하면 되는 것이다. 흔히들 DAO단에서 연결 정보를 관리하는 구조에서는 이 단점을 피해갈 수 없다. 해서 Spring.NET에서는 트랜잭션 관리자에게 연결 정보를 주고 DAO( DAC, DSL이라고도 흔히 부른다)단에서 데이터베이스에 연결할때는 이 녀석을 사용하도록 하고 있다.
위의 트랜잭션 관리자(AdoPlatformTransactionManager) 설정에 DbProvider 속성이 있다는 것이 이런 이유에서 합리적이라고 할 수 있겠다.
 

  • 트랜잭션용 AOP 프락시

 

AOP 프락시에 대해서는 이전 포스트에서 설명했다. Spring의 트랜잭션용 프락시가 어떤 위치에 있는지 개념적으로 다시 한번 더 보자. Spring.NET 레퍼런스 문서 17.5.1에 나와 있는 그림이다.

그림을 보면 클라이언트 코드는 직접 타겟 객체를 참조하지 않고 트랜잭션용 AOP 프락시를 참조한다. 트랜잭션용 AOP 프락시는 클라이언트의 호출을 받으면 바로 타겟 객체의 메소드를 호출하지 않는다. 트랜잭션용 어드바이저를 호출한다. 이 녀석은 트랜잭션 작업을 수행하는데, 트랜잭션이 생성되어 있지 않다면 새로운 트랜잭션을 생성하거나 또는 기존의 트랜잭션 공간에 참여하는 작업을 하게 되는 것이다. 커스텀 어드바이저가 또 있다면 그 녀석들의 작업을 차례로 수행하고 나서 마지막으로 타겟 객체의 메소드를 호출하게 된다.

타겟 메소드가 리턴을 하게 되면 그 결과는 다시 호출순과 반대순으로 차례로 전달된다. 이때 트랜잭션용 어드바이저는 해당 호출에 대해 예외가 발생하지 않고 호출이 성공을 하게 되면 해당 트랜잭션을 커밋하게 된다.

이런 트랜잭션용 AOP 프락시를 생성하는 방법을 Spring.NET에서는 여러가지를 제공한다.

 

  • Spring의 트랜잭션용 프락시 객체 생성 방법

 

Spring.NET에서 제공하는 트랜잭션용 프락시 객체를 생성하는 방법에는 몇 가지가 있지만 이곳에서는 기업용 애플리케이션에 적용하기 적합한 2가지만 언급하겠다.  다른 방법에 대해서는 Spring.NET 레퍼런스 문서를 참고하길 바란다.

.ProxyFactoryObject  :

이 녀석을 사용해서 타겟 객체에 대한 프락시 객체를 얻는 방법은 이전의 포스트에서 설명한 적 있다. 이 녀석을 이용하기 위해서는 타겟 객체에 대한 참조 그리고 트랜잭션용 어드바이스( 인터셉터 )에 대한 참조를  넘겨줘야 한다.

그러나 이 녀석보다는 기업용 애플리케이션에 적합한 것은 다음 녀석이다.

.DefaultAdvisorAutoProxyCreator :

이 녀석은 TransactionAttribute와 함께 사용해서 트랜잭션용 프락시를 생성하고 싶은 메소드를 손쉽게 결정할 수 있다. 트랜잭션 커밋을 하기 위해서 [AutoComplete]같은 어트리뷰를 사용해 본적이 있을 것이다. 비슷하게 [Transaction]어트리뷰트를 사용해서 해당 메소드를 호출할때는 트랜잭션용 프락시가 자동으로 생성될 수 있도록 하는 방법이다.

public class UserMgmtDSL : ...

{

    [Transaction()]

    public void Save( ... )

    {

        //필요한 작업을 한다.

    }

}                              

이 녀석에게는 타겟 객체에 대한 참조나 트랜잭션용 어드바이스에 대한 참조를 명시적으로 건네주지 않아도 된다.  이게 무슨 말인지 ProxyFactoryObject를 사용하는 설정과 DefaultAdvisorAutoProxyCreator를 사용하기 위한 설정을 비교해 보도록 하자.

 

  • ProxyFactoryObject를 사용하기 위한 설정

 

<!--프락시 생성자-->

<object id="proxyCreator" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">

  <property name="Target" ref="bslObject"/>

  <property name="ProxyInterfaces">

    <value>Spring.Data.IBSLObject</value>

  </property>

  <property name="InterceptorNames">

    <value>transactionInterceptor</value>

  </property>

</object>

 

<!-- 트랜잭션용 어드바이스-->

<object id="transactionInterceptor" type="Spring.Transaction.Interceptor.TransactionInterceptor, Spring.Data">

  <property name="TransactionManager" ref="transactionManager"/>

  <!-- note do not have converter from string to this property type registered -->

  <property name="TransactionAttributeSource" ref="methodMapTransactionAttributeSource"/>

</object>

<!-- 포인트컷 및 트랜잭션 옵션-->

<object name="methodMapTransactionAttributeSource"

type="Spring.Transaction.Interceptor.MethodMapTransactionAttributeSource, Spring.Data">

  <property name="MethodMap">

    <dictionary>

      <entry key="Spring.Data.BSLObject.SaveTwoTestObjects, Spring.Data.Integration.Tests"

      value="PROPAGATION_REQUIRED"/>

      <entry key="Spring.Data.BSLObject.DeleteTwoTestObjects, Spring.Data.Integration.Tests"

      value="PROPAGATION_REQUIRED"/>

    </dictionary>

  </property>

</object>

 

<!--   BSL, DAO 객체 정의   -->

<object id="bslObject" type="Spring.Data.BSLObject, Spring.Data.Integration.Tests">

  <property name="DaoObject" ref="daoObject"/>

</object>

 

프락시 생성자 ProxyFactoryObject 를 설정하는 부분에서는 어떤 타겟 객체에 대해서 프락시를 생성해야 할지를 “Target” 속성을 통해서 설정해줘야 한다. 그리고 “InterceptorNames” 속성을 통해서 사용할 트랜잭션용 어드바이스도 설정해줘야 한다.

위 설정에서는 “bslObject”라는 이름으로 정의된 BSL단 객체에 대해서 프락시를 생성하겠다는 것을 보여주고 있다. 기업용 애플리케이션에서는 수많은 BSL단 객체들이 나올텐데 이것들을 프락시 생성자들에 모두 등록하는 것은 아니올시다이다. 그리안해도 BSL단의 객체들은 모두 위 설정의 아래처럼 configuration에 정의해야 한다. 맨 아래처럼 해야 하는 것은 Spring.NET의 IoC 기능이나 AOP 기능을 위해서는 어쩔 수 없는 것이라 해도 트랜잭션을 위해서 또 한번 객체마다 트랜잭션용 프락시에 등록작업을 해야 한다는 것은 중복작업이라는 기분을 피할 수 없다.

 

  • DefaultAdvisorAutoProxyCreator용 설정

 

Spring.NET은 TransactionAttribute를 인식할 수 있는 기능을 지원한다고 했으니 configuration에 정의된 객체중에서 [Transaction()] 어트리뷰트가 표시된 메소드들이 호출될때는 스스로 인식해서 트랜잭션용 AOP 프락시를 생성해서 반환해주는 기능이 있으면 좋을 것이다.

Spring.NET은 DefaultAdvisorAutoProxyCreator와 TransactionAttribute를 사용해서 그런 시나리오를 지원해준다는 것이다. 즉 DefaultAdvisorAutoProxyCreator를 사용하면 BSL단의 객체를 이 녀석에게 등록할 필요가 없다는 것이다. configuration에 정의된 모든 객체들중에서 TransactionAttribute가 표시된 객체들에 대해서 자동으로 트랜잭션용 AOP 프락시를 생성한다는 것이다. 또한 “InterceptorNames” 속성도 “transactionInterceptor”라고 설정하면 Spring.NET에서 기본적으로 제공되는 트랜잭션용 어드바이스를 사용하게 된다.

따라서 굳이 트랜잭션용 프락시나 어드바이스 설정을 해 주지 않아도 된다는 것이다. 그런 기본적인 구현을 사용하겠다는 표시를 해주면 된다. 다음 설정을 보자.

<!--기본 트랜잭션용 AOP 프락시 생성자 및 어드바이스 사용 -->

<tx:attribute-driven transaction-manager="transactionManager"/>

 

<!--   BSL, DAO 객체 정의   -->

<object id="bslObject"

        type="Spring.Data.BSLObject, Spring.Data.Integration.Tests">

  <property name="DaoObject" ref="daoObject"/>

</object>

<object id="daoObject" type="Spring.Data.DaoObject, Spring.Data.Integration.Tests">

  <property name="AdoTemplate" ref="adoTemplate"/>

</object>

트랜잭션용 AOP 프락시를 생성하는데 DefaultAdvisorAutorProxyCreator를 사용하고 기본적인 트랜잭션 어드바이스를 사용하겠다는 표시로 <tx:attribute-driven>을 추가하고 있다. that’s all !

이런 방식으로 트랜잭션용 프락시와 어드바이스 설정을 하기 위해서는 한 가지가 더 필요하다. 트랜잭션용 네임스페이스를 파싱할 수 있는 파서가 등록되어야 한다.

 

  • 트랜잭션용 네임스페이스 파서 등록

 

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <configSections>

    <sectionGroup name="spring">

      <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" />

      <!-- other spring config sections like context, typeAliases, etc not shown for brevity -->

    </sectionGroup>

  </configSections>

  <spring>

    <parsers>

      <!-- -->

      <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />

      <parser type="Spring.Transaction.Config.TxNamespaceParser, Spring.Data" />

      <parser type="Spring.Aop.Config.AopNamespaceParser, Spring.Aop" />

    </parsers>

  </spring>

  </configSections>

 

  • 트랜잭션용 최종 configuration 모습

 

달봉이는 기업용 애플리케이션에서 Spring.NET을 이용해서 비즈니스 서비스 레이어의 트랜잭션 처리를 해야 한다면 DefaultAdvisorAutoProxyCreator를 사용할 것이다. 트랜잭션 처리를 해야 하는 객체가 대규모로 있는 상황에서 반복되는 설정을 가장 피할 수 있는 방법으로 가장 적합한 녀석으로 판단된다.

이제 트랜잭션이 필요한 데이터베이스 프로그래밍을 할때 필요한 전체 설정들을 통합해보도록 하자. 기업 애플리케이션의 설정은 다음과 유사한 모습이 될 것이다.

 

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <configSections>

    <sectionGroup name="spring">

      <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" />

      <!-- other spring config sections like context, typeAliases, etc not shown for brevity -->

    </sectionGroup>

  </configSections>

  <spring>

    <parsers>

      <!-- -->

      <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />

      <parser type="Spring.Transaction.Config.TxNamespaceParser, Spring.Data" />

      <parser type="Spring.Aop.Config.AopNamespaceParser, Spring.Aop" />

    </parsers>

  </spring>

  </configSections>

 

  <objects xmlns="http://www.springframework.net"

           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

           xmlns:tx="http://www.springframework.net/tx"

           xmlns:db="http://www.springframework.net/database" >

    <!-- 데이터베이스 연결 정보-->

    <db:provider id="DbProvider"

                  provider="SqlServer-1.1"

                  connectionString="Data Source=(local);Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False"/>

    <!-- 트랜잭션 관리자-->

    <object id="transactionManager"

            type="Spring.Data.AdoPlatformTransactionManager, Spring.Data">

      <property name="DbProvider" ref="DbProvider"/>

    </object>

 

    <!--데이터베이스 접근을 위한 AdoTemplate 정의  -->

    <object id="adoTemplate" type="Spring.Data.AdoTemplate, Spring.Data">

      <property name="DbProvider" ref="DbProvider"/>

    </object>

 

    <!--기본 트랜잭션용 AOP 프락시 생성자 및 어드바이스 사용 -->

    <tx:attribute-driven transaction-manager="transactionManager"/>

 

    <!--   BSL, DAO 객체 정의   -->

    <object id="bslObject"

            type="Spring.Data.BSLObject, Spring.Data.Integration.Tests">

      <property name="DaoObject" ref="daoObject"/>

    </object>

    <object id="daoObject" type="Spring.Data.DaoObject, Spring.Data.Integration.Tests">

      <property name="AdoTemplate" ref="adoTemplate"/>

    </object>

  </objects>

</configuration>

 

이 설정에는 어떤 메소드에 트랜잭션이 적용될 것인가가 나와 있지 않다. BSL단 객체의 메소드에 어트리뷰트( TransactionAttribute)를 표시하고 있다. 앞에서 말한대로 Spring은 configuration에 정의된 모든 객체들중에서 어트리뷰트( Transaction() )이 표시된 객체들을 찾는다. 그런 다음 TrasanctionAttribute에 설정된 옵션값들( IsolationLevel, Propagation값 등)로 세팅된 AOP 트랜잭션 인터셉터를 생성한다.

이제 앞에서 보여준 그림을 다시 한번 보도록 하자. “타갯 객체의 메소드 호출자가 트랜잭션용 프락시를 통해서 메소드를 호출하게 되면 이제 트랜잭션 관리자는 트랜잭션 인터셉터에 설정된 옵션들을 이용해서 트랜잭션 객체를 생성하고 트랜잭션 관리를 시작하게 된다.” 이제 이 말이 이해가 되어야 하는데….

만약 개발자가 TransactionAttribute를 사용할때 아무 트랜잭션 옵션도 제공하지 않는다면 즉 [Transaction()]만 사용한다면 기본 옵션값으로 트랜잭션이 설정된다. 다음은 Spring.NET에서 제공하는 트랜잭션 옵션들의 기본값들이다.

 

propagation : TransactionPropagation.Required

isolation : IsolationLevel.ReadCommitted

read-only : false 즉 read/write

timeout : 이 값은 사용되는 트랜잭션 관리자에 따라서 기본값이 달라진다.

 

  • 트랜잭션 기본 옵션 변경

 

만약 이 기본값을 시스템 전체적으로 바꾸고 싶다면 어떻게 해야 하나. <tx:advice> 요소를 사용해서 변경할 수 있다.

<!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/> object below) -->

<tx:advice id="txAdvice" transaction-manager="transactionManager">

  <!-- the transactional semantics... -->

  <tx:attributes>

    <!-- all methods starting with 'get' are read-only -->

    <tx:method name="Get*" read-only="true"/>

    <!-- other methods use the default transaction settings (see below) -->

    <tx:method name="*" propagation="Required" isolation="ReadUncommitted" timeout="60" read-only="false"/>

  </tx:attributes>

</tx:advice>

 

이 설정은 메소드명이 “Get”으로 시작하는 메소드는 읽기 전용의 트랜잭션을 사용하고 그 외는 기본 트랜잭션 옵션을 설정된 트랜잭션을 이용하라는 내용이다. 이렇게 설정하면 읽기 전용의 트랜잭션을 사용하기 위해서 개발자는 메소드명을 지을때 “Get”으로 시작하는 이름을 사용하면 된다. 그 외의 모든 메소드( name=”*” )는 설정에서 앞의 설정에서 주어진 트랜잭션 컨텍스에서 실행된다.

이 설정은 전체 시스템의 기본값을 변경한다. 만약 필요하다면 메소드별로 개발자는 TransactionAttribute의 속성을 변경해서 이 값을 변경할 수 있다.

 

트랜잭션을 위한 Spring.NET 설정에 대한 이야기를 이것으로 마무리해야 겠다. Spring.NET 메뉴얼을 보면 트랜잭션과 관련해서 다른 얘기도 많이 있지만 달봉이는 기업용 애플리케이션에서 사용하기에 적절하다고 판단되는 설정에 대한 이야기를 위주로 했다.

 

  • 다음 작업

 

이제 남은 것은 트랜잭션을 사용할 BSL 및 DAO 객체를 코딩하는 것이다.

 

휴~~

Posted by dalbong2

연수 떠나기 전에 다 하지 못했던, Spring.NET 프레임워크 연재를 계속하기 위해서 다시 공부를 시작했다.

이번에는 Spring.NET이 트랜잭션을 지원하는 방식을 정리하려고 하고 있다. 그러나 이번 포스트의 토픽은 이것이 아니다. Spring.NET이 미들 티어의 트랜잭션 관리를 어떻게 하고 있는지를 설명하기 전에 패턴을 하나 미리 정리하는 것이 나을 것 같았다.

 

Strategy 패턴이 뭐여

 

달봉이도 이렇게 사용하는 것이 Strategy 패턴이구나 하고 이제서야 알게 되었다. 사실은 이런 패턴을 사용했던 것은 달봉이가 학교 다닐 때부터였다. 다음은 달봉이의 논문에 포함되어 있는 그림이다.

다른 것은 볼 필요없고, 붉은 박스 안을 보자. 교량을 건설하다 보면 어떤 특정 부분의 재료가 ‘빔(beam)’이라는 것이 사용될 수도 있고, ‘트러스바(trussbar)’라는 것이 사용될 수도 있다. 이런 부품으로는 어떤 것도 올 수 있다는 것이다. 그림에서 부품의 종류를 나타내는 마지막 박스는 점점(…)으로 채워져있는 것은 아직 개발되지 않은 부품이 나중에 사용될 수도 있다는 것이다.

이런 부품에 따라서 교량을 설계 또는 안정성 해석의 프로그램에 입력되는 값들이 달라지고 계산의 구체적인 로직이 조금씩 달라진다. 이 설계의 목적은 해석 모델의 부품은 언제든지 교체될 수 있어야 한다는 것이다.

요는 이렇다. 이런 부품들은 언제든지 변할 수 있으니 부품이 바뀌더라도 다른 부분의 코드는 수정이 없어야 한다는 것이다. 이런 시나리오가 바로 Strategy 패턴이 적용될 수 있는 부분이다.

근데 왜 Strategy라는 이름이 붙었을까? 검색을 해 보면 어딘가에 그 사연이 있겠지만 지금으로서는 달봉이도 모른다. 그러나 Strategy란 것이 교체 가능한 부품 하나 하나를 말한다고 한다. Strategy 패턴을 설명하는 wikipeadia의 설명에 따르면 Strategy를 policy라고도 한단다. 전체적인 컨셉(인터페이스)은 고정되어 있더라도 구체적인 전략이나 정책은 언제든지 바뀔 수 있는 것이다.

Spring.NET에서 다시 설명할 기회가 있겠지만, Spring.NET의 트랜잭션 관리를 예로 들어서 알아보자. Spring.NET의 트랜잭션 관리란 말은 새로운 트랜잭션 기술을 구현해서 제공하고 있다는 것은 아니다. 이미 구현되어 있는 기술들을 Strategy 패턴을 이용해서 언제든지 필요한대로 각 기술들을 사용할 수 있는 환경( 컨텍스트 )를 제공한다는 것이다. 트랜잭션을 처리할 수 있는 이미 구현된 기술들은 어떤 것들이 있나. 우리가 흔히 사용해온 COM+( Enterprise Services), ADO.NET, System.TransactionScope를 이용하는 방법들이 있겠고, NHibernate라는 것을 이용하는 방법도 있다. 그외 다른 방법도 있을 것이다. 각 트랜잭션 처리 구현 방법들은 각각의 장단점이 있을 수 있겠고 그 장단점에 맞게 사용해야 하는 환경이 있을 수 있다.

즉 트랜잭션을 처리해야 한다는 컨셉은 동일하나 그것을 구현하기 위해서 구현한 기술들은 여러 가지가 있을 수 있다. 그리고 프레임워크가 실제 기업에 적용되었을때는 어떤 기술을 사용될지는 알 수 없다. 이런 경우 프레임워크를 준비하는 입장에서 생각해 볼 수 있는 패턴이 바로 Strategy 패턴이라 하겠다. 익숙한 플러그인 개념과도 상통한다고 볼 수 있겠다.

만약 프레임워크단에서 if문이나 switch문을 이용한다면 문제가 아닐 수 없다. 다음과 같은 프레임워크 코드가 있다고 해보자.

string 트랜잭션 = 설정된트랜잭션기술


switch( 트랜잭션 )


    case : EnterpriseService
        EnterpriseService용 API를 이용한 트랜잭션 처리


    case( ADO.NET )
        ADO.NET API를 이용한 트랜잭션 처리


    case( TransactionScope )
        TransactionScope API를 이용한 트랜잭션 처리
    ...

 

만약 이 프레임워크가 case문에 없는 다른 트랜잭션 관리 기술을 지원해야 한다는 필요성이 제기된다면 프레임워크 코드를 수정해야 하나? 이미 이 프레임워크가 기업들에 배포된 경우라면?

 

UML 다이어그램

 

이제 현실적인 문제점과 이 패턴의 필요성에 대한 동기 부여는 되었을 것으로 보인다. 그럼 이 패턴을 좀더 이론적으로 정리해보자. 다음 그림은 wikipeadia에 있는 이 패턴의 UML 다이어그램이다.

오른쪽 상단에 Strategy라는 인터페이스가 있다. 이 인터페이스에는 필요한 컨셉 예를 들어서 트랜잭션을 처리할 수 있는 기술들이라면 최소한 이런 것들은 구현될 필요가 있다는 전체적인 컨셉을 미리 정의해 놓고 있다. 트랜잭션 시작, 트랜잭션 커밋, 트랜잭션 롤백 등. 구체적인

그리고 하단에 그 인터페이스를 구현하고 있는 실제적인 전략 구현 클래스들( ConcreteStrategyA, ConcreteStrategyB )이 있다. 이 클래스들은 인터페이스에서 정의한 기본 약속들을 자신들에 맞게 실제로 구현한다. 이 전략 구현 클래스들은 언제든지 필요하다면 확장될 수 있다.

좌측 상단 부분의 Context라는 것은 전략들의 기능을 사용하는 클라이언트 코드이다. 그러나 이 클라이언트 코드가 바라보는 것은 실제적인 구현 전략들이 아니라 인터페이스만을 참조하고 있다. 즉 클라이언트 코드 입장에서는 실제 구현 전략들에는 관심도 없고 그것들의 각각에 의해 영향을 받을 일도 없다는 것이다. 걔들이 하기로 한 일들( 인터페이스에서 정의한 약속)만 잘 해주면 되는 것이다. 참고로 만약 그림의 Context도 여러 종류가 있을 수 있다면 이것도 실제로 구현된 클래스가 되어서는 안될 것이다. 이것 또한 일부 구체적으로 구현될 부분은 빈 껍데기(?)로 되어 있는 타입이 되어야 할 것이다. 이때 인터페이스를 사용할것인가? 추상 클래스를 사용할 것인가? 추상 클래스 !
이것에 인터페이스와 클래스, 어떤 타입을 사용할 것인가에 대해서 이전 포스트에서 달봉이 나름대로 정리했었다. 참조할 수 있을 것이다.

앞의 UML 다이어그램에 맞는 예제 코드도 역시 wikipeadia에서 볼 수 있다.

 

Spring.NET의 적용

 

Spring.NET은 다음과 같은 방식으로 이 패턴을 적용하고 있을 것이다.

 

인터페이스 IPlatformTransactionManager가 전략 클래스들이 구현해야 하는 약속을 정의하고 있다. 

그림에서 보이는 실제 전략 클래스들의 이름은 Spring.NET의 실제 클래스명과는 다르다. 실제는 이 이름들보다 훨씬 길어서 간략하게 줄였다.

Spring.NET 프레임워크에서는 어떤 전략 트랜잭션 관리자를 사용할 것인가를 설정할 수 방법( configuration 설정 방법 또는 프로그램적인 방법 제공)을 제공하고 있다. 그럼 Spring.NET 프레임워크는 그 설정에 맞는 적절한 관리자를 로딩해서 트랜잭션 처리에 사용하게 된다. Spring.NET 프레임워크의 사용자는 어떤 트랜잭션 관리자를 사용할 것인가에 대한 설정만 하면 된다.

몇 시간의 여유가 있어서 근무 시간에 약간의 눈치를 보며 지금까지 머리속으로만 정리했던 것을 후다닥 작성한 포스트다. 잘못된 곳이 있을 수 있다. 이해하고 있는 부분이라면 그냥 넘어가도 좋고, 영 걸린다 싶으면 댓글 남겨주면 다시 한번 더 보도록 하겠다.

Posted by dalbong2

방명록에 어떤 분이 질문을 올렸는데, 답변이 약간 길어져서 포스트로 올린다.
정신적 여유가 없어서 글이 성의없게 작성되었다는 것을 미리 밝힌다.


음...기본적으로 ClickOnce에서는 어플리케이션을 시작 프로그램(Startup)으로의 등록과 제거를 지원하지 않고 있습니다.

startup등록은 약간의 꽁수면 됩니다. 사실 clickonce와 startup으로 구글링해보면 만족할 만한 결과가 나옵니다.
제 책에 이 내용을 썼던 것 같은데, 기억이 가물합니다( 지금 원고가 어디로 가 버렸는지 찾을 수가 없습니다.-_-;; ).
요는 ClickOnce 어플리케이션을 설치하면 생성되는 shortcut 파일을 Startup 폴더에 복사하는 방법입니다.
다음 링크를 참조하면 코드가 나와 있습니다. 이 문서에는 xp, vista에서의 이슈도 기술되어 있습니다.
http://keithelder.net/blog/archive/2009/04/18/how-to-run-a-clickonce-application-on-startup.aspx
http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/75d2112c-707c-4051-a5fc-eb51802558bb/

근데, 제거에 대해서는 사실 이전에 생각해 본 봐가 없습니다.
오늘 처음으로 님의 글을 보고 심각하게 생각해봤습니다.
startup 폴더로 복사한 shortcut 파일을 삭제해야 하는데 이것을 어떻게 할 수 있느냐가 문제인 것이죠.
프로그램 추가/제거를 통해서 삭제를 하면 일단 ClickOnce가 머신에 복사하고 변경한 파일은 모두 삭제해 주는데 코딩을 통해서 복사한 이 shortcut 파일은 ClickOnce가 모른다는 거죠.

구글링을 해봐도 그에 대한 이슈는 여러명이 제기한 것 같은데 답은 없군요.

근데 그 기능이 꼬옥 필요하다면 궁여지책의 방법이 하나 생각나긴 하는데, 실험 정신(?)이 필요할 것 같습니다.
저도 실은 한번도 테스트해보지 않은 방법입니다.

이런 식의 어플리케이션을 일반 사용자를 대상으로 한다면 다음 방법 또한 쓸모가 없게 됩니다.
인트라넷 애플리케이션인 경우에만 유효하다는 것을 미리 말씀드립니다.

저의 포스트중에서 .NET 카테고리에 가보면 "기본 AppDomain 생성자 변경하기"라는 것이 있는데, 이것을 이용하면 어떨까 하는 생각을 해 봤습니다.
간단히 말하면( 간단히 말할 수 있으려나...) 사용자가 .NET 어플리케이션을 실행하면 그 어플리케이션이 실행되기 전에 .NET의 CRL은 그 어플리케이션이 실행될 공간 즉 AppDomain을 먼저 생성되는데,
이 공간을 생성하는 녀석을 우리가 커스터마이징할 수 있다는 얘기입니다.
이 커스터마이징 코드에서 만약 님이 원하시는 애플리케이션이 삭제되었다는 것을 확인(?)만 할 수 있다면,
예의 shortcut 파일을 삭제하고 AppDomain을 생성하는 작업을 포기하고 그냥 리턴하면 되지 않을까 하는 생각을 해 봤습니다.

AppDomainManager 클래스를 상속해서 InitializeNewDomain()을 상속해서 님의 어플리케이션이 제거되었는지 여부를 확인해서 제거 되었다면 shortcut 파일을 제거하고 그렇지 않다면
AppDomain을 생성하는 base.InitializeNewDomain() 메소드를 호출하면 되지 않을까 되지 않을까하는 편한 생각을 해 봅니다.
AppDomainManager에 대한 설명은 앞에서 말한 필자의 포스트나 MSDN( http://msdn.microsoft.com/en-us/library/system.appdomainmanager_methods.aspx )을 참조하시길 바랍니다.

님께서 방향을 원하셔서 답글은 남깁니다만 개인적인 지적 호기심이라면 모르겠지만, 고객의 요청이라면 먼저 고객과의 합의를 시도해보는 것은 어떨런지.
참내...기술 블로그에서 이런 해결책을 제시하다니...저도 이제 게을러 진건지...죄송합니데.

Posted by dalbong2