CLOVER🍀

That was when it all began.

JasperReports/iReportで、サブレポートを作成する

今回、JaperReportsをネタに出したのは、実はこれが理由です。サブレポートの扱いがよくわからなくて、仕事中に困っていたんですよね。

サブレポートはiReport上の「Palette」で、文字通り「Subreport」Elementをドラッグ&ドロップすることで追加できます。サブレポートを追加すると、新規サブレポート作成ウィザードが起動するので、レイアウトテンプレートなどを選んで、適当に作成します。作成したサブレポートも、デフォルトの言語は「Groovy」になっているので、必要であれば「Java」に変更してください。

で、こんな感じにサブレポートを配置することができます。

で、ここでメインレポートのプレビューを見ても…サブレポートがハマった形でプレビューできません。これの意味が、最初よくわかりませんでした。これは、レポートがデフォルトではデータ(フィールドとして利用できる値)がない時は、何もページを返さないように設定されているのが原因です。

レポート自体のプロパティのうち、「When No Data」が「No Pages」になっているかと思いますので、とにかく出力してみたいのであれば「All Sections, No Detail」を選択しましょう。これで、メインのレポートのプレビューでサブレポートが含まれた形でプレビューできるはずです。

続いて、メインレポートからサブレポートにパラメータを渡すには、メインレポート上から編集できるサブレポートのプロパティで、「Parameters」を編集します。


今回は、サブレポートのパラメータ「title」にメインレポートのフィールド「title」を渡しています。これで、繰り返しごとにフィールドの値がサブレポートに渡る、ということですね。よって、サブレポート側では$P{title}で参照できるようになります。

続いて、データソースについて。

今回は、コネクションではなくコレクションベースのデータソースを使いたいのですが、どうやってサブレポートにデータソースを渡したらいいのかでちょっと困っていました。

そこで参考にしたのが、こちらの記事です。
http://zam.seesaa.net/article/53193513.html

なるほど、サブレポートのプロパティ、「Connection Type」を「Use a datasource expression」にして、「Data Source Expression」に適切な式表現を与えてあげればよいのですね。

つまり、この部分ですな。

で、先のサイトの指示通りに

new JRBeanCollectionDataSource($F{フィールド名})

と書いてみたものの、実行時にJRBeanCollectionDataSourceなんてクラス知らないよ!と怒られてしまいます。importの追加が必要っぽいですね。

というわけで、メインレポートのプロパティにJRBeanCollectionDataSourceクラスのインポートを追加します。

これで、サブレポートにデータソースが渡せるようになりました。サブレポートの「Data Source Expression」プロパティには、以下のようにフィールド「sub○」を設定するようにしました。サブレポートは今回2枚使用しているので、○の欄には1、2が入ります…。

では、作成したメインプログラム。

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.BeanUtils;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.engine.data.JRMapCollectionDataSource;

public class JasperReportsExample {
    public static void main(String[] args) {
        try {
            JasperReport jasperReport = JasperCompileManager.compileReport("pdf/input/sample_report2.jrxml");

            Map<String, Object> main = new HashMap<String, Object>();
            main.put("title", "MainTitle");
            main.put("SUBREPORT_DIR", "pdf/input/");

            List<Map<String, Object>> mainDetails = new ArrayList<Map<String, Object>>();
            Map<String, Object> detail1 = new HashMap<String, Object>();
            detail1.put("name", "name1");
            detail1.put("title", "title1");
            List<PageDetail> sub1 = new ArrayList<PageDetail>();
            PageDetail d1 = new PageDetail();
            d1.setName("detail1");
            d1.setPrice(300);
            sub1.add(d1);
            detail1.put("sub1", sub1);
            detail1.put("sub2", sub1);

            mainDetails.add(detail1);

            @SuppressWarnings("unchecked")
            JasperPrint print = JasperFillManager.fillReport(jasperReport,
                                                             main,
                                                             new JRMapCollectionDataSource((Collection)mainDetails));


            JasperExportManager.exportReportToPdfFile(print, "pdf/output/sample_report2.pdf");

        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

前回のプログラムを、ちょこっと変えただけです。パラメータに渡すオブジェクトは、面倒なのでもうMapにしてしまいました。あとはサブレポートに渡すデータソースを、

            detail1.put("sub1", sub1);
            detail1.put("sub2", sub1);

のように設定しています。

作成したレポートテンプレート3枚は、こんな感じです。メインレポート、サブレポート1、サブレポート2の順に並んでいます。


では、実行結果。

しょーもないPDFですが、一応サブレポートがはめ込まれています。

サンプルコードがかなりのやっつけコードなのですが、個人的には目標を達成したので、よしとしましょう…?