◀ 2.

【JasperReports】jar内のサブレポートを読み込む方法

この記事の要点
  • 開発環境では動くのに jar にすると失敗するのは、サブレポートをファイルパスで指定しているため。jar の中身はファイルとして開けない
  • メインレポートと同じく、サブレポートもファイルパスではなく URL・InputStream・JasperReport オブジェクトのいずれかで渡す
  • 簡単な方法: Java 側で getClass().getResource("/jasper/") の URL を取得し、パラメータで渡してファイル名と結合する
  • 最も確実な方法: Java 側でサブレポートを JasperReport オブジェクトとして読み込み、そのまま渡す
  • Jaspersoft Studio が作る $P{SUBREPORT_DIR} もこの考え方で値を差し替えれば使える

原因:jar の中のファイルはパスでは読めない

開発環境では普通にパス指定すれば jasper ファイルなどのリソースファイルは読み込めますが、本番環境にデプロイする際に jar にするとファイルが読み込めなくなります。

IDE から実行しているときは、src/main/resources の中身がビルド先のディレクトリ(target/classes など)に普通のファイルとして存在するため、"src/main/resources/jasper/sub_report.jasper" のようなパスでも動いてしまいます。ところが jar にすると、これらは jar(zip 形式)の中のエントリになり、OS のファイルパスでは開けません。

従って、リソースファイルを指定する際は通常のパスではなくURL などクラスパス経由で読める形で指定する必要があります。メインレポートだろうがサブレポートだろうが同じです。メインレポート側の読み込み方は別記事「jar内のjrxml/jasperファイルを読み込む方法」を参照してください。

方法 1:jasper ディレクトリの URL をパラメータで渡す

jasper 内で URL を取得するより、Java 側で取得してパラメータとして渡してやるのがおすすめです。下記の例では「src/main/resources/jasper」配下に jasper ファイルが格納されている前提で、jasper ディレクトリまでの URL を取得しています。

// 末尾のスラッシュまで含めた URL 文字列にしておく
String jasperResourceUrl = this.getClass().getResource("/jasper/").toString();
// 例: jar:file:/opt/app/app.jar!/jasper/          (通常の jar)
//     file:/home/dev/project/target/classes/jasper/  (IDE 実行時)

Map<String, Object> params = new HashMap<>();
params.put("jasperResourceUrl", jasperResourceUrl);

getResource("/jasper") のように末尾のスラッシュを付けないと、URL がスラッシュで終わらず、後でファイル名と結合したときに .../jaspersub_report.jasper という誤ったパスになるので注意してください。

jrxml 側では、パラメータ(jasperResourceUrl)として受け取った jasper ディレクトリまでの URL とファイル名を結合して、サブレポートとして読み込みます。

<parameter name="jasperResourceUrl" class="java.lang.String"/>

<!-- サブレポート要素の中 -->
<subreportExpression><![CDATA[$P{jasperResourceUrl} + "sub_report.jasper"]]></subreportExpression>

Jaspersoft Studio でサブレポートを配置すると、$P{SUBREPORT_DIR} + "sub_report.jasper" のような式とパラメータ SUBREPORT_DIR が自動で作られることがあります。その場合は Java 側で params.put("SUBREPORT_DIR", jasperResourceUrl) として値を差し替えれば、jrxml を書き換えずに済みます。

方法 2:JasperReport オブジェクトを渡す(おすすめ)

サブレポートの式には、文字列(パスや URL)だけでなく、読み込み済みの JasperReport オブジェクトも渡せます。Java 側でクラスパスから読み込んでおけば、jar かどうか・URL の形式がどうなるかを気にする必要がありません。

import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.util.JRLoader;

JasperReport mainReport;
JasperReport subReport;
try (InputStream main = getClass().getResourceAsStream("/jasper/main_report.jasper");
     InputStream sub  = getClass().getResourceAsStream("/jasper/sub_report.jasper")) {
    mainReport = (JasperReport) JRLoader.loadObject(main);
    subReport  = (JasperReport) JRLoader.loadObject(sub);
}

Map<String, Object> params = new HashMap<>();
params.put("SUB_REPORT", subReport);

JasperPrint print = JasperFillManager.fillReport(mainReport, params, connection);
<parameter name="SUB_REPORT" class="net.sf.jasperreports.engine.JasperReport"/>

<subreportExpression><![CDATA[$P{SUB_REPORT}]]></subreportExpression>

同じサブレポートを何度も使う場合でも、読み込みは 1 回で済むため効率的です。コンパイル済みの JasperReport はアプリ内でキャッシュしておくこともできます。

サブレポートにデータを渡すのも忘れずに

サブレポートが自分で SQL を実行する場合は、メインレポートの DB 接続を引き継ぐ設定が必要です。これがないと、サブレポートの読み込み自体は成功しても何も表示されません。

<connectionExpression><![CDATA[$P{REPORT_CONNECTION}]]></connectionExpression>

Java Bean のリストなどをデータソースにする場合は dataSourceExpression で JRBeanCollectionDataSource などを渡します。

よくある落とし穴

  • getResource が null を返す: パスの指定ミス。先頭の / はクラスパスのルートを意味する。src/main/resources/jasper/x.jasper なら /jasper/x.jasper と書く
  • jrxml だけを同梱している: サブレポートに .jasper を指定しているのに、jar にはコンパイル前の .jrxml しか入っていないケース。ビルド時にコンパイルして同梱するか、実行時に JasperCompileManager.compileReport() でコンパイルする
  • コンパイルしたバージョンの違い: Jaspersoft Studio でコンパイルした .jasper と、アプリが使う JasperReports ライブラリのバージョンが大きく違うと、読み込み時に例外が出ることがある。ライブラリと同じバージョンでコンパイルするのが安全
  • Spring Boot の実行可能 jar: クラスは BOOT-INF/classes 配下に入るため URL の形が通常の jar と異なる。方法 2 なら URL の形に依存しない
  • File クラスで扱おうとする: new File(url.toURI()) のように URL を File に変換するコードは jar 内では失敗する(URI is not hierarchical など)

確認方法

  1. jar に jasper ファイルが含まれているかを確認する
    jar tf app.jar | grep jasper
  2. 取得した URL をログに出し、末尾のスラッシュや !/ 以降のパスが正しいか確認する
  3. IDE 実行ではなく、java -jar app.jar で起動して帳票を出力し、サブレポート部分が表示されるか確認する

関連

子ページ

子ページはありません

同階層のページ
  1. jar内のjrxml/jasperファイルを読み込む方法
  2. jar内のサブレポートを読み込む方法

最近更新/作成されたページ