Archiv für die Kategorie ‘Spring-Framework’

Pitfall Exceptions bei annotierten Spring-Transaktionen

Donnerstag, 02. September 2010

Spring ist eines der beliebtesten – wenn nicht das beliebteste – Framework in der Java-Welt. Es bietet für nahezu jede vorstellbare Komponente/Facette bei der Anwendungsentwicklung Unterstützung und hilft von weiteren verwendeten Frameworks (z.B. Web-Frameworks) zu abstrahieren. Eine Paarung die man häufig in Java-Projekten zur Realisierung der Persisterung findet, ist Spring in Kombination mit Hibernate.

Spring hilft hier die Integration von Hibernate in die Anwendung zu vereinfachen und spielt vor allem beim Transaktions-Management eine wichtige Rolle. Egal ob nun Services oder DAOs, Spring erlaubt es einfach per Annotations die Transaktionen zu deklarieren und zu steuern. Dafür genügt – bei entsprechender Konfiguration – eine @Transactional Annotation an einem Method-Body:

@Transactional
public void doInTransaction() {
    writeSomething1();
    writeSomething2();
}

Die so deklarierte Methode lässt writeSomething1() und writeSomething2() in einer Transaktion ablaufen. Schlägt writeSomething2() fehl, so wird writeSomething1() zurückgerollt. Möchte man nun Auskunft über den Erfolg der Methode bzw. über gewisse, aufgetretene Fehler geben, gibt es mehrere Möglichkeiten. Die erste ist der eher veraltete, aber noch immer anzutreffende Status-Code. Hier wird zumeist ein Integer-Wert benutzt, um bestimmte Ereignisse (Erfolg, Fehler1, Fehler2 …) abzubilden. Für unser obiges Beispiel könnte dass dann etwa so ausschauen (reduziert auf einen Fehler- und Erfolgsfall):

@Transactional
public int doInTransaction() {
  try {
    writeSomething1();
    writeSomething2();    
  } catch (WriteSomething2Exception) {
      return 1:
  }
  return 0;
}
 
class WriteSomething2Exception extends Exception {}

Das problematische an dieser Implementierung ist, dass sie das Transaktionsmanagement aushebelt. Denn wirft writeSomething2() die Exception, wird sie nicht weitergeleitet und so kann Spring auch nicht erkennen, dass ein Fehler vorliegt und die Transaktion zurückgerollt werden muss.

Statt die Fehler mit Status-Codes abzubilden, kann man natürlich auch Exceptions für die Fehlerfälle verwenden:

@Transactional
public int doInTransaction() throws WriteSomething2Exception {
  writeSomething1();
  writeSomething2();    
}
 
class WriteSomething2Exception extends Exception {}

In diesem Fall wird die Exception weitergeleitet und alles sollte klappen. Sollte man mindestens meinen… Aber der obige Code hat noch immer den gleichen Effekt. Warum? Nun, um das herauszufinden muss man lediglich das java-doc der @Transactional Annotation genau lesen:

/**
 * Describes transaction attributes on a method or class.
 *
 * <p>This annotation type is generally directly comparable to Spring's
 * {@link org.springframework.transaction.interceptor.RuleBasedTransactionAttribute}
 * class, and in fact {@link AnnotationTransactionAttributeSource} will directly
 * convert the data to the latter class, so that Spring's transaction support code
 * does not have to know about annotations. If no rules are relevant to the exception,
 * it will be treated like
 * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute}
 * (rolling back on runtime exceptions).
 * 
 * @author Colin Sampaleanu
 * @author Juergen Hoeller
 * @since 1.2
 * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute
 * @see org.springframework.transaction.interceptor.RuleBasedTransactionAttribute
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
...
}

Und siehe da, die letzte Zeile im Kommentar enthält die Lösung. Spring rollt lediglich bei RuntimeExceptions zurück! Da unsere Exception jedoch eine checked Exception ist, passiert nichts. Die Lösung ist also nur RuntimeExceptions zu verwenden. Was aber, wenn wir das nicht wollen? Für diesen Fall bietet die Annotation uns das Attribut rollbackFor. Dort kann man dann als Wert die Klassen der Exceptions angeben kann, für die ein Rollback erfolgen soll. Für unser Beispiel sieht die Lösung dann wie folgt aus:

@Transactional(rollbackFor = WriteSomething2Exception.class)
public int doInTransaction() throws WriteSomething2Exception {
  writeSomething1();
  writeSomething2();    
}
 
class WriteSomething2Exception extends Exception {}

Christian Schätzlein


Zeitmessung mit Spring AOP

Donnerstag, 24. Juni 2010

Trifft man bei der Entwicklung einer Anwendung auf Performanceprobleme, reicht es meist aus, die verantwortlichen Codebereiche aufzuspüren, um dann den Fehler/das Problem zu beheben. Jedoch gestaltet sich das Auffinden der betroffenen Codestellen meist als schwierig oder langwierig. In jede verdächtige Methode Log-Ausgaben mit der Zeitmessung zu schreiben, wäre unsauber. Mit Spring ist die einfachste Lösung, einen Interceptor zu konfigurieren, der die Methodenaufrufe unterbricht und die Zeitmessung ins Log schreibt.

Als erstes definieren wir einen Advice (Interceptor-Bean), die Implementierung bringt Spring schon mit.

<bean id="timingLogger" 
class="org.springframework.aop.interceptor.PerformanceMonitorInterceptor" />

Als zweiten Schritt können wir mit der AOP Konfiguration festlegen, welche Methodenaufrufe protokolliert werden sollen, damit die Log-Datei übersichtlich bleibt. Folgend zum Beispiel alle public-Methoden der Spring-Beans im Paket service und allen Unterpaketen:

<aop:config>
   <aop:pointcut id="services" expression="execution(* de.myApp.service..*.*(..))" />
   <aop:advisor advice-ref="timingLogger" pointcut-ref="services" />
</aop:config>

Für das Definieren der korrekten pointcut-expression hilft die ausführliche Spring Dokumentation (7.2.3 Declaring a pointcut, Version 3).
Damit das Ergebnis in den Logs auftaucht, sollte für diesen Interceptor der Log-Level auf TRACE stehen:

log4j.logger.org.springframework.aop.interceptor.PerformanceMonitorInterceptor=TRACE

Die Ausgabe sieht dann zum Beispiel so aus:

2010-06-24 09:06:53,109 TRACE PerformanceMonitorInterceptor:64 - StopWatch 'de.myApp.service.MyService.methodName': running time (millis) = 1813

Für eine erste Analyse ist diesen Vorgehen hilfreich, will man jedoch aggregierte Werte über einen Zeitraum bekommen, bietet sich zum Beispiel Perf4J an. Mit diesem Tool lassen sich aggregierte Messwerte (Min, Max, Durchschnitt, Anzahl) in eine extra Logdatei schreiben.

Es besteht aber auch die Möglichkeit einer einfachen eigenen Implementierung:

public class TimingLoggingInterceptor implements MethodInterceptor {
    public Object invoke(MethodInvocation invocation) throws Throwable {
        StopWatch sw = new StopWatch();
        sw.start();
        Object returnValue = invocation.proceed();
        sw.stop();
        System.out.println(
            invocation.getClass().getSimpleName() + "#" + 
            invocation.getMethod().getName() + ": " +
            sw.getTotalTimeMillis());
        return returnValue;
    }
}

Nun muss nur noch der Wert der Spring-StopWatch gespeichert werden. Diese eigene Lösung ist besonders interessant, wenn nur unter bestimmten Bedingungen die Werte geloggt werden sollen, zum Beispiel wenn die Laufzeit eine definierte Obergrenze überschreitet.


Jan Kuenstler


Transaktionssteuerung mit Spring AOP

Freitag, 24. April 2009

Spring bietet uns mit AOP die Möglichkeit innerhalb der Spring Konfiguration die Behandlung der Datenbanktransaktionen zu steuern. Hierfür benötigen wir lediglich eine AOP Konfiguration, die auf alle Methoden zeigt, die innerhalb einer Transaktion ablaufen sollen und den Spring Tag .

In diesem Beispiel gehen wir von zwei Service Klassen aus, deren Methoden von einer Transaktion umschlossen werden sollen.

applicationContext.xml:

<-- service beans -->
<bean id="fooService" class="a.b.service.FooService" />
<bean id="barService" class="a.b.service.BarService" />
 
<-- hibernate transaction manager -->
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>
 
<-- hibernate session factory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource">
        <ref bean="dataSource" />
    </property>
    <property name="annotatedClasses">
        <list>....</list>
    </property>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
        </props>
    </property>
</bean>

Innerhalb des tx:advice definieren wir das Verhalten für die einzelnen Methoden der Services. Für alle Methodenaufrufe werden die gleichen Isolation- und Propagation-Level benutzt. Für Methoden die mit „find“ oder „get“ beginnen setzen wir die Transaktion noch auf read-only. In diesem Methoden dürfen jetzt nur noch Datenbank SELECTs ausgeführt werden, anderenfalls würde es eine Exception geben. Die Datenbank kann so das Locking auf Zeilen, die von diesem Methodenaufrufen betroffen, sind optimieren.

applicationContext.xml:

<tx:advice id="defaultTranscationAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="*" isolation="READ_COMMITTED" propagation="REQUIRED" />
        <tx:method name="find*" read-only="true" />
        <tx:method name="get*" read-only="true" />
    </tx:attributes>
</tx:advice>

Als letztes brauchen wir jetzt noch die AOP Konfiguration, die das defaultTransactionAdvice mit den Service Klassen verbindet. Durch die Definition „a.b.service.*.*(..)“ wird um jede Methode in jeder Klasse innerhalb des Paketes a.b.service eine Transaktion gelegt. Es können so weitere Service Klassen hinzugefügt werden, ohne dass das Transaktion Handling angepasst werden muss.

applicationContext.xml:

<aop:config>
    <aop:pointcut id="allServiceMethods" expression="execution(* a.b.service.*.*(..))" />
    <aop:advisor advice-ref="defaultTranscationAdvice" pointcut-ref="allServiceMethods" />
</aop:config>

Innerhalb der Service Methoden brauchen wir uns nun keine Gedanken mehr um offene oder geschlossene Transaktionen machen. Wir bewegen uns in jedem Fall innerhalb einer Transaktion. Sollte es zu einem Fehler kommen, so wird ein Rollback auf allen Änderungen der Methode durchgeführt.
Erst beim Verlassen der Service Methode müssen wir berücksichtigen, dass Objekte ggf. nicht mehr an eine Session gebunden sind (hier kann es dann zu Problemen mit LazyLoading o.ä. kommen).


Felix Breske


Spring Singleton

Donnerstag, 20. November 2008

Wer in Spring-Konfigurationen seiner Anwendung Beans als Singletons definiert, sollte sich darüber im Klaren sein, dass er mit diesem Bean-Scope keine Umsetzung des Singleton-Entwurfsmusters der Gang of Four erreicht.
Eine Spring Bean, die mit dem Singleton-Scope versehen wurde (default-Konfiguration), ist lediglich im Kontext einer IOC-Container-Instanz einzigartig. D.h. dass es durchaus mehrere Instanzen einer solchen Singleton Bean geben kann. Nämlich genau dann, wenn man sich mehrere IOC-Container-Instanzen in Form von ApplicationContext-Instanzen erzeugt. Jede dieser Instanzen verwaltet dann ihre eigenen Beans.
Ein Verständnis dieses Konzepts hilft dabei, schwierig aufzufindende Fehler zu vermeiden.
Möchte man zum Beispiel in seiner Anwendung an irgendeiner Stelle Zugriff auf eine Spring Bean haben und diesen nicht per Dependency Injection ermöglichen, so muss man sich den Zugriff mit Hilfe einer ApplicationContext-Instanz verschaffen. Erstellt man sich nun aber diese Instanz zum Beispiel mittels

new ClassPathXmlApplicationContext(path);

dann muss man beachten, dass damit eine neue IOC-Container-Instanz erzeugt wird, deren Singleton Beans nicht die sind, die von der Anwendung für die Dependency Injection verwendet werden. Denn hierfür wird eine IOC-Instanz benutzt, die beim Anwendungsstart generiert wird.
Definiert man nun zum Beispiel Beans, die eine Datenbankverbindung erzeugen, so führt die gerade beschriebene Art und Weise der Zugriffsverschaffung auf diese Beans dazu, dass mit jeder ApplicationContext-Instanz auch jeweils neue Verbindungen geöffnet werden, bis es irgendwann zu viele für den Datenbank Server sind.

Es ist ratsam, Spring Bean Instanzen nur per Dependency Injection zur Verfügung zu stellen. Falls dies nicht möglich ist, so sollte man sich bemühen, die in seiner Anwendung bereits automatisch erzeugte und benutzte ApplicationContext-Instanz zu verwenden. Dies ist bei Web-Anwendungen zum Beispiel möglich durch:

ContextLoader.getCurrentWebApplicationContext();

Christian Borkowski