ZetCode

Spring Boot web JasperReports 集成

最后修改于 2020 年 7 月 13 日

在本教程中,我们将展示如何将 JasperReports 与 Spring Boot 框架一起使用。我们创建一个 Web 应用程序。

JasperReports 是一个 Java 开源报告库。它可以创建多种格式的报告,包括 PDF、HTML、XLS 或 CSV。JasperReports 以简单灵活的方式创建面向页面、可打印的文档。

JdbcTemplate 是一个 Spring 库,可以帮助程序员创建与关系数据库和 JDBC 配合使用的应用程序。它处理许多繁琐且易出错的低级细节,例如事务处理、资源清理和异常正确处理。JdbcTemplate 包含在 Spring 的 spring-jdbc 模块中。

Spring 是一个用于开发 Java 企业应用程序的 Java 应用程序框架。它还有助于集成各种企业组件。Spring Boot 使创建支持 Spring、生产级的应用程序和服务变得容易,并且设置要求最低。

Apache Derby 是一个完全用 Java 实现的开源关系数据库。它占用的空间小,易于部署和安装。它可以在嵌入式和客户端/服务器模式下运行。

CARS 表

我们使用下表

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);

cars.sql 文件创建了 CARS 表。

$ $DERBY_HOME/bin/ij
ij version 10.11
ij> CONNECT 'jdbc:derby:testdb';
ij> RUN 'cars.sql';

一种选择是使用 ij 工具从 SQL 脚本创建表。请参阅 Apache Derby 教程以熟悉 Derby。

$ $DERBY_HOME/bin/NetworkServerControl start &

Derby 服务器使用 NetworkServerControl 工具启动。

应用程序

以下 Spring Boot 应用程序从数据库表中加载数据,并使用 JasperReports 库从中生成 PDF 报告。该应用程序在嵌入式 Tomcat 服务器上运行。

$ tree
.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── zetcode
    │   │           ├── Application.java
    │   │           ├── bean
    │   │           │   └── Car.java
    │   │           ├── conf
    │   │           │   ├── AppConfig.java
    │   │           │   └── MvcConf.java
    │   │           ├── controller
    │   │           │   └── MyController.java
    │   │           └── service
    │   │               ├── CarService.java
    │   │               └── ICarService.java
    │   └── resources
    │       ├── application.yml
    │       ├── report2.jrxml
    │       └── static
    │           └── index.html
    └── 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>JasperSpringBootWeb</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</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>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.2.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derbyclient</artifactId>
            <version>10.13.1.1</version>
        </dependency>

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

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Maven pom.xml 文件包含 JasperReports 库、Derby 驱动程序和 Spring Boot 的依赖项。

report2.xml
<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE jasperReport PUBLIC "//JasperReports//DTD Report Design//EN"
   "http://jasperreports.sourceforge.net/dtds/jasperreport.dtd">

<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports
   http://jasperreports.sourceforge.net/xsd/jasperreport.xsd"
              name="report2" pageWidth="595" pageHeight="842"
              columnWidth="555" leftMargin="20" rightMargin="20"
              topMargin="20" bottomMargin="20">

    <field name="Id" class="java.lang.Long">
        <fieldDescription><![CDATA[id]]></fieldDescription>
    </field>

    <field name="Name" class="java.lang.String">
        <fieldDescription><![CDATA[name]]></fieldDescription>
    </field>

    <field name="Price" class="java.lang.Integer">
        <fieldDescription><![CDATA[price]]></fieldDescription>
    </field>

    <detail>
        <band height="15">

            <textField>
                <reportElement x="0" y="0" width="50" height="15" />

                <textElement textAlignment="Right" verticalAlignment="Middle"/>

                <textFieldExpression class="java.lang.Long">
                    <![CDATA[$F{Id}]]>
                </textFieldExpression>
            </textField>

            <textField>
                <reportElement x="150" y="0" width="100" height="15" />

                <textElement textAlignment="Left" verticalAlignment="Middle"/>

                <textFieldExpression class="java.lang.String">
                    <![CDATA[$F{Name}]]>
                </textFieldExpression>
            </textField>

            <textField>
                <reportElement x="200" y="0" width="100" height="15"/>
                <textElement textAlignment="Right" verticalAlignment="Middle"/>

                <textFieldExpression class="java.lang.Integer">
                    <![CDATA[$F{Price}]]>
                </textFieldExpression>
            </textField>

        </band>
    </detail>

</jasperReport>

这是报告模板文件。它位于 src/main/resources 目录中。模板仅包含详细信息部分。在详细信息部分中,每个元素会根据数据源提供的每条记录重复一次。

<field name="Id" class="java.lang.Long">
    <fieldDescription><![CDATA[id]]></fieldDescription>
</field>

<field name="Name" class="java.lang.String">
    <fieldDescription><![CDATA[name]]></fieldDescription>
</field>

<field name="Price" class="java.lang.Integer">
    <fieldDescription><![CDATA[price]]></fieldDescription>
</field>

我们在报告中有三个字段。这些字段映射到数据源 bean 的元素。

<textField>
    <reportElement x="0" y="0" width="50" height="15" />

    <textElement textAlignment="Right" verticalAlignment="Middle"/>

    <textFieldExpression class="java.lang.Long">
        <![CDATA[$F{Id}]]>
    </textFieldExpression>
</textField>

文本字段是一个由动态数据填充的元素。我们将字段中的值放在文本字段内。我们使用 $F{} 语法引用变量。

com/zetcode/Car.java
package com.zetcode.bean;

public class Car {

    private Long id;
    private String name;
    private int price;

    public Car() {}

    public Car(Long id, String name, int price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Car{" + "id=" + id + ", name=" + name + ", price=" + price + '}';
    }
}

这是 Car bean 类。它包含项目 ID、名称和价格。

application.yml
datasource:
  url: jdbc:derby://:1527/testdb
  username: app
  password: app
  driverClassName: org.apache.derby.jdbc.ClientDriver

application.yml 是主要的 Spring Boot 配置文件。它包含 Derby 数据源。

com/zetcode/AppConfig.java
package com.zetcode.conf;

import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class AppConfig {

    @Bean
    @Primary
    @ConfigurationProperties(prefix = "datasource")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
}

AppConfig 是一个 Java 配置类。它根据配置文件创建数据源 bean。

com/zetcode/MyController.java
package com.zetcode.controller;

import com.zetcode.service.ICarService;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.jasperreports.JasperReportsPdfView;

@Controller
public class MyController {

    @Autowired
    private ApplicationContext appContext;

    @Autowired
    private ICarService carService;

    @RequestMapping(path = "/pdf", method = RequestMethod.GET)
    public ModelAndView report() {

        JasperReportsPdfView view = new JasperReportsPdfView();
        view.setUrl("classpath:report2.jrxml");
        view.setApplicationContext(appContext);

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

        return new ModelAndView(view, params);
    }
}

MyController 中,我们有两个响应两个请求的方法。

@Autowired
private ICarService carService;

我们将 CarService 对象注入到属性中。该服务对象用于从数据库检索数据。

@RequestMapping(path = "/pdf", method = RequestMethod.GET)
public ModelAndView report() {

    JasperReportsPdfView view = new JasperReportsPdfView();
    view.setUrl("classpath:report2.jrxml");
    view.setApplicationContext(appContext);

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

    return new ModelAndView(view, params);
}

report 方法中,会生成报告并将其发送回客户端。JasperReportsPdfView 是一个 Spring 类,它根据提供的模板和数据生成 PDF 报告。

com/zetcode/ICarService.java
package com.zetcode.service;

import com.zetcode.bean.Car;
import java.util.List;

public interface ICarService {

    public List<Car> findAll();
}

ICarService 提供了一个契约方法,用于从数据源获取所有汽车。

com/zetcode/CarService.java
package com.zetcode.service;

import com.zetcode.bean.Car;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
public class CarService implements ICarService {

    @Autowired
    private JdbcTemplate jtm;

    @Override
    public List<Car> findAll() {

        String sql = "SELECT * FROM Cars";

        List<Car> cars = jtm.query(sql, new BeanPropertyRowMapper(Car.class));

        return cars;
    }
}

CarService 包含 findAll 方法的实现。我们借助 JdbcTemplateCARS 表中检索所有汽车。

@Autowired
private JdbcTemplate jtm;

JdbcTemplate 已注入。

String sql = "SELECT * FROM Cars";

这是要执行的 SQL。我们从 CARS 表中选择所有汽车。

List<Car> cars = jtm.query(sql, new BeanPropertyRowMapper(Car.class));

BeanPropertyRowMapper 将一行转换为指定映射目标类的新实例。

index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Home Page</title>
    </head>
    <body>
        <a href="/pdf.html">Generate report</a>
    </body>
</html>

index.html 文件包含一个生成 PDF 报告的链接。

com/zetcode/Application.java
package com.zetcode;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {

        SpringApplication.run(Application.class, args);
    }
}

`Application` 设置了 Spring Boot 应用程序。

在本教程中,我们使用 JasperReports 创建了一个 PDF 报告。该应用程序使用了 Spring Boot 框架,并在 Web 环境中运行。