JasperReports mit JavaBeans #
JasperReports ist ein sehr empfehlenswerter Report-Generator in Java. Ich habe bei einigen Gelegenheiten Erfahrungen damit gesammelt und möchte hier beschreiben, wie man JavaBeans als Datenquellen einsetzt.
Die ersten Schritte mit JasperReports werden wohl die meisten mit einer Datenbankverbindung als Datenquelle machen. Allerdings muss das nicht so sein. Man kann auch aus internen Datenstrukturen heraus einen Report erzeugen. Hierzu benutzt man in Java normale JavaBeans, die man in einem Array oder einer Collection sammelt.
Ein weiteres Beispiel ist die Benutzung mit meiner Datenbank-Schicht SwingingBeans. Diese Bibliothek modifiziert Beanklassen und Collections so, daß man mit ihnen wie mit ganz normalem Java arbeiten kann, obwohl man in Wirklichkeit mit Daten aus einer Datenbank arbeitet.
Vieles des hier geschriebenen gilt übrigens auch, wenn man eine andere, ganz eigene Datenquelle (ohne Beans) implementieren und benutzen möchte.
Vorarbeit in meinem eigenen Programm #
Ich benötige eine Klasse, die eine JasperReports-Datenquelle zur Verfügung stellt. Dazu implementiert man entweder einen JRDataSourceProvider (beschrieben im iReports-Guide, Chapter 9, "JasperReports Datasource Providers") oder schreibt eine Factory für eine JRDataSource (beschrieben im iReports-Guide, Chapter 9, "Using JavaBeans Set Datasources"). Der Provider machte mir auf den ersten Blick einen moderneren und weiterentwickelteren Eindruck. Das täuscht allerdings. Die Entscheidung für JavaBeans als Medium sagt ja eigentlich schon alles aus, so das man weitere Informationen z.B. über Felder gar nicht mehr benötigt. Der einzige verbleibende Vorteil ist der, daß das JasperReport Template als Parameter übergeben wird. Alles in allem ist der Provider besser geeignet, wenn man etwas anderes als Beans benutzt. Ich hatte zuerst einen fertigen Provider erzeugt, habe mich dann aber doch entschieden, ihn wieder zu löschen und habe eine viel einfachere Factory erzeugt.
Diese Factory wird übrigens insbesondere für den Report-Generator iReports benötigt. Ruft man später JasperReports direkt aus seinem Code auf, kann man auf die extra Factory-Methode verzichten und direkt die Datasource aus seiner Collection erzeugen. Das bedeutet dann umgekehrt, daß die Factory-Methode möglichst gute Testdaten liefert, um bei der Erstellung des Reports mit iReport alle Varianten seiner Daten sehen zu können.
Eine Factory für JRDataSource-Objekte ist im Grunde nichts anderes als eine Klasse mit einer statischen Methode, die ein Objekt vom Typ Collection oder JRDataSource zurückgibt. Diese JRDataSource sollte in unserem Fall eine JRBeanCollectionDataSource sein. Deren Konstruktor übergibt man lediglich unsere Collection (die man ggf. vorher vorbereitet hat) und fertig.
Ein bisschen Arbeit habe ich mir in der Factory-Methode übrigens mit dem Abfangen von Exceptions gemacht. Insbesondere sollte man am besten Debug-Ausgaben sowie Exceptions und Stacktraces in eine Datei ausgeben, weil iReport gerne alle Fehlermeldungen verschluckt, wenn man die Factory später aus dem Reportgenerator heraus nutzt.
Vorarbeit in iReport #
Nun starte ich den Reportgenerator iReport. Als allererstes muss ich in "Optionen / Klassenpfad" sowohl die *.class-Files meines Projektes als auch alle benötigten Bibliotheken angeben. Leider ist es nicht so ganz einfach, herauszufinden, wenn etwas fehlt (siehe unten für Debugging).
Besonderheit mit HSQLDB #
Mit iReport wird einehsqldb.jar in der Version 7.7.x ausgeliefert. Aktuell ist mindestens 8.x. Die Unterschiede waren für mich recht gravierend, so daß ich einfach aus dem lib-Verzeichnis von iReport die hsqldb.jar gelöscht und durch eine neue ersetzt habe. Danach klappte alles wunderbar.
Debugging #
Debugging ist leider nicht so einfach. iReport neigt dazu, alle Meldungen unseres Codes komplett zu verschlucken. Gerade dann, wenns spannend wird, wird die Standardausgabe verbogen und Logger umgeleitet. Auf jeden Fall hilft es, iReport von der Kommandozeile aus zu starten. Ansonsten sollte man überlegen, an verdächtigen Stellen eigene Logging-Befehle (ohne das normale Java-Logging) in eine Datei oder eine Pipe einzufügen.
Erzeugen einer Datenquelle in iReport #
Bei "Daten/Datenquellen -> neu" kann man sich verschiedene Sorten von Datenquellen aussuchen. Neben "JRDataSourceProvider", das ich zuerst getestet habe, nehmen wir jetzt "JavaBeans Datenquelle", wenn unsere Factory-Methode eine Collection zurückgibt (für die Faulenzer unter uns) oder '"Benutzerdefinierte JRDataSource"', wenn unsere Methode bereits eine fertige Datasource erzeugt hat (für die Helden unter uns). Im folgenden Dialog muss lediglich die Factory-Klasse und -Methode eingetragen werden und fertig ist die Datenquelle.
Erzeugen eines Reports #
Diese Anleitung geht davon aus, daß wir echte Männer sind und einen neuen, leeren Report erzeugen. Natürlich geht das alles auch mit dem Assistenten von iReport (der für die Warmduscher). Auch damit sollte es kein Problem sein, ich habe es allerdings noch nicht ausprobiert.
Nachdem wir einen neuen (leeren) Report erzeugt haben, stellt sich nun die Frage, wie wir die in unserer Datenquelle vorhandenen Daten benutzen. Dafür sind zwei Schritte nötig: Auswahl der Datenquelle (sollte bereits geschehen sein) und Anlage von Feldern. Felder sind die eigentliche Schnittstelle zwischen der Datenquelle und dem Report. Daten, für die kein Feld angelegt ist, existieren im Report nicht. Mit "Daten / Berichts Abfrage" öffnen wir den Dialog zu erzeugen von Feldern. Dieser bietet mehrere Registerkarten. Die erste "Berichtsabfrage" ist nur für SQL-Datenquellen von Belang. Die dritte "DataSource Provider" ist die richtige Wahl, wenn man einen solchen Provider implementiert hat, der nämlich seine Felder fix und fertig übergeben kann.
Wir benutzen die zweite Registerkarte "JavaBean Datenquelle" (übrigens unabhängig davon, ob wir eine "JavaBean" oder eine "benutzerdefinierte" Datenquelle eingerichtet haben - es kommt darauf an, ob unsere Datenquelle richtige Beans enthält). Jetzt kommt ein bisschen Arbeit, weil wir nämlich den vollqualifizierten Klassennamen unserer Bean eingeben müssen. Danach kommt die Magie, indem wir auf "Attribute lesen" klicken und staunen.
Wir brauchen jetzt eine Property nur anzuwählen und auf "ausgewählte Felder hinzufügen" klicken und wir haben unsere Feld-Definition! Eine Besonderheit gibt es noch, wenn eine Property auf eine andere JavaBean zeigt: Durch einen Doppelklick kann man direkt auf die Properties dieser indirekten Objekte zugreifen. Man kann also z.B. aus einer Lieferschein-Datasource direkt ein JasperReports-Feld "strasse" mit der Beschreibung "adresse.strasse" erhalten, wenn es in der Lieferschein-Klasse eine Methode ~getAdresse() gibt und die Adresse eine Methode ~getStrasse() hat.
Ich hatte etwas Ärger mit den Rückgabewerten der Felder. Mit dem oben erwähnten "Klick & fertig" wird als Datentyp der Rückgabetyp der Property genommen (logisch!). Allerdings gibt es ein paar von iReport vorgeschlagene Feldtypen und es ist einfacher ist, diese zu verwenden. Dementsprechend kann man die Feldklasse Objekt angeben. Ich konnte zwar im Menü per "Edit / Report Import Directives" meine eigenen Klassen angeben. Dies führte dazu, daß ich in meinen Ausdrücken diese Klassen direkt benutzen konnte, wie man sich das vorstellt. Der Report kam richtig heraus, aber dennoch bekomme ich im "Problems"-Fenster eine ClassNotFoundException. Da mich diese nervt, habe ich vorerst auf meine eigenen Datentypen verzichtet. Wer hierzu genaueres sagen kann, kann diesen Absatz gerne ergänzen!
Anpassung von Datentypen #
Wie oben kurz erwähnt, ergeben meine Properties manchmal ganz eigene Datentypen. Darunter sind einige, die eigentlich ganz normal, aber eben doch ganz anders sind. So ist mein FixedDecimalbigDecimalValue() im Grunde sowas wie ein eingeschränktes BigDecimal. Oder ein DayDate ist sowas wie ein Java-Date. Da bietet es sich an, die erweiterten Fähigkeiten von JasperReports, was die Formatierung von Zahlen und Daten anbetrifft, zu nutzen und diese Objekte vor der Ausgabe umzuwandeln. Jetzt zahlt es sich aus, daß ich ein Held war und meine Collection in eine JRBeanCollectionDataSource umgewandelt habe. Ich konnte ganz einfach diese nehmen, ableiten und dabei die Methode ~getFieldValue() überladen. Dort habe ich dann Objekte der speziellen Datentypen einfach konvertiert.
Leider betrifft diese Anpassung nur die Datasource und nicht die eigentliche JavaBean, auf der aber die Magie der automatischen Feld-Definition beruht. Deshalb muss man den Datentyp dieser Felder nach der automatischen Übernahme noch von Hand anpassen: Wo DayDate steht, muss Date hin.
Subreports #
Subreports aus einer Javabean sind auch ganz einfach zu bewerkstelligen. Sagen wir mal, ich habe ein Datenfeld "umsatzzeilen", dessen Datentyp "Collection" ist. In den Einstellungen zum Subreport stelle ich dann als "Data Source Exporession" folgendes ein:
new de.bayen.database.printing.SwingingBeansDataSource($F{umsatzzeilen}) }}
Statt meiner eigenen, abgeleiteten Wrapper-Klasse kann hier natürlich auch JRBeanCollectionDataSource angegeben werden.
-- ThomasBayen
Edit:
iReport von Sourceforge hat mir gerade die SQL-Erzeugung enorm beschleunigt, dranbleiben, jetzt teste ich BIRT unter eclipse. --MarkusMonderkamp am 14.11.2008