Projektowanie, Programowanie, Codzienność – BeniaminZaborski.com

21 Maj 2009

Ugryźć Spring.Net – (cz.5) Zarządzanie transakcjami

Filed under: Ugryźć Spring.Net — Beniamin Zaborski @ 21:30

Czas zająć się wreszcie kwestią istotną, ale często traktowaną przez wielu z nas po macoszemu, mianowicie transakcjami, a w zasadzie to zarządzaniem transakcjami. Każdy zdaję sobie sprawę z tego, że jest to bardzo ważny temat, ale nie do końca poświęcamy mu odpowiednią ilość czasu. W tym artykule chciałbym przybliżyć cały mechanizm zarządzania transakcjami jaki daje nam Spring.NET.

To co powinno nas na początku zainteresować to interfejs IPlatformTransactionManager, który wygląda tak:


public interface IPlatformTransactionManager
{
  ITransactionStatus GetTransaction( ITransactionDefinition definition );
  void Commit( ITransactionStatus transactionStatus );
  void Rollback( ITransactionStatus transactionStatus );
}

Spring.Net dostarcza kilka różnych implementacji tego interfejsu, tj.:

  • AdoPlatformTransactionManager – obsługa lokalnych transakcji ADO.NET
  • ServiceDomainPlatformTransactionManager – obsługa transakcji rozproszonych bazujących na menadżerze Enterprise Services
  • TxScopePlatformTransactionManager – obsługa transakcji lokalnych i zdalnych bazująca na System.Transactions
  • HibernatePlatformTransactionManager – obsługa transakcji lokalnych dla NHibernate i ADO.NET

Jak widać metoda GetTransaction zwraca odpowiedni obiekt implementujący interfejs ITransactionStatus w zależności od przekazanych parametrów (ITransactionDefinition). Dzięki temu obiekt ITransactionStatus może reprezentować nową lub już istniejącą transakcję.

Co zatem określa obiekt ITransactionDefinition? Przyjrzyjmy się temu bliżej. Oto co definiuje:

  • Isolation – poziom izolacji transakcji
  • Propagation – pozwala określić czy ma zostać utworzona nowa transakcja czy kod ma być wykonany w istniejącej transakcji
  • Timeout – czas działania transakcji po jakim zostanie zgłoszony błąd przekroczenia czasu oczekiwania
  • Read-only – ważna właściwość określająca, że dane w transakcji nie będą modyfikowane. Ma to szczególne znaczenie w przypadku NHibernate, bo podnosi wydajność takiej transakcji.

ITransactionStatus zwracany z metody GetTransaction określa status transakcji i niejako jej kontekst wykonania. Zauważmy, że parametr tego typu przyjmują metody Commit i Rollback interfejsu IPlatformTransactionManager.

Najprostszym sposobem powołania do życia menadżera transakcji jest zlecenie tego kontenerowi IoC. W wypadku prostego menadżera dla lokalnych transakcji ADO.NET plik konfiguracyjny wygląda tak:


<objects xmlns=’http://www.springframework.net’ xmlns:db=”http://www.springframework.net/database”&gt;
  <db:provider id=”DbProvider”
  provider=”SqlServer-2.0″
  connectionString=”Data Source=BENIAMINZ\SQLEXPRESS;Initial Catalog=ADMINET;Integrated Security=SSPI;”/>
  <object id=”TransactionManager” type=”Spring.Data.AdoPlatformTransactionManager, Spring.Data”>
    <property name=”DbProvider” ref=”DbProvider”/>
  </object>
</objects>

Jedyne co wymaga wyjaśnienia to DbProvider, ale jego już używaliśmy niejednokrotnie. Menadżerowi transakcji wystarczy przekazać referencję do odpowiedniego DbProvider, który jak widać na przykładzie jest zdefiniowany wyżej.

W wypadku menadżera transakcji bazującego na System.Transactions plik konfiguracyjny wyglądałby dużo prościej:


<object id=”TransactionManager” type=”Spring.Data.TxScopeTransactionManager, Spring.Data”>
</object>

Tu już nie przekazujemy DbProvidera, ponieważ mechanizm typu System.Transactions to rozproszony system zarządzania transakcjami.

Jeszcze inaczej się przedstawia konfiguracja menadżera transakcji dla NHibernate:


<object id=”HibernateTransactionManager” type=”Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate”>
  <property name=”DbProvider” ref=”DbProvider”/>
  <property name=”SessionFactory” ref=”MySessionFactory”/>
</object>

Podobnie jak w wypadku tego pierwszego przekazujemy DbProvidera, ale jeszcze dodatkowo musimy wskazać na odpowiedni SessionFactory. SessionFactory definiowaliśmy w części poświęconej dostępowi do danych poprzez NHibernate, więc nie będę tego ponownie przedstawiał.

Zarządzanie transakcjami w Spring.Net jest możliwe na dwa sposoby: deklaratywnie i imperatywnie. Jak podaje dokumentacja większość użytkowników wybiera sposób deklaratywny. Hmm … ja też! Podstawowy powód mojego wyboru jest taki, iż ta metoda wymaga naprawdę niewiele wysiłku, a także i może przede wszystkim, nie „wplątuje” się w kod logiki biznesowej aplikacji.
Dodatkowe cechy deklaratywnej obsługi transakcji w Spring.NET to możliwość definiowania własnych reguł wycofywania transakcji, czy możliwość kontrolowania procesu działania transakcji, tj. np. wykonania dodatkowego kodu podczas rollback. Możliwe jest to dzięki temu, iż deklaratywna obsługa transakcji opiera się na AOP.

W typowych przypadkach transakcje powinniśmy stosować na poziomie komponentów biznesowych czy serwisów pełniących rolę fasad. Nie stosujemy transakcji na niższym poziomie abstrakcji, tj, bezpośrednio w obiektach dostępu do danych. Przedstawię prosty przykład serwisu:


public class AbonenciService : IAbonenciService
{
  private AbonentDAO abonentDAO;
  public AbonentDAO AbonentDAO
  {
    get { return abonentDAO; }
    set { abonentDAO = value; }
  }

  [Transaction()]
  public void DodajAbonenta(AbonentDTO abonentDTO)
  {
    AbonentDAO.Zapisz(AbonentConverter.ToAbonent(abonentDTO));
  }
}            

Jak widać jedyne co musimy zrobić to opatrzyć metodę serwisu atrybutem [Transaction()]. Tutaj został użyty najprostszy przypadek, ale można również na tym poziomie określić dodatkowe cechy transakcji jak: poziom izolacji, to czy transakcja ma być read-only, itp.

Przyjrzyjmy się teraz przykładowi kodu z poprzedniej części serii poszerzonemu o definicję naszego serwisu:


<spring>
  <parsers>
    <parser type=”Spring.Remoting.Config.RemotingNamespaceParser, Spring.Services” />
    <parser type=”Spring.Data.Config.DatabaseNamespaceParser, Spring.Data” />
    <parser type=”Spring.Transaction.Config.TxNamespaceParser, Spring.Data” />
  </parsers>
  <context>
    <resource uri=”config://spring/objects” />
  </context>
  <objects xmlns=”http://www.springframework.net&#8221;
  xmlns:db=”http://www.springframework.net/database&#8221;
  xmlns:tx=”http://www.springframework.net/tx”&gt;
    <db:provider id=”DbProvider”
    provider=”SqlServer-2.0″
    connectionString=”Data Source=BENIAMINZ\SQLEXPRESS;Initial Catalog=ADMINET;Integrated Security=SSPI;”/>
    <object id=”NHSessionFactory” type=”Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate12″>
    <property name=”DbProvider” ref=”DbProvider”/>
    <property name=”MappingAssemblies”>
      <list>
        <value>BizDev.SimpleSpringApplication</value>
      </list>
    </property>
    <property name=”HibernateProperties”>
      <dictionary>
        <entry key=”hibernate.connection.provider”
        value=”NHibernate.Connection.DriverConnectionProvider”/>
        <entry key=”hibernate.dialect”
        value=”NHibernate.Dialect.MsSql2005Dialect”/>
        <entry key=”hibernate.connection.driver_class”
        value=”NHibernate.Driver.SqlClientDriver”/>
        <entry key=”hibernate.current_session_context_class”
        value=”Spring.Data.NHibernate.SpringSessionContext, Spring.Data.NHibernate12″/>
      </dictionary>
    </property>
  </object&gt

  <object id=”transactionManager” type=”Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate12″>
    <property name=”DbProvider” ref=”DbProvider”/>
    <property name=”SessionFactory” ref=”NHSessionFactory”/>
  </object>

  <object id=”AbonentDao” type=”BizDev.SimpleSpringApplication.AbonentDAO, BizDev.SimpleSpringApplication”>
    <property name=”SessionFactory” ref=”NHSessionFactory”/>
  </object>
 
  <object id=”AbonenciService” type=”BizDev.SimpleSpringApplication.AbonenciService, BizDev.SimpleSpringApplication”>
    <property name=”AbonentDao” ref=”AbonentDao”/>
  </object>

  <tx:attribute-driven/>
  </objects>
</spring>

To jest kompletny w pełni działający przykład na którym można dostrzec: obiekt dostępu do danych, serwis, provider bazy danych, NHibernate-owe SessionFactory oraz to co nas interesuje najbardziej czyli menadżer transakcji dla NHibernate. Wyjaśnienia wymaga jeszcze wpis <tx:attribute-driven/>, który jest niezbędny dla deklaratywnego mechanizmu zarządzania transakcjami. Tutaj został zdefiniowany z domyślnymi parametrami, tj. wskazuje na menadżera transakcji o domyślnej nazwie „transactionManager”. Jeżeli ta nazwa byłaby inna wtedy musielibyśmy to określić tak:


<object id=”nhTransactionManager” type=”Spring.Data.NHibernate.HibernateTransactionManager, Spring.Data.NHibernate12″>
  <property name=”DbProvider” ref=”DbProvider”/>
  <property name=”SessionFactory” ref=”NHSessionFactory”/>
</object>
<tx:attribute-driven transaction-manager=”nhTransactionManager”/>

Inna metoda obsługi transakcji w Spring.NET to metoda imperatywna, czyli programowa. To ta mniej preferowana przeze mnie. Do zarządzania transakcjami w ten sposób możemy skorzystać z jednego z dwóch rozwiązań:

  • TransactionTemplate
  • IPlatformTransactionManager

Pierwsza metoda polega na stosowaniu anonimowych delegatów. Prawda, że ten kod nie wygląda przyjemnie?


tt.Execute(delegate(ITransactionStatus status)
{
  try
  {
    AbonentDAO.Zapisz(abonent);
  } catch (Exception ex)
  {
    status.RollbackOnly = true;
  }
  return null;
});

Drugie podejście to użycie klasy implementującej IPlatformTransactionManager. Interfejs IPlatformTransactionManager już znamy. Spójrzmy na samo opisujący się przykład, gdzie transactionManager implementuje wspomniany interfejs:

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.PropagationBehavior = TransactionPropagation.Required;
ITransactionStatus status = transactionManager.GetTransaction(def);
try
{
  AbonentDAO.Zapisz(abonent);
}
catch (Exception e)
{
  transactionManager.Rollback(status);
  throw;
}
transactionManager.Commit(status);   

Wygląda prosto i tak też jest w rzeczywistości. Pobieramy instancję ItransactionStatus, a następnie wykonujemy commit na sukces lub rollback w wypadku niepowodzenia przekazując ją jako parametr.

Spring.NET daje nam świetny mechanizm zarządzania transakcjami i zwalania nas od implementowania własnych rozwiązań pozwalając skupić się na właściwym biznesie aplikacji.
Dokumentacja Spring.Net omawia jeszcze szczegółowo wiele aspektów zarządzania transakcjami. Zainteresowanych samym działaniem mechanizmu od kuchni oraz tym jaką rolę w tym wszystkim odgrywa AOP odsyłam do dokumentacji.
W kolejnej części omówię stosowanie serwisów w aplikacjach rozproszonych Spring.NET.

2 komentarzy »

  1. Mam dwa pytania:
    1) Czy nie prośbiej olać cały mechanizm ze Spring.NET i wykorzystywać starego dobrego TransactionScope’a?
    2) Czy nie dałoby radę bardziej wyeksponować kanału RSS na Twoum blogu? Bo musiałem się domyśleć, jaki ma adres. :)

    Komentarz - autor: Jakubin — 22 Maj 2009 @ 08:39

    • Owszem można wykorzystać TransactionScope czy System.Transactions, ale Spring.NET wprowadza jeden zunifikowany mechanizm obsługi transakcji niezależnie od jej rodzaju. Działa to na wyższym poziomie abstrakcji.
      Co to drugiego pytania pomyślę nad RSS ;).

      Komentarz - autor: Beniamin Zaborski — 22 Maj 2009 @ 10:30


RSS feed for comments on this post. TrackBack URI

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

Stwórz darmową stronę albo bloga na WordPress.com.

%d bloggers like this: