ZetCode

JasperReports - 以编程方式创建报告

最后修改于 2020 年 7 月 13 日

在本教程中,我们将展示如何以编程方式使用 JasperReports 创建报告。我们从 CARS 表创建一个 PDF 报告。我们在应用程序中使用 Derby 数据库。

一份报告是包含以叙述、图形或表格形式组织的信息的文档,该文档是根据临时、定期、重复、规则或按需编制的。JasperReports 是一个开源报告库。它从数据源加载数据并从中创建报告。报告是 PDF、HTML、RTF、XLS、ODT、CSV 或 XML 格式的文件。

JasperReports 使用模板来定义文档结构。模板在 XML 文件中定义,或使用编程 API 创建。在本教程中,我们使用后者选项。

cars.sql
-- SQL for the CARS table

CREATE TABLE CARS(ID BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY 
    (START WITH 1, INCREMENT BY 1), NAME VARCHAR(30), PRICE INT);

INSERT INTO CARS(Name, Price) VALUES('Audi', 52642);
INSERT INTO CARS(Name, Price) VALUES('Mercedes', 57127);
INSERT INTO CARS(Name, Price) VALUES('Skoda', 9000);
INSERT INTO CARS(Name, Price) VALUES('Volvo', 29000);
INSERT INTO CARS(Name, Price) VALUES('Bentley', 350000);
INSERT INTO CARS(Name, Price) VALUES('Citroen', 21000);
INSERT INTO CARS(Name, Price) VALUES('Hummer', 41400);
INSERT INTO CARS(Name, Price) VALUES('Volkswagen', 21600);

在代码示例中,我们使用此表。

在 Derby 中创建 testdb 数据库

Derby 可以使用命令行工具或 NetBeans IDE 的 Services 选项卡进行管理。您可以在 Apache Derby 教程中找到更多关于 Derby 的信息。

$ export DERBY_HOME=$JAVA_HOME/db

我们设置了 DERBY_HOME 目录。Derby 附带 JDK,因此我们无需单独安装。

$ export DERBY_OPTS=-Dderby.system.home=/home/janbodnar/.derby

我们设置了 derby 系统目录。数据库和 derby 日志在此目录中创建。

$ $DERBY_HOME/bin/ij

我们启动了 ij 系统工具。

ij> CONNECT 'jdbc:derby:testdb;create=true';

使用 CONNECT 命令,我们创建了 testdb 数据库。

ij> run 'cars.sql';

使用 run 命令,我们执行 cars.sql 文件中的 SQL 语句。

ij> SELECT * FROM CARS;
ID                  |NAME                          |PRICE      
---------------------------------------------------------------
1                   |Audi                          |52642      
2                   |Mercedes                      |57127      
3                   |Skoda                         |9000       
4                   |Volvo                         |29000      
5                   |Bentley                       |350000     
6                   |Citroen                       |21000      
7                   |Hummer                        |41400      
8                   |Volkswagen                    |21600  

这是创建的 CARS 表。

ij> exit;

我们退出了 ij 工具。

$ $DERBY_HOME/bin/startNetworkServer &

我们启动了 Derby 网络服务器。

应用程序

以下应用程序连接到 Derby 服务器,从 CARS 表加载数据,并使用 JasperReports 从数据创建 PDF 文件。

$ tree
.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── zetcode
    │   │           └── main
    │   │               ├── CommandLineRunner.java
    │   │               ├── JasperProgrammatic.java
    │   │               └── ReportBuilder.java
    │   └── resources
    └── test
        └── java

这是项目结构。

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zetcode</groupId>
    <artifactId>JasperProgrammatic3</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
    <dependencies>
        
        <dependency>
            <groupId>net.sf.jasperreports</groupId>
            <artifactId>jasperreports</artifactId>
            <version>6.4.0</version>
        </dependency>
        
        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derbyclient</artifactId>
            <version>10.13.1.1</version>
        </dependency>           
             
    </dependencies>        
    
</project>

在 Maven pom.xml 文件中,我们具有 jasperreportsderbyclient 依赖项。

com/zetcode/CommandLineRunner.java
package com.zetcode.main;

public class CommandLineRunner {

    public static void main(String[] args) throws Exception {

        JasperProgrammatic app = new JasperProgrammatic();
        app.start();
    }
}

CommandLineRunner 设置应用程序并调用其 start 方法。

com/zetcode/ReportBuilder.java
package com.zetcode.main;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.design.JRDesignBand;
import net.sf.jasperreports.engine.design.JRDesignExpression;
import net.sf.jasperreports.engine.design.JRDesignField;
import net.sf.jasperreports.engine.design.JRDesignParameter;
import net.sf.jasperreports.engine.design.JRDesignQuery;
import net.sf.jasperreports.engine.design.JRDesignSection;
import net.sf.jasperreports.engine.design.JRDesignStaticText;
import net.sf.jasperreports.engine.design.JRDesignStyle;
import net.sf.jasperreports.engine.design.JRDesignTextField;
import net.sf.jasperreports.engine.design.JasperDesign;
import net.sf.jasperreports.engine.type.HorizontalTextAlignEnum;

public class ReportBuilder {

    public JasperDesign build() throws JRException {
        
        JasperDesign jasDes = new JasperDesign();
        jasDes.setName("myreport");
        jasDes.setPageWidth(595);
        jasDes.setPageHeight(842);
        jasDes.setLeftMargin(20);
        jasDes.setRightMargin(20);
        jasDes.setTopMargin(20);
        jasDes.setBottomMargin(20);
        jasDes.setColumnWidth(555);

        // Style
        JRDesignStyle mystyle = new JRDesignStyle();
        mystyle.setName("mystyle");
        mystyle.setDefault(true);
        mystyle.setFontName("DejaVu Sans");
        mystyle.setFontSize(22f);
        mystyle.setPdfFontName("Helvetica");
        mystyle.setPdfEncoding("UTF-8");
        jasDes.addStyle(mystyle);

        // Fields
        JRDesignField field1 = new JRDesignField();
        field1.setName("id");
        field1.setValueClass(String.class);
        jasDes.addField(field1);

        JRDesignField field2 = new JRDesignField();
        field2.setName("name");
        field2.setValueClass(String.class);
        jasDes.addField(field2);

        JRDesignField field3 = new JRDesignField();
        field3.setName("price");
        field3.setValueClass(String.class);
        jasDes.addField(field3);

        // Parameter
        JRDesignParameter par = new JRDesignParameter();
        par.setName("CarPrice");
        par.setValueClass(Integer.class);
        jasDes.addParameter(par);

        // Query
        JRDesignQuery query = new JRDesignQuery();
        query.setText("SELECT * FROM Cars WHERE Price > $P{CarPrice}");
        jasDes.setQuery(query);

        // Title
        JRDesignBand titleBand = new JRDesignBand();
        titleBand.setHeight(50);

        JRDesignStaticText titleText = new JRDesignStaticText();
        titleText.setText("Expensive cars");
        titleText.setX(0);
        titleText.setY(10);
        titleText.setWidth(515);
        titleText.setHeight(30);
        titleText.setHorizontalTextAlign(HorizontalTextAlignEnum.CENTER);
        titleText.setFontSize(22f);
        titleBand.addElement(titleText);
        jasDes.setTitle(titleBand);

        // Detail
        JRDesignBand detailBand = new JRDesignBand();
        detailBand.setHeight(60);

        JRDesignTextField tf1 = new JRDesignTextField();
        tf1.setBlankWhenNull(true);
        tf1.setX(0);
        tf1.setY(10);
        tf1.setWidth(60);
        tf1.setHeight(30);
        tf1.setHorizontalTextAlign(HorizontalTextAlignEnum.CENTER);
        tf1.setStyle(mystyle);
        tf1.setExpression(new JRDesignExpression("$F{id}"));
        detailBand.addElement(tf1);

        JRDesignTextField tf2 = new JRDesignTextField();
        tf2.setBlankWhenNull(true);
        tf2.setX(80);
        tf2.setY(10);
        tf2.setWidth(120);
        tf2.setHeight(30);
        tf2.setHorizontalTextAlign(HorizontalTextAlignEnum.LEFT);
        tf2.setStyle(mystyle);
        tf2.setExpression(new JRDesignExpression("$F{name}"));
        detailBand.addElement(tf2);

        JRDesignTextField tf3 = new JRDesignTextField();
        tf3.setBlankWhenNull(true);
        tf3.setX(200);
        tf3.setY(10);
        tf3.setWidth(100);
        tf3.setHeight(30);
        tf3.setHorizontalTextAlign(HorizontalTextAlignEnum.RIGHT);
        tf3.setStyle(mystyle);
        tf3.setExpression(new JRDesignExpression("$F{price}"));
        detailBand.addElement(tf3);

        ((JRDesignSection) jasDes.getDetailSection()).addBand(detailBand);

        return jasDes;
    }
}

ReportBuilder 类中,我们以编程方式创建 Jasper 报告。

JasperDesign jasDes = new JasperDesign();
jasDes.setName("myreport");
jasDes.setPageWidth(595);
jasDes.setPageHeight(842);
jasDes.setLeftMargin(20);
jasDes.setRightMargin(20);
jasDes.setTopMargin(20);
jasDes.setBottomMargin(20);
jasDes.setColumnWidth(555);

JasperDesign 在使用编程 API 创建报告方面发挥着重要作用。通过其 setter 方法,我们定义了报告名称及其基本设置。

// Style
JRDesignStyle mystyle = new JRDesignStyle();
mystyle.setName("mystyle");
mystyle.setDefault(true);
mystyle.setFontName("DejaVu Sans");
mystyle.setFontSize(22f);
mystyle.setPdfFontName("Helvetica");
mystyle.setPdfEncoding("UTF-8");
jasDes.addStyle(mystyle);

JRDesignStyle 用于定义自定义样式。使用样式可避免代码重复。

// Fields
JRDesignField field1 = new JRDesignField();
field1.setName("id");
field1.setValueClass(String.class);
jasDes.addField(field1);

JRDesignField field2 = new JRDesignField();
field2.setName("name");
field2.setValueClass(String.class);
jasDes.addField(field2);

JRDesignField field3 = new JRDesignField();
field3.setName("price");
field3.setValueClass(String.class);
jasDes.addField(field3);

我们有三个字段,分别对应 CARS 表的三个列。报告字段是元素,表示数据源和报告模板之间的数据映射。

// Parameter
JRDesignParameter par = new JRDesignParameter();
par.setName("CarPrice");
par.setValueClass(Integer.class);
jasDes.addParameter(par);

JRDesignParameter 定义了传递给报告引擎的报告填充操作的动态参数。

// Query
JRDesignQuery query = new JRDesignQuery();
query.setText("SELECT * FROM Cars WHERE Price > $P{CarPrice}");
jasDes.setQuery(query);

JRDesignQuery 定义了从数据库获取数据的 SQL 查询。$P{CarPrice} 占位符由传递给报告引擎的参数值填充。

// Title
JRDesignBand titleBand = new JRDesignBand();
titleBand.setHeight(50);

JRDesignStaticText titleText = new JRDesignStaticText();
titleText.setText("Expensive cars");
titleText.setX(0);
titleText.setY(10);
titleText.setWidth(515);
titleText.setHeight(30);
titleText.setHorizontalTextAlign(HorizontalTextAlignEnum.CENTER);
titleText.setFontSize(22f);
titleBand.addElement(titleText);
jasDes.setTitle(titleBand);

在这里,我们创建了一个报告标题。标题是 JRDesignStaticText 的实例。报告模板由几个部分组成,包括标题、列标题、详细信息和摘要。每个部分都放置在一个称为“band”的水平区域中。

// Detail
JRDesignBand detailBand = new JRDesignBand();
detailBand.setHeight(60);

JRDesignTextField tf1 = new JRDesignTextField();
tf1.setBlankWhenNull(true);
tf1.setX(0);
tf1.setY(10);
tf1.setWidth(60);
tf1.setHeight(30);
tf1.setHorizontalTextAlign(HorizontalTextAlignEnum.CENTER);
tf1.setStyle(mystyle);
tf1.setExpression(new JRDesignExpression("$F{id}"));
detailBand.addElement(tf1);
...

Detail 是放置报告数据的 band。表中的每一行都由 detail band 中的一行表示。detail band 由三个 JRDesignTextFields 组成,这些字段会根据给定的表达式生成动态数据。$F{id} 表达式给出当前表行的 ID。

com/zetcode/JasperProgrammatic.java
package com.zetcode.main;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.design.JasperDesign;

public class JasperProgrammatic {

    private Connection con;

    public void start() throws JRException, SQLException {

        try {
            String url = "jdbc:derby://:1527/testdb";
            String user = "app";
            String password = "app";

            DriverManager.registerDriver(new org.apache.derby.jdbc.ClientDriver());
            con = DriverManager.getConnection(url, user, password);
            
            JasperDesign jdes = new ReportBuilder().build();

            JasperReport report = JasperCompileManager.compileReport(jdes);

            HashMap params = new HashMap();
            params.put("CarPrice", 30000);

            JasperPrint jprint = JasperFillManager.fillReport(report, params, con);

            JasperExportManager.exportReportToPdfFile(jprint,
                    "src/main/resources/expensivecars.pdf");
                    
        } catch (SQLException ex) {
            Logger.getLogger(JasperProgrammatic.class.getName()).log(Level.SEVERE, null, ex);
            
        } finally {
            if (con != null) {
                con.close();
            }
        }
    }
}

JasperProgrammatic 类中,我们构建、编译和导出报告。

JasperDesign jdes = new ReportBuilder().build();

使用 ReportBuilder 类创建 JasperDesign,这是一个由编程 API 创建的报告模板。

JasperReport report = JasperCompileManager.compileReport(jdes);

使用 JasperCompileManager,我们将模板编译成一个中间的 JasperReport 对象。

HashMap params = new HashMap();
params.put("CarPrice", 30000);

我们的参数映射包含一个参数:CarPrice,用于过滤数据。报告中只显示昂贵的汽车。

JasperPrint jprint = JasperFillManager.fillReport(report, params, con);

下一步是用数据填充已编译的 JasperReport 对象。此操作会生成一个 JasperPrint 对象,该对象可以查看、打印或导出为其他格式。

JasperExportManager.exportReportToPdfFile(jprint,
        "src/main/resources/expensivecars.pdf");

我们使用 JasperExportManagerJasperPrint 对象导出为 PDF 文件。生成的 PDF 文件写入 src/main/resources/ 目录。

在本教程中,我们使用 JasperReports 库的编程 API 创建了一个 PDF 文件报告。