«1. Обзор

JasperReports — это библиотека отчетов с открытым исходным кодом, которая позволяет пользователям создавать пиксельные отчеты, которые можно распечатать или экспортировать во многих форматах, включая PDF, HTML и XLS.

В этой статье мы рассмотрим его ключевые функции и классы, а также реализуем примеры, чтобы продемонстрировать его возможности.

2. Зависимость Maven

Во-первых, нам нужно добавить зависимость jasperreports в наш pom.xml:

<dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports</artifactId>
    <version>6.4.0</version>
</dependency>

Последнюю версию этого артефакта можно найти здесь.

3. Шаблоны отчетов

Дизайн отчетов определяется в файлах JRXML. Это обычные файлы XML с определенной структурой, которую может интерпретировать движок JasperReports.

Давайте теперь посмотрим только на соответствующую структуру файлов JRXML, чтобы лучше понять Java-часть процесса генерации отчетов, которая является нашей основной задачей.

Давайте создадим простой отчет для отображения информации о сотрудниках:

<jasperReport ... >
    <field name="FIRST_NAME" class="java.lang.String"/>
    <field name="LAST_NAME" class="java.lang.String"/>
    <field name="SALARY" class="java.lang.Double"/>
    <field name="ID" class="java.lang.Integer"/>
    <detail>
        <band height="51" splitType="Stretch">
            <textField>
                <reportElement x="0" y="0" width="100" height="20"/>
                <textElement/>
                <textFieldExpression class="java.lang.String">
                  <![CDATA[$F{FIRST_NAME}]]></textFieldExpression>
            </textField>
            <textField>
                <reportElement x="100" y="0" width="100" height="20"/>
                <textElement/>
                <textFieldExpression class="java.lang.String">
                  <![CDATA[$F{LAST_NAME}]]></textFieldExpression>
            </textField>
            <textField>
                <reportElement x="200" y="0" width="100" height="20"/>
                <textElement/>
                <textFieldExpression class="java.lang.String">
                  <![CDATA[$F{SALARY}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
</jasperReport>

3.1. Компиляция отчетов

Файлы JRXML необходимо скомпилировать, чтобы механизм отчетов мог заполнить их данными.

Выполним эту операцию с помощью класса JasperCompilerManager:

InputStream employeeReportStream
  = getClass().getResourceAsStream("/employeeReport.jrxml");
JasperReport jasperReport
  = JasperCompileManager.compileReport(employeeReportStream);

Чтобы не компилировать его каждый раз, мы можем сохранить его в файл:

JRSaver.saveObject(jasperReport, "employeeReport.jasper");

4. Заполнение отчетов

Наиболее распространенный способ заполнения скомпилированных отчетов — записями из базы данных. Для этого требуется, чтобы отчет содержал SQL-запрос, который механизм будет выполнять для получения данных.

Во-первых, давайте изменим наш отчет, чтобы добавить SQL-запрос:

<jasperReport ... >
    <queryString>
        <![CDATA[SELECT * FROM EMPLOYEE]]>
    </queryString>
    ...
</jasperReport>

Теперь давайте создадим простой источник данных:

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
      .setType(EmbeddedDatabaseType.HSQL)
      .addScript("classpath:employee-schema.sql")
      .build();
}

Теперь мы можем заполнить отчет:

JasperPrint jasperPrint = JasperFillManager.fillReport(
  jasperReport, null, dataSource.getConnection());

Обратите внимание, что мы передаем null во второй аргумент, так как наш отчет еще не получает никаких параметров.

4.1. Параметры

Параметры полезны для передачи обработчику отчетов данных, которые он не может найти в своем источнике данных, или когда данные изменяются в зависимости от различных условий выполнения.

Мы также можем изменить части или даже весь SQL-запрос с параметрами, полученными в операции заполнения отчета.

Во-первых, давайте изменим отчет, чтобы получить три параметра:

<jasperReport ... >
    <parameter name="title" class="java.lang.String" />
    <parameter name="minSalary" class="java.lang.Double" />
    <parameter name="condition" class="java.lang.String">
        <defaultValueExpression>
          <![CDATA["1 = 1"]]></defaultValueExpression>
    </parameter>
    // ...
</jasperreport>

Теперь давайте добавим раздел title, чтобы показать параметр title:

<jasperreport ... >
    // ...
    <title>
        <band height="20" splitType="Stretch">
            <textField>
                <reportElement x="238" y="0" width="100" height="20"/>
                <textElement/>
                <textFieldExpression class="java.lang.String">
                  <![CDATA[$P{title}]]></textFieldExpression>
            </textField>
        </band>
    </title>
    ...
</jasperreport/>

Затем давайте изменим запрос, чтобы использовать minSalary и параметры условия:

SELECT * FROM EMPLOYEE
  WHERE SALARY >= $P{minSalary} AND $P!{condition}

Обратите внимание на другой синтаксис при использовании параметра условия. Это сообщает обработчику, что параметр не должен использоваться как стандартный параметр PreparedStatement, а как если бы значение этого параметра было записано изначально в SQL-запросе.

Наконец, подготовим параметры и заполним отчет:

Map<String, Object> parameters = new HashMap<>();
parameters.put("title", "Employee Report");
parameters.put("minSalary", 15000.0);
parameters.put("condition", " LAST_NAME ='Smith' ORDER BY FIRST_NAME");

JasperPrint jasperPrint
  = JasperFillManager.fillReport(..., parameters, ...);

Обратите внимание, что ключи параметров соответствуют именам параметров в отчете. Если механизм обнаруживает, что параметр отсутствует, он получит значение из defaultValueExpression параметра, если таковой имеется.

5. Экспорт

Чтобы экспортировать отчет, мы сначала создаем объект класса экспортера, который соответствует нужному нам формату файла.

Затем мы устанавливаем наш предыдущий заполненный отчет в качестве входных данных и определяем, куда выводить результирующий файл.

При желании мы можем установить соответствующие объекты конфигурации отчета и экспорта, чтобы настроить процесс экспорта.

5.1. PDF

JRPdfExporter exporter = new JRPdfExporter();

exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
exporter.setExporterOutput(
  new SimpleOutputStreamExporterOutput("employeeReport.pdf"));

SimplePdfReportConfiguration reportConfig
  = new SimplePdfReportConfiguration();
reportConfig.setSizePageToContent(true);
reportConfig.setForceLineBreakPolicy(false);

SimplePdfExporterConfiguration exportConfig
  = new SimplePdfExporterConfiguration();
exportConfig.setMetadataAuthor("baeldung");
exportConfig.setEncrypted(true);
exportConfig.setAllowedPermissionsHint("PRINTING");

exporter.setConfiguration(reportConfig);
exporter.setConfiguration(exportConfig);

exporter.exportReport();

5.2. XLS

JRXlsxExporter exporter = new JRXlsxExporter();
 
// Set input and output ...
SimpleXlsxReportConfiguration reportConfig
  = new SimpleXlsxReportConfiguration();
reportConfig.setSheetNames(new String[] { "Employee Data" });

exporter.setConfiguration(reportConfig);
exporter.exportReport();

5.3. CSV

JRCsvExporter exporter = new JRCsvExporter();
 
// Set input ...
exporter.setExporterOutput(
  new SimpleWriterExporterOutput("employeeReport.csv"));

exporter.exportReport();

5.4. HTML

HtmlExporter exporter = new HtmlExporter();
 
// Set input ...
exporter.setExporterOutput(
  new SimpleHtmlExporterOutput("employeeReport.html"));

exporter.exportReport();

6. Подотчеты

Подотчеты — это не что иное, как стандартный отчет, встроенный в другой отчет.

Во-первых, давайте создадим отчет для отображения электронных писем сотрудника:

<jasperReport ... >
    <parameter name="idEmployee" class="java.lang.Integer" />
    <queryString>
        <![CDATA[SELECT * FROM EMAIL WHERE ID_EMPLOYEE = $P{idEmployee}]]>
    </queryString>
    <field name="ADDRESS" class="java.lang.String"/>
    <detail>
        <band height="20" splitType="Stretch">
            <textField>
                <reportElement x="0" y="0" width="156" height="20"/>
                <textElement/>
                <textFieldExpression class="java.lang.String">
                  <![CDATA[$F{ADDRESS}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
</jasperReport>

Теперь давайте изменим наш отчет о сотрудниках, включив в него предыдущий:

<detail>
    <band ... >
        <subreport>
            <reportElement x="0" y="20" width="300" height="27"/>
            <subreportParameter name="idEmployee">
                <subreportParameterExpression>
                  <![CDATA[$F{ID}]]></subreportParameterExpression>
            </subreportParameter>
            <connectionExpression>
              <![CDATA[$P{REPORT_CONNECTION}]]></connectionExpression>
            <subreportExpression class="java.lang.String">
              <![CDATA["employeeEmailReport.jasper"]]></subreportExpression>
        </subreport>
    </band>
</detail>

Обратите внимание, что мы ссылаемся на подотчет по имя скомпилированного файла и передача ему idEmployee и текущего соединения отчета в качестве параметров.

Далее скомпилируем оба отчета:

InputStream employeeReportStream
  = getClass().getResourceAsStream("/employeeReport.jrxml");
JasperReport jasperReport
  = JasperCompileManager.compileReport(employeeReportStream);
JRSaver.saveObject(jasperReport, "employeeReport.jasper");

InputStream emailReportStream
  = getClass().getResourceAsStream("/employeeEmailReport.jrxml");
JRSaver.saveObject(
  JasperCompileManager.compileReport(emailReportStream),
  "employeeEmailReport.jasper");

Наш код для заполнения и экспорта отчета не требует доработок.

7. Заключение

В этой статье мы кратко рассмотрели основные функции библиотеки JasperReports.

«Мы смогли составить и заполнить отчеты записями из базы данных; мы передали параметры для изменения данных, отображаемых в отчете, в соответствии с различными условиями выполнения, встроили вложенные отчеты и экспортировали их в наиболее распространенные форматы.

Полный исходный код этой статьи можно найти на GitHub.