www.alexander-merz.com | Alexander Merz

JasperReports Einführung Teil 4 - Die Datenquellen

JasperReports bietet bereits verschiedenen Klassen für den Datenzugriff, schauen wir uns einige davon an.

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

XML

Liegen die Daten in einem XML-Format vor, dann können wir die Klasse net.sf.jasperreports.engine.data.JRXmlDataSource einsetzen. Die Verwendung weicht etwas von den übrigen Klassen für Datenquellen ab.

Im Beispiel Example9.java wird die XML-Datei artikel.xml geladen. Sie enthält ein XML-Docbook-Dokument bestehend aus mehreren Sektionen und Absätzen. Wir wollen JasperReports benutzen, um daraus ein PDF zu erzeugen - mal eine andere Art der Verwendung eines Reporting-Werkzeugs. Das PDF soll die Überschrift der Sektionen und den zugehörigen Absatz enthalten.

Im Konstruktor der Klasse JRXmlDataSource übergeben wir neben dem Namen der XML-Datei auch einen XPath-Ausdruck. Dieser XPath-Ausdruck definiert die zu beachtenden Tags. In diesem Beispiel sind es jeweils die <sect1>-Tags unterhalb von <article>. Siehe Example9.java:

JRXmlDataSource xml = new JRXmlDataSource(
                            new File("data/artikel.xml"),
                            "/article/sect1");
...
jasperPrint = JasperFillManager.fillReport(jasperReport,
                            parameter, xml);

Welche Daten konkret ausgelesen werden, ist dann abhängig von der Angabe im <field>-Tag des XML-Templates. Entweder werden die darzustellenden Daten aus untergeordneten Tags geholt oder aus einem Attribut der zu beachtenden Tags. In Example9.jrxml holen wir die Daten aus dem Inhalt der Tags <title> und <para> in der Docbook-Datei.

Dazu müssen wir die Field-Definition wie folgt schreiben:

<field name="title" class="java.lang.String">
  <fieldDescription>title</fieldDescription>
 </field>
 <field name="para" class="java.lang.String">
  <fieldDescription>para</fieldDescription>
</field>

Innerhalb von <fieldDescription> geben wir das Tag an, dessen Inhalt eingesetzt werden soll. Es findet kein automatische Zuordnung von Feld-Platzhaltern und Tag-Namen statt. Diese müssen wir über obige Definition herstellen.

Der Grund für dieses etwas umständliche Vorgehen sehen Sie bei Beispiel Example10.jrxml. Dort lesen wir den Titel nicht aus dem <title>-Tag aus, sondern aus dem Attribut id des <simplesect>-Tags. Für Attribute stellen ein @ davor:

<field name="title" class="java.lang.String">
 <fieldDescription>@id</fieldDescription>
</field>
<field name="para" class="java.lang.String">
 <fieldDescription>para</fieldDescription>
</field>

CSV

Dateien im Comma Separated Value-Format schwirren immer noch zur Genüge herum und die meisten Programme beherrschen es als Export-Format. Über die Klasse net.sf.jasperreports.engine.data.JRCsvDataSource sind solche Dateien einfach zu verwenden. Gehen wir von folgender Datei aus (data.csv):

Merz;Alex;
Friend;Ron;

Im Konstruktor der Klasse übergeben wir den Namen der Datei; des weiteren müssen wir noch in über entsprechende Methoden das Trennzeichen übergeben und wie unsere Spalten bzw. Felder benannt sind (Example11.java):

String[] header = {"nachname", "vorname"};
JRCsvDataSource csv = new JRCsvDataSource(new File("data/data.csv"));
csv.setFieldDelimiter(';');
csv.setColumnNames(header);
In der XML-Report-Definition müssen wir nichts weiter beachten, bis auf die Benennung der Felder, sie muss übereinstimmen mit den Spalten-Namen.

Der Name der Felder kann auch in der CSV-Datei angegeben werden (head_data.csv):

nachname;vorname;
Merz;Alex;
Friend;Ron;
In diesem Fall kann die Klasse die Feldnamen selbstständig auslesen, wenn wir sie entsprechend anweisen (Example12.java):
JRCsvDataSource csv = new JRCsvDataSource(new File("data/head_data.csv"));
csv.setFieldDelimiter(';');
csv.setUseFirstRowAsHeader(true);

Collection/Array von Maps

Liegen die Daten bereits in Form einer Java-Datenstruktur vor, können auch diese einfach verwendet werden. Vorraussetzung ist, dass die Datensätze in einer Struktur vorliegen, die das Interface Collection bzw. Array implementieren, und das ein Datensatz in einer Struktur gespeichert ist, der das Map-Interface umsetzt.

Das ist z.B. der Fall bei einer ArrayList, die HashMaps enthält. In diesem Fall können wir die Klasse net.sf.jasperreports.engine.data.JRMapCollectionDataSource verwenden. Example13.java demonstriert ihre Verwendung:

ArrayList<HashMap> al = new ArrayList<HashMap>();
HashMap<String,String> hm = new HashMap<String, String>();
hm.put("vorname", "Alex");
hm.put("nachname", "Merz");
al.add(hm);
hm = new HashMap<String, String>();
hm.put("vorname", "Ron");
hm.put("nachname", "Friend");
al.add(hm);

JRMapCollectionDataSource ds = new JRMapCollectionDataSource(al);

Datenbank

Sollen die Daten aus einer Datenbank geholt werden, kann das <query>-Tag innerhalb der Template-Definition verwendet werden. Eine entsprechende Darstellung befindet sich bereits im ersten Artikel und soll hier nicht wiederholt werden. Reichen dessen Möglichkeiten nicht aus, dann sollte eine eigene Klasse als Datenquelle implementiert werden.

Eigene Implementierung

Die Programmierung einer eigenen Klassen, um sie als Datenquelle einzusetzen, ist nicht sonderlich schwierig. Wir müssen nur das Interface net.sf.jasperreports.engine.JRDataSource implementieren mit seinen beiden Methoden next() und getFieldValue().

Die Methode next() soll beim Aufruf entweder wahr oder falsch zurückliefern, je nachdem, ob noch Datensätze vorhanden sind. Sind welche vorhanden, soll ein interner Zeiger oder ähnliches auf den nächsten aktuellen Datensatz zeigen.

Die Methode getFieldValue() wird mit einem Parameter aufgerufen und liefert den Inhalt eines Feldes des aktuellen Datensatzes. Dieser Parameter vom Typ JRField enthält Informationen darüber, welches Feld des aktuellen Datensatzes gewünscht wird.

In Example14.java wird MyDataSource als Datenquelle verwendet. Diese Klasse kapselt einen Datenbankzugriff. Ruft JasperReports die Methode next() der Klasse auf, dann wird beim ersten Aufruf eine Verbindung zur Datenbank hergestellt und eine Abfrage durchgeführt. Bei allen weiteren Aufrufen wird einfach die next()-Methode des ResultSets aufgerufen.

In der Methode getFieldValue() wird der Name des gewünschten Feldes extrahiert (getName()), und aus dem aktuellen Datensatz im ResultSet wird das gewählte Feld (bzw. Spalte) zurückgegeben. Innerhalb der getFieldValue()-Methode sind beliebige Operationen mit den Daten möglich. Es ist nur darauf zu achten, dass der Rückgabewert dem class-Attribut in der <field>-Definition der XML-Report-Vorlage entspricht.

Die hier gezeigte Kapselung des Datenbankzugriffs ist nicht ganz sauber. Denn die Datenbankverbindung und das ResultSet werden während des Durchlaufens der Ergebnismenge offen bzw. im Speicher gehalten. Dabei wird aber deutlich, dass die Funktionsweise des Datenzugriffs in JasperReports sich sehr stark am klassischen Datenbank-Zugriff orientiert. In einer eigenen Implementierung sollte man Datenbankverbindungen etc. natürlich sofort nach der Abfrage schließen und das ResultSet in eine eigene, effizientere Struktur überführen, vor allem bei potenziell größeren Datenmengen.

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