29. srpna 2007

Spring framework: konfigurace transakcí v XML pomocí TransactionInterceptor

Dnes jsem kolegovi vysvětloval jak se definují transakce v XML a myslím, že je to natolik častá činnost, že se tato informace bude hodit více lidem. Hned na začátek podotýkám, že zde budu popisovat pouze jeden z možných způsobů konfigurace. Praxe mě naučila, že Spring framework nabízí více možností jak určité věci řešit a nejinak tomu je i zde. Dále se budu zabývat pouze možností konfigurace pomocí XML a to pomocí třídy TransactionInterceptor a BeanNameAutoProxyCreator. Tato konfigurace je zejména vhodná pro Spring framework ve verzi 1.2.x, protože ve verzi 2.x se možnosti značně rozšířily (možnost anotací, XML tagy přímo pro definici transakcí, AOP)

Nechci zde duplikovat informace uvedené v javadoc Springu, mojí snahou je spíše uvést skutečnosti, které na první pohled nemusí být zřejmé.

V rámci konfigurace transakcí je možné definovat (nejenom) následující parametry chování transakce:

  • propagace transakce - nejčastěji používané hodnoty jsou PROPAGATION_REQUIRED, PROPAGATION_REQUIRES_NEW a PROPAGATION_SUPPORTS.

  • readOnly (volitelné) - zda se jedná o transakcí, která bude pouze číst. Zda se tento parametr využije závisí na zvoleném typu propagace transakce a na použitém transakčním manažeru.

  • -Exceptions - vyjímky (oddělené čárkou), při kterých dojde k roll-backu transakce (volitelné). Pokud není uvedena žádná vyjímka v definici, tak se předpokládá vyjímka typu RuntimeException, ne Exception.

  • +Exceptions - vyjímky (oddělené čárkou), při kterých daná transakce projde. Takové vyjímky mezi vyjímkami :-)

Pouze první parametr je povinný. Jednotlivé parametry se oddělují čárkou. Definice může tedy vypadat třeba následovně: PROPAGATION_REQUIRED,readOnly,-Exception,+UnsupportedMethodCallException,+OperationAlreadyFinishedException.

Všechny tyto parametry a spousta dalších věcí okolo transakcí ve Springu jsou velice pěkně popsány v tomto článku.

Třída TransactionInterceptor má následující settery:
  • setTransactionManager() pro nastavení manažera, který provádí vlastní management transakcí. Např. pro Hibernate je to HibernateTransactionManager. Pro různé manažery (Hibernate, JDBC, JCR) můžeme mít různé požadavky na transakce, proto je tedy konfigurace parametrů transakce vztažena vždy k jednomu transakčnímu manažerovi.

  • setTransactionAttributes() - nastavení metod a parametrů transakcí pomocí Properties, kde klíč je jméno metody a hodnota obsahuje parametry transakce. Zde je nutné poznamenat, že toto nastavení je interně zpracováno třídou NameMatchTransactionAttributeSource. Na tento setter se dá tedy nahlížet jako na pomocnou metodu pro uživatele, protože používá nejčastěji používanou implementaci pro zápis metod.

  • setTransactionAttributeSource() - nastavení transakcí dle vlastní volby. Možné volby jsou dány rozhraním TransactionAttributeSource a jeho implementacemi. V úvahu pro XML zápis připadají tyto implementace:
    • MatchAlwaysTransactionAttributeSource - jednoduchá implementace, která nastavuje stejné transakční parametry bez ohledu na metody

    • MethodMapTransactionAttributeSource - vhodné pro definice transakcí, kde nám jde o vyjádření metod pomocí plných jmen (FQCN), tedy pomocí plné cesty k dané třídě nebo rozhraní a jménu metody. Je možné používat i zástupný znak * v názvu metody. Pak to funguje tak, že Spring si během inicializace automaticky doplní interní seznam metod o plné názvy metod, které odpovídají zápisům s hvězdičkou. Přesněji specifikovaný zápis metody přebijí volnější definici s hvězdičkou.

    • NameMatchTransactionAttributeSource - nejčastěji používaná implementace, která umožňuje definovat pravidla pro zachytávání metod velice volně pomocí hvězdičkové notace (např. "xxx*", "*xxx" or "*xxx*"). Zde to funguje trochu jinak než u předchozí implementace - až teprve při spouštění metody se Spring snaží dle definovaných pravidel najít správný záznam, který lze použít a pokud ho najde, tak nastaví transakci dle definovaných parametrů.

  • setTransactionAttributeSources() - variace na předchozí setter. Tato volba je vhodná pro nastavení transakcí více způsoby. Toto se může hodit celkem často, protože co lze vyjádřit jedním způsobem zápisu již nelze tak dobře vyjádřit jiným způsobem.

Dle mé zkušenosti nefunguje kombinace metod setTransactionAttributes() a setTransactionAttributeSource(). Pokud uživatel potřebuje nadefinovat transakce pomocí více způsobů, tak by měl použít metodu setTransactionAttributeSources.

Nakonec dvě ukázky - první je příklad nejčastějšího použití, druhý příklad ukazuje definici pomocí složitější konstrukce.

<bean id="DbTransactionInterceptor"
class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="dbTransactionManager" />
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="search*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="exist*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="add*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="remove*">PROPAGATION_REQUIRED,-Exception</prop>
<prop key="change*">PROPAGATION_REQUIRED,-Exception</prop>

<prop key="cz.anect.mis.web.facade.DocumentFacade.getAdminsByLocation">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="cz.anect.mis.web.facade.DocumentFacade.addDocumentOnlineRequest">PROPAGATION_REQUIRED,-Exception</prop>

</props>
</property>
</bean>


<bean id="DbTransactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="dbTransactionManager"/>
<!-- full qualified method names -->
<property name="transactionAttributeSource">
<bean class="org.springframework.transaction.interceptor.MethodMapTransactionAttributeSource">
<property name="methodMap">
<map>
<entry key="cz.anect.mis.services.DocumentService.addDocument" value="PROPAGATION_REQUIRED,-Exception" />
<entry key="cz.anect.mis.services.DocumentService.addDocumentToData" value="PROPAGATION_REQUIRED,-Exception" />
<entry key="cz.anect.mis.web.facade.DocumentFacade.addDocumentData" value="PROPAGATION_REQUIRED,-Exception" />
<entry key="cz.anect.mis.web.facade.DocumentFacade.get*" value="PROPAGATION_REQUIRED,readOnly" />
<entry key="cz.anect.mis.web.facade.DocumentFacade.exist*" value="PROPAGATION_REQUIRED,readOnly" />
<entry key="cz.anect.mis.web.facade.DocumentFacade.getDocumentByDownloadHash" value="PROPAGATION_REQUIRED,-Exception" />

<entry key="cz.anect.mis.web.facade.CodelistFacade.addStorePlaceAndFund" value="PROPAGATION_REQUIRED" />
<entry key="cz.anect.mis.web.facade.CodelistFacade.addNewDigiUser" value="PROPAGATION_REQUIRED" />
<entry key="cz.anect.mis.web.facade.CodelistFacade.get*" value="PROPAGATION_REQUIRED,readOnly" />
</map>
</property>
</bean>
</property>
</bean>
Aby byla konfigurace transakcí kompletní je nutné ještě dodefinovat propojení mezi transakčními "zachytávači" (TransactionInterceptor) a instancemi tříd, na které budeme transakční pravidla aplikovat.

<bean id="BeanNameProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<idref bean="DocumentFacade"/>
<idref bean="CodelistFacade"/>
<idref bean="DocumentService"/>
<idref bean="PlaceIdentificationFacade"/>
</list>
</property>
<property name="interceptorNames">
<list>
<idref bean="DbTransactionInterceptor"/>
<idref bean="JcrTransactionInterceptor"/>
<idref bean="gisJdbcTransactionInterceptor"/>
</list>
</property>
</bean>

5 komentářů:

Novoj řekl(a)...

Spíš jen dotázek - není toto způsob deklarativních transakcí ze Springu 1.X? Jelikož teď jsem implementoval deklarativní transakce ve svém projektu nad Spring 2.X a tam se to dělá pomocí AOP (AspectJ).

Jen si to chci ujasnit ...

Petr Jůza řekl(a)...

Ano, máš pravdu. Já to i uvádím na začátku článku, že tento článek je spíše pro verzi 1.x. Ve verzi 2.x je to stále možné, nicméně jsou další možnosti - anotace, využití speciálních XML tagů pro transakce nebo si nadefinovat transakce přes AOP. V konečném důsledku je to pro jakýkoliv způsob přes AOP.

Také je vhodné v některých případech využít třídu TransactionCallbackWithoutResult.

Novoj řekl(a)...

Aha, promiň to jsem přehlédl, že jsi to na začátku uváděl. Ještě si nejsem úplně 100% jistý, jak se poperou programově řízené transakce, pokud je používaná TransactionTeplate s deklaračními transakcemi. Nikde jsem to explicitně nenašel potvrzené, ale empiricky řečeno, myslím, že to funguje a že TransactionTemplate se zapojí do vyšší transakce, pokud je tato deklarovaná ve Spring konfiguráku. Pokud o tom víš něco víc, budu rád, když se podělíš. Tak i tak se na to asi kouknu do zdrojáků, jak tam s tím pracují. Zrovna ty transakce jsou ale docela zamotaná záležitost na pochopení (když se zapojí to AOP).

Petr Jůza řekl(a)...

Já bych řekl, že záleží. Když se podívám na JavaDoc TransactionTemplate, tak vidím konstruktor s parametrem pro nastavení TransactionDefinition. Z toho bych řekl, že výsledné chovaní si nastavíš dle vlastních potřeb. Jaké je defaultní chování jsem přesně nezjistil, protože to je závislé na konkrétní implementaci PlatformTransactionManager.

Já jsem začal využívat TransactionTemplate právě z toho důvodu, že mám nějakou globální transakci a v ní jsem potřeboval atomicky něco dělat. Přesně řečeno indexuji dokumenty (globální transakce) a potřebuji, aby indexace každého dokumentu byla atomická. Tedy když jeden dokument neprojde, tak aby prošly ostatní.

Roman Pichlík řekl(a)...

Samozrejme, ze to v te parent transakci participuje bez ohledu na to jestli je rozjeta programove a nebo demarkovana metadaty. Muzes to ovsem ovlivnit na urovni dcerine transakce a jeji propagacni semantiky.