Archiv für die Kategorie ‘Java’

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


Excel-Dateien mit Apache POI bearbeiten

Montag, 07. Juni 2010

Ein für viele Entwickler eher unerfreuliches Thema ist die Arbeit mit Microsoft Office Dateien in Java. Insbesondere Excel-Dateien werden von Fachabteilungen gerne als Ein- oder Ausgabeformat gesehen. Oft kann man sich dann auf einen Import bzw. Export via csv einigen. Wenn dies nicht möglich ist, lohnt es sich, einen Blick auf Apache POI zu werfen. Das Projekt hat sich der MS-Office-Thematik angenommen und gliedert sich in mehrere Subprojekte, die sich jeweils mit den einzelnen Office-Formaten beschäftigen. Viele dieser Subprojekte sind noch in einem sehr frühen Stadium und daher mit Vorsicht einzusetzen. Als ziemlich ausgereift kann man hingegen die Subprojekte HSSF und XSSF bezeichnen. HSSF liefert Unterstützung für das alte Excel-Dateinformat, das bis einschließlich Excel 2003 eingesetzt wurde. XSSF dagegen liefert die Unterstützung für das neuere XML-basierte Format, das mit Excel 2007 eingeführt wurde. Im folgenden soll anhand eines kurzen Beispiels ein erster Einblick in die Arbeit mit HSSF/XSSF gegeben werden. Dabei wird die Version 3.6 von POI eingesetzt.

HSSF und XSSF arbeiten mit einer Reihe von gemeinsamen Interfaces, die die Bestandteile einer Excel-Datei repräsentieren. Die folgenden Interfaces kommen in unserem Beispiel zum Einsatz:

  • Workbook repräsentiert eine Excel-Datei
  • Sheet repräsentiert eine einzelne Tabelle
  • Row repräsentiert eine einzelne Tabellenzeile
  • Column repräsentiert eine einzelne Tabellenspalte
  • Cell repräsentiert eine einzelne Tabellenzelle

Um nun eine einfache Excel-Tabelle zu erstellen und in einer xls-Datei zu speichern gehen wir wie folgt vor:

Workbook workbook;
File outputFile;
// wir wollen eine Excel 97 xls-Datei erzeugen, verwenden also HSSF
workbook = new HSSFWorkbook();
outputFile = new File("c:/Dateiname.xls");
 
// bei der Erzeugung der Daten arbeiten wir nur noch mit den Interfaces und damit
// formatunabhängig:
Sheet sheet = workbook.createSheet("Einkäufe");
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellType(Cell.CELL_TYPE_STRING);
cell.setCellValue("Ware");
cell = row.createCell(1);
cell.setCellType(Cell.CELL_TYPE_STRING);
cell.setCellValue("Preis");
row = sheet.createRow(1);
cell = row.createCell(0);
cell.setCellType(Cell.CELL_TYPE_STRING);
cell.setCellValue("Brot");
cell = row.createCell(1);
cell.setCellType(Cell.CELL_TYPE_NUMERIC);
cell.setCellValue(1.19);
row = sheet.createRow(2);
cell = row.createCell(0);
cell.setCellType(Cell.CELL_TYPE_STRING);
cell.setCellValue("Butter");
cell = row.createCell(1);
cell.setCellType(Cell.CELL_TYPE_NUMERIC);
cell.setCellValue(0.89);
row = sheet.createRow(3);
cell = row.createCell(0);
cell.setCellType(Cell.CELL_TYPE_STRING);
cell.setCellValue("Summe");
cell = row.createCell(1);
cell.setCellType(Cell.CELL_TYPE_NUMERIC);
// wir können mit setCellFormula auch Formeln für einzelne Zellen angeben
cell.setCellFormula("SUM(B2:B3)");
 
// Schreiben der Datei
FileOutputStream fileOutputStream = new FileOutputStream(outputFile);
workbook.write(fileOutputStream);
fileOutputStream.close();

Um stattdessen eine xslx-Datei (Excel 2007) zu erzeugen müssen wir im obigen Beispiel lediglich die Zeilen

workbook = new HSSFWorkbook();
outputFile = new File("c:/Dateiname.xls");

durch

workbook = new XSSFWorkbook();
outputFile = new File("c:/Dateiname.xlsx");

ersetzen. Der restliche Code bleibt unverändert. Ziemlich einfach, oder?

Ich wünsche viel Spaß beim Ausprobieren!


Malte Wulf


Hibernate Proxies und Polymorphismus

Freitag, 07. Mai 2010

Beim LazyLoading mit Hibernate werden die eigentlichen Entitäten durch Proxy Objekte ersetzt. Diese Proxy Objekte werden erst dann initialisiert, wenn auf sie zugegriffen wird. Hibernate erstellt eine Unterklasse der Entität und überschreibt alle Methoden der Elternklasse. Wird eine dieser Methoden aufgerufen, so delegiert der Proxy die Anfrage an die Datenbank und füllt die Entität aus der Datenbank. Diese Objekte lassen sich mit instanceof auf ihren Typ prüfen und sie können in ihren Ursprungstyp gecastet werden, da es sich ja bei dem Proxy um eine Kindklasse der eigentlichen Entität handelt.

Wenn jetzt aber Objekthierarchien und Polymorphismus mit ins Spiel kommen, dann wird es schwieriger.

Ein Beispiel:

public class Vehicle{
...
}
 
public class Car extends Vehicle{
   public void reFuel(){};
}
 
public class Bicycle extends Vehicle{
...
}
 
public class Garage {
   Set<Vehicle> vehicle = new HashSet<Vehicle>();
   public Set<Vehicle> getVehicle(){
       return vehicle;
   }
}

Alle Autos sollen nun aufgetankt werden.
Eine einfache Möglichkeit wäre:

for(Vehicle v : garage.getVehicle()){
   if(v instanceof Car){
       Car car = (Car)v;
       car.reFuel();
   }
}

Wenn jetzt aber Hibernate mit LazyLoading eingesetzt wird, so werden wir leider bei nächster Gelegenheit mit dem Auto ohne Benzin liegen bleiben. Hibernate ersetzt die einzelnen „Vehicle“ durch Proxies, also durch Kindklassen der Klasse “Vehicle”. Da der Proxy dann aber kein „Car“ ist, schlägt das instanceof fehl und das Auto wird nicht aufgetankt. Selbst wenn wir uns sicher sind, dass nur Autos in der Garage stehen (also auf das instanceof verzichten), schlägt spätestens der Cast nach „Car“ mit einer ClassCastException fehl.

Um sowohl auf das „instanceof“ wie auch auf den Cast verzeichten zu können, können wir das Visitor Pattern einsetzen.

public interface VehicleVisitor{
   void visit(Car car);
   void visit(Bicycle bicycle);
}

Für die Bequemlichkeit einen Adapter:

public VehilceVisitorAdapter implements VehicleVisitor{
   void visit(Car car){ /* leer */};
   void visit(Bicycle bicycle){/* leer */};
}

Unsere Fahrzeuge müssen den Visitor entgegennehmen:

public class Vehicle{
   public void accept(VehilceVisitor visitor){
       // leer
   }
...
}
 
public class Car extends Vehicle{
   public void reFuel(){};
   public void accept(VehilceVisitor visitor){
       visitor.visit(this);
   }
}

Jetzt kann die ursprüngliche Schleife angepasst werden:

VehicleVisitor fuelVisitor = new VehicleVisitorAdapter(){
     public void visit(Car car){
        car.reFuel();
     }
};
 
for(Vehicle v : garage.getVehicle()){
   v.accept(fuelVisitor);
}

Jetzt sind dann auch wirklich alle Autos aufgetankt und wir brauchen keine Angst mehr vor Hibernate Proxies haben. Nebenbei haben wir auf das „hässliche“ instanceof verzichtet und brauchen auch nicht mehr die Objekte zu casten.


Felix Breske