跳转至

在 Spring Boot 中创建 PDF 报告

原文: http://zetcode.com/springboot/servepdf/

Spring Boot 创建 PDF 报告教程展示了如何在 Spring Boot Web 应用中提供 PDF 文件。 该报告是使用 iText 库生成的。

iText 是一个开放源代码库,用于在 Java 中创建和处理 PDF 文件。

Spring 是用于开发 Java 企业应用的 Java 应用框架。 它还有助于集成各种企业组件。 Spring Boot 使创建具有 Spring 动力的生产级应用和服务变得很容易,而对安装的要求却最低。

H2 是完全用 Java 实现的开源关系数据库管理系统。 它可以嵌入 Java 应用中或以客户端-服务器模式运行。 它占地面积小,易于部署和安装。 它包含一个基于浏览器的控制台应用,用于查看和编辑数据库表。

Spring Data JPA 是总括性 Spring Data 项目的一部分,该项目使实现基于 JPA 的存储库变得更加容易。 Spring Data JPA 使用 JPA 将数据存储在关系数据库中。 它可以在运行时从存储库接口自动创建存储库实现。

Spring Boot 服务 PDF 示例

以下 Spring Boot 应用从数据库表中加载数据,并使用 iText 库从中生成 PDF 报告。 它使用ResponseEntityInputStreamResource将 PDF 数据发送到客户端。

pom.xml
src
├───main
│   ├───java
│   │   └───com
│   │       └───zetcode
│   │           │   Application.java
│   │           ├───controller
│   │           │       MyController.java
│   │           ├───model
│   │           │       City.java
│   │           ├───repository
│   │           │       CityRepository.java
│   │           ├───service
│   │           │       CityService.java
│   │           │       ICityService.java
│   │           └───util
│   │                   GeneratePdfReport.java
│   └───resources
│           application.yml
│           import.sql
└───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>springbootservepdf</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

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

    <dependencies>

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

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

        <dependency>
            <groupId>com.lowagie</groupId>
            <artifactId>itext</artifactId>
            <version>4.2.2</version>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>

    </dependencies>

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

</project>

这是 Maven 构建文件。

Spring Boot 启动器是一组有用的依赖项描述符,可大大简化 Maven 配置。 spring-boot-starter-parent具有 Spring Boot 应用的一些常用配置。 spring-boot-starter-web是使用 Spring MVC 构建 Web 应用的入门工具。 它使用 Tomcat 作为默认的嵌入式容器。 spring-boot-starter-data-jpa是将 Spring Data JPA 与 Hibernate 结合使用的入门工具。

此外,我们还包括 H2 数据库和 iText 库的依赖项。

spring-boot-maven-plugin在 Maven 中提供了 Spring Boot 支持,使我们可以打包可执行的 JAR 或 WAR 档案。 它的spring-boot:run目标运行 Spring Boot 应用。

com/zetcode/model/City.java

package com.zetcode.model;

import java.util.Objects;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "cities")
public class City {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int population;

    public City() {
    }

    public City(String name, int population) {
        this.name = name;
        this.population = population;
    }

    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 getPopulation() {
        return population;
    }

    public void setPopulation(int population) {
        this.population = population;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 79 * hash + Objects.hashCode(this.id);
        hash = 79 * hash + Objects.hashCode(this.name);
        hash = 79 * hash + this.population;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final City other = (City) obj;
        if (this.population != other.population) {
            return false;
        }
        if (!Objects.equals(this.name, other.name)) {
            return false;
        }
        return Objects.equals(this.id, other.id);
    }

    @Override
    public String toString() {

        var builder = new StringBuilder();
        builder.append("City{id=").append(id).append(", name=")
                .append(name).append(", population=")
                .append(population).append("}");

        return builder.toString();
    }
}

这是City实体。 每个实体必须至少定义两个注解:@Entity@Idspring.jpa.hibernate.ddl-auto属性的默认值为create-drop,这意味着 Hibernate 将根据该实体创建表架构。

@Entity
@Table(name = "cities")
public class City {

@Entity注解指定该类是一个实体,并映射到数据库表。 @Table实体指定要用于映射的数据库表的名称。

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Id注解指定实体的主键,@GeneratedValue提供规范主键值的生成策略。

resources/application.yml

spring:
  main:
    banner-mode: "off"

logging:
  level:
    org:
      springframework: ERROR

application.yml是主要的 Spring Boot 配置文件。 使用banner-mode属性,我们可以关闭 Spring 横幅。 Spring 框架日志记录设置为ERROR

resources/import.sql

INSERT INTO cities(name, population) VALUES('Bratislava', 432000);
INSERT INTO cities(name, population) VALUES('Budapest', 1759000);
INSERT INTO cities(name, population) VALUES('Prague', 1280000);
INSERT INTO cities(name, population) VALUES('Warsaw', 1748000);
INSERT INTO cities(name, population) VALUES('Los Angeles', 3971000);
INSERT INTO cities(name, population) VALUES('New York', 8550000);
INSERT INTO cities(name, population) VALUES('Edinburgh', 464000);
INSERT INTO cities(name, population) VALUES('Suzhou', 4327066);
INSERT INTO cities(name, population) VALUES('Zhengzhou', 4122087);
INSERT INTO cities(name, population) VALUES('Berlin', 3671000);
INSERT INTO cities(name, population) VALUES('Brest', 139163);
INSERT INTO cities(name, population) VALUES('Bucharest', 1836000);

模式是由 Hibernate 自动创建的。 之后,将执行import.sql文件以将数据填充到表中。

com/zetcode/repository/CityRepository.java

package com.zetcode.repository;

import com.zetcode.model.City;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CityRepository extends CrudRepository<City, Long> {

}

通过从 Spring CrudRepository扩展,我们为数据库实现了一些方法,包括findAll()findOne()。 这样,我们不必编写很多样板代码。

com/zetcode/service/ICityService.java

package com.zetcode.service;

import com.zetcode.model.City;
import java.util.List;

public interface ICityService {

    List<City> findAll();
}        

ICityService提供了一种从数据库中获取所有城市的契约方法。

com/zetcode/service/CityService.java

package com.zetcode.service;

import com.zetcode.model.City;
import com.zetcode.repository.CityRepository;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CityService implements ICityService {

    @Autowired
    private CityRepository repository;

    @Override
    public List<City> findAll() {

        return (List<City>) repository.findAll();
    }
}

CityService包含findAll()方法的实现。 我们使用存储库从数据库检索数据。

@Autowired
private CityRepository repository;

注入CityRepository

return (List<City>) repository.findAll();

存储库的findAll()方法返回城市列表。

com/zetcode/controller/MyController.java

package com.zetcode.controller;

import com.zetcode.model.City;
import com.zetcode.service.ICityService;
import com.zetcode.util.GeneratePdfReport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.io.ByteArrayInputStream;
import java.util.List;

@Controller
public class MyController {

    @Autowired
    private ICityService cityService;

    @RequestMapping(value = "/pdfreport", method = RequestMethod.GET,
            produces = MediaType.APPLICATION_PDF_VALUE)
    public ResponseEntity<InputStreamResource> citiesReport() {

        var cities = (List<City>) cityService.findAll();

        ByteArrayInputStream bis = GeneratePdfReport.citiesReport(cities);

        var headers = new HttpHeaders();
        headers.add("Content-Disposition", "inline; filename=citiesreport.pdf");

        return ResponseEntity
                .ok()
                .headers(headers)
                .contentType(MediaType.APPLICATION_PDF)
                .body(new InputStreamResource(bis));
    }
}

citiesReport()方法返回生成的 PDF 报告。 Resource接口抽象了对低级资源的访问; InputStreamResource是它对流资源的实现。

@Autowired
private ICityService cityService;

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

var cities = (List<City>) cityService.findAll();

我们使用findAll()方法查找所有城市。

ByteArrayInputStream bis = GeneratePdfReport.citiesReport(cities);

GeneratePdfReport.citiesReport()使用 iText 库从城市列表中生成 PDF 文件。

HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "inline; filename=citiesreport.pdf");

通过将Content-Disposition设置为inline,PDF 文件将直接显示在浏览器中。

return ResponseEntity
        .ok()
        .headers(headers)
        .contentType(MediaType.APPLICATION_PDF)
        .body(new InputStreamResource(bis));

我们使用ResponseEntity创建响应。 我们指定标题,内容类型和主体。 内容类型为MediaType.APPLICATION_PDF。 身体是InputStreamResource

com/zetcode/util/GeneratePdfReport.java

package com.zetcode.util;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.FontFactory;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.PdfPCell;
import com.itextpdf.text.pdf.PdfPTable;
import com.itextpdf.text.pdf.PdfWriter;
import com.zetcode.model.City;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.List;

public class GeneratePdfReport {

    private static final Logger logger = LoggerFactory.getLogger(GeneratePdfReport.class);

    public static ByteArrayInputStream citiesReport(List<City> cities) {

        Document document = new Document();
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        try {

            PdfPTable table = new PdfPTable(3);
            table.setWidthPercentage(60);
            table.setWidths(new int[]{1, 3, 3});

            Font headFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD);

            PdfPCell hcell;
            hcell = new PdfPCell(new Phrase("Id", headFont));
            hcell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(hcell);

            hcell = new PdfPCell(new Phrase("Name", headFont));
            hcell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(hcell);

            hcell = new PdfPCell(new Phrase("Population", headFont));
            hcell.setHorizontalAlignment(Element.ALIGN_CENTER);
            table.addCell(hcell);

            for (City city : cities) {

                PdfPCell cell;

                cell = new PdfPCell(new Phrase(city.getId().toString()));
                cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                cell.setHorizontalAlignment(Element.ALIGN_CENTER);
                table.addCell(cell);

                cell = new PdfPCell(new Phrase(city.getName()));
                cell.setPaddingLeft(5);
                cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                cell.setHorizontalAlignment(Element.ALIGN_LEFT);
                table.addCell(cell);

                cell = new PdfPCell(new Phrase(String.valueOf(city.getPopulation())));
                cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
                cell.setHorizontalAlignment(Element.ALIGN_RIGHT);
                cell.setPaddingRight(5);
                table.addCell(cell);
            }

            PdfWriter.getInstance(document, out);
            document.open();
            document.add(table);

            document.close();

        } catch (DocumentException ex) {

            logger.error("Error occurred: {0}", ex);
        }

        return new ByteArrayInputStream(out.toByteArray());
    }
}

GeneratePdfReport根据提供的数据创建 PDF 文件。

ByteArrayOutputStream out = new ByteArrayOutputStream();

数据将被写入ByteArrayOutputStream

PdfPTable table = new PdfPTable(3);

我们将数据放在表格中; 为此,我们有PdfPTable类。 该表具有三列:ID,名称和人口。

Font headFont = FontFactory.getFont(FontFactory.HELVETICA_BOLD);

对于表头,我们使用粗体的 Helvetica 字体。

PdfPCell hcell;
hcell = new PdfPCell(new Phrase("Id", headFont));
hcell.setHorizontalAlignment(Element.ALIGN_CENTER);
table.addCell(hcell);

数据放置在表单元格内,由PdfPCell表示。 使用setHorizontalAlignment()方法将文本水平对齐。

PdfWriter.getInstance(document, out);

使用PdfWriter,将文档写入ByteArrayOutputStream

document.open();
document.add(table);

该表将插入到 PDF 文档中。

document.close();

为了将数据写入ByteArrayOutputStream,必须关闭文档。

return new ByteArrayInputStream(out.toByteArray());

最后,数据返回为ByteArrayInputStream

com/zetcode/Application.java

package com.zetcode;

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

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Application设置 Spring Boot 应用。

$ mvn spring-boot:run

我们启动 Spring Boot 应用。

我们导航到http://localhost:8080/pdfreport以生成报告。

在本教程中,我们展示了如何将生成的 PDF 文件发送回客户端。 PDF 报告是使用 iText 生成的,数据来自 H2 数据库。 我们使用 Spring Data JPA 访问数据。 您可能也对这些相关教程感兴趣: Spring Boot 基本注解Spring Boot H2 教程Spring Boot JasperReports Web 集成Java 教程或列出所有 Spring Boot 教程



回到顶部