www.alexander-merz.com | Alexander Merz

JasperReports-Einführung Teil 1

JasperReports ist ein freies, leistungsfähiges Reporting-Werkzeug für Java zur Erzeugung von Berichten. Eine losen Reihe von Artikeln gibt eine Einführung in die Arbeit mit der JasperReports-Bibliothek.

Die Quelltexte der Java-Klassen, der XML-Dateien und der SQL-Skripte finden sich unter www.alexander-merz.com/jr-examples.zip.

Die JasperReports-Bibliothek ist äußerst umfangreich:

  • Sie definiert einen umfangreichen XML-Dialekt zur Beschreibungen von Berichten. Die Beschreibung ist nicht nur eine reine Vorlage, sondern darf gleichfalls einfache Berechnungen und Bedingungen enthalten, wie man sie auch von Tabellenkalkulationen kennt.
  • JasperReports kann zum Befüllen der Vorlage Datenanfrage an eine SQL-Datenbank durchführen oder alternativ können eigene Datencontainer bereitgestellt werden. Ein manuelles Befüllen der Vorlage ist nicht erforderlich.
  • Es werden viele populäre Formate für die Ausgabe unterstützt, u. a. PDF und HTML.

Hinzukommt, dass mit iReport Designer ein freies und leistungsfähiges Werkzeug zur Definition und Erzeugung von Reporten bereitsteht, das kommerziellen Reporting-Werkzeugen in nichts nach steht.

Dieses Mini-Tutorial gibt einen Einblick in das grundlegende Arbeiten mit JasperReports.

Installation

Um JasperReports in eigenen Projekten einsetzen zu können, benötigt man neben dem Jar-Archiv für JasperReports auch die des iText-Projektes für die PDF-Erzeugung und einige Jar-Archive des Jakarta Common-Projektes des Apache Projects. Die einfachste Variante alle mit einem Schlag zu bekommen, ist die Installation von iReport Designer und die benötigten Jar-Dateien aus dessen lib-Verzeichnis zu kopieren.

Quellen:

Die Definition

Obwohl mit iReport ein grafisches WYSIWYG-Werkzeug zur Erzeugung von Berichten bereitsteht, werden wir hier die Definition von Hand erzeugen. Wenn man mit dem erforderlichen XML vertraut ist, ist der Einstieg in iReport Designer relativ einfach – umgekehrt gilt das nicht.

Das Pflichtprogramm sieht folgendermaßen aus:

<?xml version="1.0" encoding="UTF-8"  ?>
<!DOCTYPE jasperReport PUBLIC "...">

<jasperReport name="Example1">
...
</jasperReport>
Dieses Beispiel speichern wir in der Datei Example1.jrxml. Die Dateiendung "jrxml" ist für die Beschreibungsdateien üblich. Würden wir das Beispiel von JasperReports kompilieren lassen, erhalten wir einfach nur eine leere Seite erhalten. Das ändern wir im nächsten Schritt.

Für das Einfügen von Texten stehen zwei Möglichkeiten zur Verfügung:

  • statischer Text (<staticText>) für unveränderte Textinhalte, z.B. Überschriften.
  • Textfelder (<textField>) für Text, der bei der Erzeugung eingefügt werden soll.
Bei beiden Tags handelt es sich um Container-Tags, d. h. ihr eigentlicher Inhalt und dessen Aussehen und Eigenschaften wird durch weitere Tags bestimmt.

Für unser Beispiel wollen wir den Text zur Laufzeit setzen, müssen ihn also einem <textField> platzieren. Der Text an sich wird über das <textFieldExpression>-Tag gesetzt, jede <textFieldExpression> ist an eine Java-Klasse geknüpft, die für die Formatierung der Ausgabe verantwortlich ist:

<jasperReport name="Example1">
  <textField >
    <textFieldExpression class="java.lang.String">
     ...
    </textFieldExpression>
  </textField>
</jasperReport>

Wo sich im Beispiel die drei Punkte befinden, fügen wir als nächstes einen Parameter mit dem Namen aParameter ein:

<textFieldExpression class="java.lang.String">
  <![CDATA[ $P{aParameter} ]]>
</textFieldExpression>
Text der Form Dollarzeichen-Buchstabe-Geschweifte Klammer ($P{...}) stellen Platzhalter dar. Der Buchstabe kennzeichnet die Art des Platzhalters: ein großes P ist ein Parameter. Es gibt noch weitere Formen von Platzhaltern.

Es ist nicht gefordert, den Platzhalter in einer CDATA-Sektion zu plazieren, allerdings ist es sinnvoll. Für XML-Unerfahrene: Der Inhalt einer XML-Datei, der in <[CDATA[ ]]> eingeschlossen wird, findet vom XML-Parser keine Beachtung. Er wird einfach an die Anwendung weitergegeben und zwar genauso wie er ist - inklusive Leerzeichen und Zeilenumbrüchen.

Allerdings müssen wir JasperReports noch mitteilen, dass ein Parameter mit dem Namen aParameter überhaupt existiert. Dies geschieht mit dem Tag <parameter>, den wir am Anfang platzieren:

...
<jasperReport name="Example1">
 <parameter name="aParameter" class="java.lang.String"/>
 <textField >
  ...
Auch hier ist wieder das class-Attribute notwendig, welches den Typ des Parameters.

Würden wir das Beispiel versuchen zu kompilieren, dann würden wir scheitern. Denn JasperReports wüsste nicht, wohin es das Textfeld setzen sollte.

Eine Berichtsseite besteht in JasperReports aus mehreren Abschnitten, für uns relevant ist aber erstmal nur der <detail>-Abschnitt.

Unser Text soll als Spalteninhalt fungieren, deshalb schließen wir den <textField>-Container in ein <detail>-Tag ein:

<jasperReport name="Example1">
 <parameter name="aParameter" class="java.lang.String"/>
 <detail>
   <textField >
    <textFieldExpression class="java.lang.String">
     $P{aParameter}
    </textFieldExpression>
   </textField>
 </detail>
</jasperReport>

Aber auch durch diese Ergänzungen sind wir noch nicht fertig. Jedes Seitenelement kann in Bänder (<band>) unterteilt werden. Mindestens eins müssen wir definieren:

...
<detail>
  <band>
    <textField >
     ...
    </textField>
  </band>
</detail>

Jetzt können wir endlich die Definition erfolgreich kompilieren und daraus ein PDF erzeugen.

Anmerkung: In der Beispiel-Datei Example1.jrxml befinden sich noch einige zusätzlich Attribut-Angaben. Diese werden wir in einem weiteren Artikel genauer behandeln.

Die Java-Klasse

Als ersten Schritt muss die XML-Datei in ein internes JasperReports-spezifisches Format überführt werden ("Kompilierung"). Dafür ist der JasperCompileManager zuständig. Als zweites kümmert sich der JasperFillManager darum, den Bereicht mit Werten zu füllen. Und als drittes und letztes wird mit dem JasperExportManager das PDF erzeugt.

Was sich kompliziert anhört, sind allerdings tatsächlich nur drei Methodenaufrufe – sieht man vom Überbau ab.

Der Überbau besteht darin, die notwendigen Variablen zu definieren, und eine HashMap zu erzeugen, die unseren Parameter aufnimmt. Die Klasse selbst nennen wir Example1:

public class Example1 {
   public static void main(String[] args) {
      JasperReport jasperReport;
      JasperPrint jasperPrint;
      HashMap<String, String> parameter =
                new HashMap<String, String>();
   }
}

Setzen wir als nächstes den Parameter, der Schlüssel des Eintrags in der HashMap entspricht den Namen des Parameters. Die Klasse des Schlüsselobjektes muss mit dem class-Attribut im <parameter>-Tag übereinstimmen. Der Wert bzw. Objekt des Eintrags muss mit dem class-Attribut des <textFieldExpression> übereinstimmen.

parameter.put("aParameter", "Hallo Welt");

Nun die besagten drei Zeilen:

jasperReport =
   JasperCompileManager.compileReport("JRXML/Example1.jrxml");
jasperPrint =
   JasperFillManager.fillReport(jasperReport,
                          parameter, new JREmptyDataSource());
JasperExportManager.exportReportToPdfFile(jasperPrint,
                          "output/Example1.pdf");

Die erste und letzte Zeile benötigen hoffentlich keine nähere Erläuterung. Es irritiert vermutlich die zweite Zeile mit ihrer fillReport()-Methode. Der zweite Parameter enthält die HashMap mit den Parameter-Einträgen, aber der dritte Parameter mit einer ominösen leeren Datenquelle?

Bei den Platzhaltern wurde bereits angedeutet, dass es nicht nur Parameter für variable Textinhalte gibt. JasperReports kennt auch Felder (Fields). Felder entsprechen Spaltenbezeichnungen in einem Datenobjekt, wie z.B. Ergebnismengen von Datenbank-Abfragen. Da wir keine Felder in unserem Report haben, übergeben wir hier eben einfach nur eine leere Datenquelle. Diesen Thema widmen wir uns im nächsten Teil ausführlicher.

Datenzugriffe

Die Inhalte von Berichten bzw. Reports stammen zumeist aus Datenbanken. JasperReports bietet neben der Möglichkeit Datenbanken direkt anzubinden, aber auch Unterstützung für andere Formate wie z.B. XML und CSV-Dateien. Über ein entsprechendes Interface können aber auch Datenquellen erschlossen werden, die JasperReport nicht direkt unterstützt. Widmen wir uns zuerst der Datenbank-Integration.

Im Beispiel gehen wir von einer Datenbank reports aus, die eine Tabelle persons enthält mit den Spalten firstname, lastname und birthday.

JasperReports kann die Datenbankanfrage selbstständig durchführen, wenn eine entsprechende Klasse für den Zugriff übergeben wird. Unsere Java-Klasse Example2 muss gegenüber dem ersten Beispiel deshalb nur wenig erweitert werden.

Wir ergänzen die Klassen-Variable conn, welche die Datenbank-Klasse aufnimmt und weisen dieser Variable eine entsprechende Instance zu. Hier verwenden wir eine MySQL-Datenbank:

Connection conn;

Class.forName( "com.mysql.jdbc.Driver" );
conn = DriverManager.getConnection(
             "jdbc:mysql://localhost/reports",
             "report",
             "report" );

Die Variable conn übergeben wir einfach als dritten Parameter an fillReport():

jasperPrint = JasperFillManager.fillReport(jasperReport, parameter, conn);

Damit ist der Java-Part erledigt. Was allerdings offensichtlich fehlt, ist das SQL-Statement! Dieses definieren wir in der JRXML-Datei.

An der gleichen Stelle, wo im ersten Beispiel die Parameter-Definition stand, setzen wir ein <queryString>-Tag. Es enthält das Statement:

<queryString>
<![CDATA[SELECT firstname, lastname, birthday FROM persons]]>
</queryString>

Darauf folgen die Definition der Felder wie bei Parametern.

<field name="firstname" class="java.lang.String"/>
<field name="lastname" class="java.lang.String"/>
<field name="birthday" class="java.sql.Timestamp"/>
Interessant ist die letzte Zeile, statt eines Strings weisen wir dem Feld birthday einen Zeit-Datentyp zu. Auf diese Weise können wir später spezielle Datums- und Zeit-Formatierungen auf das Datum anwenden.

Die Formulierung des jeweiligen <textField>-Elementes für firstname und lastname unterscheidet sich nicht vom ersten Beispiel, lediglich der Platzhalter muss angepasst werden: statt $P{...} verwenden wir $F{...}, hier gezeigt für firstname:

 <textField>
  ...
  <textFieldExpression class="java.lang.String">
   <![CDATA[$F{firstname}]]>
  </textFieldExpression>
 </textField>

Für birthday sind die Unterschiede schon größer. Erstens geben wir ein Muster an, wie der Eintrag dargestellt werden soll. Zweitens müssen wir für das class-Attribute die Timestamp-Klasse angeben:

 <textField  pattern="dd.MM.yyyy">
  ...
  <textFieldExpression class="java.sql.Timestamp">
   <![CDATA[$F{birthday}]]>
  </textFieldExpression>
 </textField>
Der pattern-Parameter gibt eine Formatierung an. In der Datenbank wurde die birthday-Spalte mit einem Datums-Zeit-Datentyp angelegt. Die Standard-Formatierung für diesen Wert in der Ausgabe ist in der Ausgabe von der Datenbank und deren Einstellungen abhängig. Bei MySQL und deutschen Systemeinstellungen würde der Datumswert im PDF so ausgegeben: "25.07.1979 00:". Die letzten drei Stellen verweisen auf eine Uhrzeit – die für das Datum der Geburt sinnlos ist. Um nun ein einheitliches Format zu erreichen, geben wir explizit ein Formatierungsmuster mit dem pattern-Atribut vor. Damit sind wir unabhängig von den Systemeinstellungen und der Datenbank.

Im Beispiel-Quellcode ist auch eine SQL-Datei enthalten, welche die Tabelle reports mit zwei Datensätzen enthält. Führen wir nun das Java-Programm aus, wird ein PDF erzeugt, in dem sich beide Datensätze befinden. An dieser Stelle kommt unweigerlich die Frage auf, wieso eigentlich.

Wer mit Template- bzw. Vorlagen-System bereits Erfahrung hat, wird eigentlich erwarten, dass irgendwo ein Art Block definiert werden muss, dessen Inhalt für jeden gefunden Datensatz wiederholt wird. Genau diesen Block gibt es auch: Es ist der <detail>-Abschnitt in einem JRXML-Dokument, den wir die ganze Zeit bereits verwenden. Wir werden auf den Seitenaufbau eines Report-Dokumentes in einem weiteren Artikel noch näher eingehen. Um es vorweg zu nehmen: Natürlich gibt es Situationen, in denen dieser eine Block nicht reicht – dafür bietet JasperReports aber gleichfalls Lösungen an.

An diesem Punkt wissen wir wie man ein einfaches Dokument erzeugt und es mit Daten aus einer Datenbank befüllt. In weiteren Artikeln widmen wir uns den Layoutmöglichkeiten und anderen Formen des Befüllen des Reports mit Daten.


JasperReports-Einführung Teil 1
JasperReports-Einführung Teil 2 – Abschnitte und Größenangaben
JasperReports-Einführung Teil 3 - Texte und Schriften
JasperReports Einführung Teil 4 - Die Datenquellen
JasperReports Einführung Teil 5 - Die Ausgabeformat