首页技术栈归档照片墙音乐日记随想收藏夹友链留言关于

报表技术 POI 与 PDF

写作时间:2026-07-07

POI基本介绍

在企业级应用开发中,Excel报表是一种最常见的报表需求。Excel报表开发一般分为两种形式: 1、为了方便操作,基于Excel的报表批量上传数据,也就是把Excel中的数据导入到系统中。 2、通过java代码生成Excel报表。也就是把系统中的数据导出到Excel中,方便查阅。

Excel的两种版本

目前世面上的Excel分为两个大的版本Excel2003和Excel2007及以上两个版本; 两者之间的区别如下:

1585643739275

Excel2003 是一个特有的二进制格式,其核心结构是复合文档类型的结构,存储数据量较小;

Excel2007 的核心结构是 XML 类型的结构,采用的是基于 XML 的压缩方式,使其占用的空间更小,

​ 操作效率更高

** 常见的Excel操作工具**

Java中常见的用来操作Excel的方式一般有2种:JXL和POI。

JXL

JXL只能对Excel进行操作,属于比较老的框架,它只支持到Excel 95-2000的版本。现在已经停止更新和 维护,所以本课程中只时简单地演示一下jxl的代码,不会把它作为重点,

POI

POI是apache的项目,可对微软的Word,Excel,PPT进行操作,包括office2003和2007,Excle2003和2007。 poi现在一直有更新。所以现在主流使用POI。

Apache POI是Apache软件基金会的开源项目,由Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java语言操作Microsoft Office的功能。

API对象介绍 工作簿 : WorkBook (HSSFWordBook : 2003版本,XSSFWorkBook : 2007级以上) 工作表 : Sheet (HSSFSheet : 2003版本,XSSFSheet : 2007级以上) 行 : Row (HSSFRow : 2003版本,XSSFRow : 2007级以上) 单元格 : Cell (HSSFCell :

POI基础操作

使用JXL导出excel

目前Excel的版本有2013、2010、2007,这些都是新版本的excel,新版本的excel已经出现十多年了,使用人群已经比较多了,所以目前做项目大都做的是导出新版本的excel,而jxl只能操作低版本的excel,所以现在使用jxl做项目已经比较少见,那我们在这里使用jxl导出一个简单一些的excel。

通过WritableWorkbook,WritableSheet,Label这三个对象我们就可以实现Excel文件的导出工作。

1、 创建可写入的Excel工作薄

WritableWorkbook workbook= Workbook.createWorkbook(输出流);

2、创建工作表

WritableSheet sheet= workbook.createSheet(工作表的名称, 工作表的索引值);

3、创建单元格

添加文本类单元格

Label labelC = new Label(列索引值, 行索引值, "单元格中的内容");

sheet.addCell(labelC);

4、写入到文件

workbook.write();// 写入数据

5、释放资源:

workbook.close();// 关闭文件

代码实现导出用户列表数据

第一步:UserController中添加方法

@GetMapping(value = "/downLoadXlsByJxl",name = "使用jxl下载")
public void downLoadXlsByJxl(HttpServletResponse response){
    userService.downLoadByJxl(response);
}

第二步:UserService中的方法

private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-ss");

public void downLoadXlsByJxl(HttpServletResponse response){
        try {
//            创建一个工作薄
            ServletOutputStream outputStream = response.getOutputStream();
            WritableWorkbook workbook = Workbook.createWorkbook(outputStream);
//            创建一个工作表
            WritableSheet sheet = workbook.createSheet("一个JXL入门", 0);
//            设置列宽
            sheet.setColumnView(0,5);
            sheet.setColumnView(1,8);
            sheet.setColumnView(2,15);
            sheet.setColumnView(3,15);
            sheet.setColumnView(4,30);
//            处理标题
            String[] titles = new String[]{"编号","姓名","手机号","入职日期","现住址"};
            Label label = null;
            for (int i = 0; i < titles.length; i++) {
                label = new Label(i,0,titles[i]);
                sheet.addCell(label);
            }
//            处理导出的内容
            List<User> userList = this.findAll();
            int rowIndex = 1;
            for (User user : userList) {
                label = new Label(0,rowIndex,user.getId().toString());
                sheet.addCell(label);
                label = new Label(1,rowIndex,user.getUserName());
                sheet.addCell(label);
                label = new Label(2,rowIndex,user.getPhone());
                sheet.addCell(label);
                label = new Label(3,rowIndex,simpleDateFormat.format(user.getHireDate()));
                sheet.addCell(label);
                label = new Label(4,rowIndex,user.getAddress());
                sheet.addCell(label);
                rowIndex++;
            }

            //            导出的文件名称
            String filename="一个JXL入门.xls";
//            设置文件的打开方式和mime类型
             response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
            response.setContentType("application/vnd.ms-excel");
//            导出
            workbook.write();
//            关闭资源
            workbook.close();
            outputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

导出内容如下:

1585664186829

POI操作excel

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>4.0.1</version>
</dependency>

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>4.0.1</version>
</dependency>

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml-schemas</artifactId>
    <version>4.0.1</version>
</dependency>

在POI包中有如下几个主要对象和excel的几个对象对应:

对应excel名称 低版本中的类名 高版本中的类名
工作簿 HSSFWorkbook XSSFWorkbook
工作表 HSSFSheet XSSFSheet
行 HSSFRow XSSFRow
单元格 HSSFCell XSSFCell
单元格样式 HSSFCellStyle XSSFCellStyle

操作低版本:

package com.itheima.demo;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
public class POIDemo01 {
    public static void main(String[] args) throws Exception{
        Workbook workbook = new HSSFWorkbook(); //创建了一个全新(里面什么都没有)的工作薄
        Sheet sheet = workbook.createSheet("demo测试");  //创建了一个全新(里面什么都没有)的工作表
        Row row = sheet.createRow(0);  //创建了第一行(空的)
        Cell cell = row.createCell(0);//创建的是第一行的第一个单元格
        cell.setCellValue("这是我第一次玩POI");
//        把工作薄输出到本地磁盘
        workbook.write(new FileOutputStream("d://test.xls"));
    }
}

操作高版本:

package com.itheima.demo;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.FileOutputStream;

public class POIDemo02 {
    public static void main(String[] args) throws Exception{
        Workbook workbook = new XSSFWorkbook(); //创建了一个全新(里面什么都没有)的工作薄
        Sheet sheet = workbook.createSheet("demo测试");  //创建了一个全新(里面什么都没有)的工作表
        Row row = sheet.createRow(0);  //创建了第一行(空的)
        Cell cell = row.createCell(0);//创建的是第一行的第一个单元格
        cell.setCellValue("这是我第一次玩POI");
//        把工作薄输出到本地磁盘
        workbook.write(new FileOutputStream("d://test.xlsx"));
    }
}

比较上面两个代码会发现,在开发中只是类的名称不一样,方法是一样的。

实现用户数据的导入

第一步:在Controller接收文件,具体的实现交给service

@PostMapping(value = "/uploadExcel", name = "上传用户数据")
public void uploadExcel(MultipartFile file)  throws Exception{
   userService.uploadExcel(file);
}

第二步:UserService添加上传用户的方法

public void uploadExcel(MultipartFile file) throws Exception {
    Workbook workbook = new XSSFWorkbook(file.getInputStream()); //根据上传的输入流创建workbook
    Sheet sheet = workbook.getSheetAt(0); //获取工作薄中的第一个工作表
    int lastRowIndex = sheet.getLastRowNum(); //获取这个sheet中最后一行数据,为了循环遍历

    //        以下三个为了节省栈内存,所以提到循环的外面
    User user = null;
    Row row = null;
    Cell cell = null;

    //开始循环每行,获取每行的单元格中的值,放入到user属性中
    for (int i = 1; i <= lastRowIndex; i++) {
        row = sheet.getRow(i);
        user = new User();
        //          因为第一个列单元格中是字符串,可以直接使用getStringCellValue方法
        String userName = row.getCell(0).getStringCellValue(); //用户名
        user.setUserName(userName);
        String phone = null; //手机号
        try {
            phone = row.getCell(1).getStringCellValue();
        } catch (IllegalStateException e) {
            phone = row.getCell(1).getNumericCellValue()+"";
        }
        user.setPhone(phone);
        String province = row.getCell(2).getStringCellValue(); //省份
        user.setProvince(province);
        String city = row.getCell(3).getStringCellValue(); //城市
        user.setCity(city);
        //            因为在填写excel中的数据时就可以约定这个列只能填写数值,所以可以直接用getNumericCellValue方法
        Integer salary = ((Double)row.getCell(4).getNumericCellValue()).intValue(); //工资
        user.setSalary(salary);
        String hireDateStr = row.getCell(5).getStringCellValue(); //入职日期
        Date hireDate = simpleDateFormat.parse(hireDateStr);
        user.setHireDate(hireDate);
        String birthdayStr = row.getCell(6).getStringCellValue(); //出生日期
        Date birthday = simpleDateFormat.parse(birthdayStr);
        user.setBirthday(birthday);

        String address = row.getCell(7).getStringCellValue(); //现住地址
        user.setAddress(address);
        userMapper.insert(user);
    }

}

上传成功后直接查询数据库中的数据:

1585753500701

实现用户数据的导出

第一步:在Controller添加方法,具体的实现交给service

@GetMapping(value = "/downLoadXlsxByPoi",name = "使用POI下载高版本")
public void downLoadXlsx(HttpServletResponse response) throws Exception{
    userService.downLoadXlsx(response);
}

第二步:UserService中实现

public void downLoadXlsx(HttpServletResponse response) throws Exception {
    //        创建一个空的工作薄
    Workbook workbook = new XSSFWorkbook();
    //        在工作薄中创建一个工作表
    Sheet sheet = workbook.createSheet("测试");
    //        设置列宽
    sheet.setColumnWidth(0,5*256);
    sheet.setColumnWidth(1,8*256);
    sheet.setColumnWidth(2,15*256);
    sheet.setColumnWidth(3,15*256);
    sheet.setColumnWidth(4,30*256);
    //            处理标题
    String[] titles = new String[]{"编号","姓名","手机号","入职日期","现住址"};

    //        创建标题行
    Row titleRow = sheet.createRow(0);
    Cell cell = null;
    for (int i = 0; i < titles.length; i++) {
        cell = titleRow.createCell(i);
        cell.setCellValue(titles[i]);
    }
    //        处理内容
    List<User> userList = this.findAll();
    int rowIndex = 1;
    Row row = null;
    for (User user : userList) {
        row = sheet.createRow(rowIndex);
        cell = row.createCell(0);
        cell.setCellValue(user.getId());

        cell = row.createCell(1);
        cell.setCellValue(user.getUserName());

        cell = row.createCell(2);
        cell.setCellValue(user.getPhone());

        cell = row.createCell(3);
        cell.setCellValue(simpleDateFormat.format(user.getHireDate()));

        cell = row.createCell(4);
        cell.setCellValue(user.getAddress());

        rowIndex++;
    }
    //            导出的文件名称
    String filename="员工数据.xlsx";
    //            设置文件的打开方式和mime类型
    ServletOutputStream outputStream = response.getOutputStream();
    response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    workbook.write(outputStream);

}

1585753903405

导出时设置样式

如果要求导出的excel如下内容:

1585754689384

通过上图可以看出有些样式需要我们来设置,来看一下都有哪些知识点:

  1. 画框线
/**
* 设置框线
*/
HSSFCellStyle contentStyle = book.createCellStyle();
contentStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN);//底线
contentStyle.setBorderTop(HSSFCellStyle.BORDER_THIN);//顶部线
contentStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN);//左侧线
contentStyle.setBorderRight(HSSFCellStyle.BORDER_THIN);//右侧线

  1. 合并单元格
//合并单元格 起始行, 结束行, 起始列, 结束列		
sheet.addMergedRegion(new CellRangeAddress(0,0,0,4));
  1. 设置行高
/**
设置行高
*/
sheet.getRow(1).setHeight((short)500);
  1. 设置表格的对齐方式和字体
//*设置对齐方式和字体***/
//内容部分的样式
style_content.setAlignment(HSSFCellStyle.ALIGN_CENTER);//设置水平居中
style_content.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//设置垂直居中

HSSFFont font = book.createFont();//创建字体
font.setFontName("宋体");//设置字体名称
font.setFontHeightInPoints((short)11);//设置字体大小
style_content.setFont(font);//对样式设置字体

//标题样式
HSSFCellStyle style_title = book.createCellStyle();//创建标题样式
style_title.setAlignment(HSSFCellStyle.ALIGN_CENTER);//设置水平居中
style_title.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//设置垂直居中
HSSFFont titleFont = book.createFont();//设置标题字体
titleFont.setFontName("黑体");
titleFont.setBold(true);//加粗
titleFont.setFontHeightInPoints((short)18);//字体大小    
style_title.setFont(titleFont);//将标题字体设置到标题样式
sheet.getRow(0).getCell(0).setCellStyle(style_title);//单元格设置标题样式

基于模板导出列表数据

按照以下样式导出excel

1585835889460

第一步:准备一个excel作为导出的模板,模板内容如下

第一个sheet:

1585836080606

第二个sheet:

1585836034890

第二步:把这个模板改一个英文名称比如:userList.xlsx,放入到项目中

1585836590367

第三步:UserService实现方法

public void downLoadXlsxWithTempalte(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //        获取模板的路径
    File rootPath = new File(ResourceUtils.getURL("classpath:").getPath()); //SpringBoot项目获取根目录的方式
    File templatePath = new File(rootPath.getAbsolutePath(),"/excel_template/userList.xlsx");
    //        读取模板文件产生workbook对象,这个workbook是一个有内容的工作薄
    Workbook workbook  = new XSSFWorkbook(templatePath);
    //        读取工作薄的第一个工作表,向工作表中放数据
    Sheet sheet = workbook.getSheetAt(0);
    //        获取第二个的sheet中那个单元格中的单元格样式
    CellStyle cellStyle = workbook.getSheetAt(1).getRow(0).getCell(0).getCellStyle();
    //        处理内容
    List<User> userList = this.findAll();
    int rowIndex = 2;
    Row row = null;
    Cell cell = null;
    for (User user : userList) {
        row = sheet.createRow(rowIndex);
        row.setHeightInPoints(15); //设置行高

        cell = row.createCell(0);
        cell.setCellValue(user.getId());
        cell.setCellStyle(cellStyle); //设置单元格样式

        cell = row.createCell(1);
        cell.setCellValue(user.getUserName());
        cell.setCellStyle(cellStyle);

        cell = row.createCell(2);
        cell.setCellValue(user.getPhone());
        cell.setCellStyle(cellStyle);

        cell = row.createCell(3);
        cell.setCellValue(simpleDateFormat.format(user.getHireDate()));
        cell.setCellStyle(cellStyle);

        cell = row.createCell(4);
        cell.setCellValue(user.getAddress());
        cell.setCellStyle(cellStyle);

        rowIndex++;
    }
    //            导出的文件名称
    String filename="用户列表数据.xlsx";
    //            设置文件的打开方式和mime类型
    ServletOutputStream outputStream = response.getOutputStream();
    response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    workbook.write(outputStream);

}

第四步:修改UserController中的方法,导出测试

@GetMapping(value = "/downLoadXlsxByPoi",name = "使用POI下载高版本")
public void downLoadXlsx(HttpServletRequest request,HttpServletResponse response) throws Exception{
    //       userService.downLoadXlsx(response);
    userService.downLoadXlsxWithTempalte(request,response); //下载的excel带样式
}

导出用户详细数据

如下,点击用户列表中的下载按钮,下载文件内容如下

1587091568056

思路

最简单的方式就是先根据案例制作模板,导出时查询用户数据、读取模板,把数据放入到模板中对应的单元格中,其中我们先处理最基本的数据,稍后再处理图片

实现

第一步:制作一个excel导出模板,如下

1587093493548

第二步:制作好的模板放入到项目中

1587093536347

第三步:Controller中添加方法

@GetMapping(value = "/download",name = "导出用户详细信息")
public void downLoadUserInfoWithTempalte(Long id,HttpServletRequest request,HttpServletResponse response) throws Exception{
    userService.downLoadUserInfoWithTempalte(id,request,response);
}

第四步:在UserService中添加方法

    public void downLoadUserInfoWithTempalte(Long id, HttpServletRequest request, HttpServletResponse response) throws Exception  {
        //        获取模板的路径
        File rootPath = new File(ResourceUtils.getURL("classpath:").getPath()); //SpringBoot项目获取根目录的方式
        File templatePath = new File(rootPath.getAbsolutePath(),"/excel_template/userInfo.xlsx");
//        读取模板文件产生workbook对象,这个workbook是一个有内容的工作薄
        Workbook workbook  = new XSSFWorkbook(templatePath);
//        读取工作薄的第一个工作表,向工作表中放数据
        Sheet sheet = workbook.getSheetAt(0);
//        处理内容
        User user = userMapper.selectByPrimaryKey(id);
//        接下来向模板中单元格中放数据
//        用户名   第2行第2列
        sheet.getRow(1).getCell(1).setCellValue(user.getUserName());
//        手机号   第3行第2列
        sheet.getRow(2).getCell(1).setCellValue(user.getPhone());
//        生日     第4行第2列  日期转成字符串
        sheet.getRow(3).getCell(1).setCellValue
            (simpleDateFormat.format(user.getBirthday()));
//        工资 第5行第2列
        sheet.getRow(4).getCell(1).setCellValue(user.getSalary());
//        工资 第6行第2列
        sheet.getRow(5).getCell(1).setCellValue
            (simpleDateFormat.format(user.getHireDate()));
//        省份     第7行第2列
        sheet.getRow(6).getCell(1).setCellValue(user.getProvince());
//        现住址   第8行第2列
        sheet.getRow(7).getCell(1).setCellValue(user.getAddress());
//        司龄     第6行第4列暂时先不考虑

//        城市     第7行第4列
        sheet.getRow(6).getCell(3).setCellValue(user.getCity());

//            导出的文件名称
        String filename="用户详细信息数据.xlsx";
//            设置文件的打开方式和mime类型
        ServletOutputStream outputStream = response.getOutputStream();
        response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        workbook.write(outputStream);
    }

点击页面上的下载按钮,效果如下:

1587095143882

导出数据带图片、公式

导出图片

个人信息的导出中包含了头像照片,需要用到POI的导出图片功能,那么POI主要提供了两个类来处理照片,这两个类是Patriarch和ClientAnchor前者负责在表中创建图片,后者负责设置图片的大小位置。

在UserService的方法中添加以下代码

// 先创建一个字节输出流
ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
// BufferedImage是一个带缓冲区图像类,主要作用是将一幅图片加载到内存中
BufferedImage bufferImg = ImageIO.read(new File(rootPath + user.getPhoto()));
// 把读取到图像放入到输出流中
ImageIO.write(bufferImg, "jpg", byteArrayOut);
// 创建一个绘图控制类,负责画图
Drawing patriarch = sheet.createDrawingPatriarch();
// 指定把图片放到哪个位置
ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, 2, 1, 4, 5);
// 开始把图片写入到sheet指定的位置
patriarch.createPicture(anchor, workbook.addPicture(
    byteArrayOut.toByteArray(), Workbook.PICTURE_TYPE_JPEG));

关于XSSFClientAnchor的8个参数说明:

dx1 - the x coordinate within the first cell.//定义了图片在第一个cell内的偏移x坐标,既左上角所在cell的偏移x坐标,一般可设0
dy1 - the y coordinate within the first cell.//定义了图片在第一个cell的偏移y坐标,既左上角所在cell的偏移y坐标,一般可设0
dx2 - the x coordinate within the second cell.//定义了图片在第二个cell的偏移x坐标,既右下角所在cell的偏移x坐标,一般可设0
dy2 - the y coordinate within the second cell.//定义了图片在第二个cell的偏移y坐标,既右下角所在cell的偏移y坐标,一般可设0

col1 - the column (0 based) of the first cell.//第一个cell所在列,既图片左上角所在列
row1 - the row (0 based) of the first cell.//图片左上角所在行
col2 - the column (0 based) of the second cell.//图片右下角所在列
row2 - the row (0 based) of the second cell.//图片右下角所在行

导出公式

应用场景说明,在导出用户详细数据时有一个司龄的显示,这里的司龄就是截止到现在入职到本公司的时间,为了学习POI对公式的操作,我们这里使用POI的公式来做。

计算截止到现在入职到本公司的时间应该用到两个日期相差的函数:DATEDIF函数,这个函数需要3个参数

P1: 一个日期 P2:截止日期 P3: 时间单位 举例:

1、DATEDIF("2015-10-01","2020-10-01","y") 结果是5

2、CONCATENATE(DATEDIF("2015-10-01","2020-10-01","y")),"年",DATEDIF("2015-10-01","2020-10-01","ym"),"个月") 结果是5年0个月

放到这个用户导出时,第一个参数就是放到相应单元格上数据,第二个参数就是当天时间,

如果直接在excel中操作,如下:

1587129195878

在使用POI导出时使用setCellFormula方法来设置公式:

1587129258183

关于POI支持公式详见官网: https://poi.apache.org/components/spreadsheet/eval-devguide.html

百万数据导出

概述

​ 我们都知道Excel可以分为早期的Excel2003版本(使用POI的HSSF对象操作)和Excel2007版本(使用POI的XSSF操作),两者对百万数据的支持如下: ​ Excel 2003:在POI中使用HSSF对象时,excel 2003最多只允许存储65536条数据,一般用来处理较少的数据量。这时对于百万级别数据,Excel肯定容纳不了。 ​ Excel 2007:当POI升级到XSSF对象时,它可以直接支持excel2007以上版本,因为它采用ooxml格式。这时excel可以支持1048576条数据,单个sheet表就支持近百万条数据。但实际运行时还可能存在问题,原因是执行POI报表所产生的行对象,单元格对象,字体对象,他们都不会销毁,这就导致OOM的风险。

解决方案分析

​ 对于百万数据量的Excel导入导出,只讨论基于Excel2007的解决方法。在ApachePoi 官方提供了对操作大数据量的导入导出的工具和解决办法,操作Excel2007使用XSSF对象,可以分为三种模式:

java代码解析xml

dom4j:一次性加载xml文件再解析

SAX:逐行加载,逐行解析

**用户模式:**用户模式有许多封装好的方法操作简单,但创建太多的对象,非常耗内存(之前使用的方法)

**事件模式:**基于SAX方式解析XML,SAX全称Simple API for XML,它是一个接口,也是一个软件包。它是一种XML解析的替代方法,不同于DOM解析XML文档时把所有内容一次性加载到内存中的方式,它逐行扫描文档,一边扫描,一边解析。

SXSSF对象:是用来生成海量excel数据文件,主要原理是借助临时存储空间生成excel

1571231226389

**原理分析 **

在实例化SXSSFWorkBook这个对象时,可以指定在内存中所产生的POI导出相关对象的数量(默认100),一旦内存中的对象的个数达到这个指定值时,就将内存中的这些对象的内容写入到磁盘中(XML的文件格式),就可以将这些对象从内存中销毁,以后只要达到这个值,就会以类似的处理方式处理,直至Excel导出完成。

百万数据的导出

模拟数据

第一步、创建表

CREATE TABLE `tb_user2` (
  `id` bigint(20) NOT NULL  COMMENT '用户ID',
  `user_name` varchar(100) DEFAULT NULL COMMENT '姓名',
  `phone` varchar(15) DEFAULT NULL COMMENT '手机号',
  `province` varchar(50) DEFAULT NULL COMMENT '省份',
  `city` varchar(50) DEFAULT NULL COMMENT '城市',
  `salary` int(10) DEFAULT NULL,
  `hire_date` datetime DEFAULT NULL COMMENT '入职日期',
  `dept_id` bigint(20) DEFAULT NULL COMMENT '部门编号',
  `birthday` datetime DEFAULT NULL COMMENT '出生日期',
  `photo` varchar(200) DEFAULT NULL COMMENT '照片路径',
  `address` varchar(300) DEFAULT NULL COMMENT '现在住址' 
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2、创建存储过程

DELIMITER $$    -- 重新定义“;”分号
DROP PROCEDURE IF EXISTS test_insert $$   -- 如果有test_insert这个存储过程就删除
CREATE PROCEDURE test_insert()			  -- 创建存储过程

BEGIN
   DECLARE n int DEFAULT 1;				    -- 定义变量n=1
   SET AUTOCOMMIT=0;						    -- 取消自动提交

   	while n <= 5000000 do					
   		INSERT INTO `tb_user2` VALUES ( n, CONCAT('测试', n), '13800000001', '北京市', '北京市', '11000', '2001-03-01 21:18:29', '1', '1981-03-02 00:00:00', '\\static\\user_photos\\1.jpg', '北京市西城区宣武大街1号院');
   		SET n=n+1;
   	END while;
   	COMMIT;
END $$

3、开始执行

CALL test_insert();

插入500W数据大概需要200至300秒左右

思路分析

导出时使用的是SXSSFWorkBook这个类,一个工作表sheet最多只能放1048576行数据, 当我们的业务数据已超过100万了,一个sheet就不够用了,必须拆分到多个工作表。

导出百万数据时有两个弊端:

1、不能使用模板

2、不能使用太多的样式

也就是说导出的数据太多时必须要放弃一些。

代码实现

UserController代码

@GetMapping(value = "/downLoadMillion",name = "导出用户百万数据的导出")
public void downLoadMillion(Long id,HttpServletRequest request,HttpServletResponse response) throws Exception{
    userService.downLoadMillion(request,response);
}

UserService代码

public void downLoadMillion(HttpServletRequest request, HttpServletResponse response) throws Exception {
//        创建一个空的工作薄
        Workbook workbook = new SXSSFWorkbook();
        int page = 1;
        int pageSize=200000;
        int rowIndex = 1; //每一个工作页的行数
        int num = 0; //总数据量
        Row row = null;
        Cell cell = null;
        Sheet sheet = null;
        while (true){  //不停地查询
             List<User> userList = this.findPage(page,pageSize);
             if(CollectionUtils.isEmpty(userList)){  //如果查询不到就不再查询了
                 break;
             }
            if(num%1000000==0){  //每100W个就重新创建新的sheet和标题
                rowIndex = 1;
                 //        在工作薄中创建一个工作表
                 sheet = workbook.createSheet("第"+((num/1000000)+1)+"个工作表");
//        设置列宽
                 sheet.setColumnWidth(0,8*256);
                 sheet.setColumnWidth(1,12*256);
                 sheet.setColumnWidth(2,15*256);
                 sheet.setColumnWidth(3,15*256);
                 sheet.setColumnWidth(4,30*256);
                 //            处理标题
                 String[] titles = new String[]{"编号","姓名","手机号","入职日期","现住址"};
                //        创建标题行
                Row titleRow = sheet.createRow(0);

                for (int i = 0; i < titles.length; i++) {
                    cell = titleRow.createCell(i);
                    cell.setCellValue(titles[i]);
                }
             }

//        处理内容

        for (User user : userList) {
            row = sheet.createRow(rowIndex);
            cell = row.createCell(0);
            cell.setCellValue(user.getId());

            cell = row.createCell(1);
            cell.setCellValue(user.getUserName());

            cell = row.createCell(2);
            cell.setCellValue(user.getPhone());

            cell = row.createCell(3);
            cell.setCellValue(simpleDateFormat.format(user.getHireDate()));

            cell = row.createCell(4);
            cell.setCellValue(user.getAddress());
            rowIndex++;
            num++;
        }
          page++;// 继续查询下一页
        }
//            导出的文件名称
        String filename="百万数据.xlsx";
//            设置文件的打开方式和mime类型
        ServletOutputStream outputStream = response.getOutputStream();
        response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        workbook.write(outputStream);
    }

测试结果

导出的这个文档大概需要3-5分钟的时间,有105 MB,内容如下

1587221323911

百万数据导入

使用POI基于事件模式解析案例提供的Excel文件

**用户模式:**加载并读取Excel时,是通过一次性的将所有数据加载到内存中再去解析每个单元格内容。当Excel数据量较大时,由于不同的运行环境可能会造成内存不足甚至OOM异常。

例如读取我们刚刚导出的百万数据:

package com.itheima.test;

import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

//测试百万数据的导入
public class POIDemo5 {
    public static void main(String[] args) throws Exception {
        XSSFWorkbook workbook = new XSSFWorkbook("C:\\Users\\syl\\Desktop\\百万用户数据的导出.xlsx");
        XSSFSheet sheetAt = workbook.getSheetAt(0);
        String stringCellValue = sheetAt.getRow(0).getCell(0).getStringCellValue();
        System.out.println(stringCellValue);
    }
}

会直接报内存溢出的错误:

1574905257439

**事件模式:**它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势。

代码实现

步骤分析

(1)设置POI的事件模式 根据Excel获取文件流 根据文件流创建OPCPackage 用来组合读取到的xml 组合出来的数据占用的空间更小 创建XSSFReader对象 (2)Sax解析 自定义Sheet处理器 创建Sax的XmlReader对象 设置Sheet的事件处理器 逐行读取

自定义处理器

package com.itheima.test;

import com.itheima.pojo.User;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.usermodel.XSSFComment;

public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {

//    编号 用户名  手机号  入职日期 现住址
    private User user=null;
    @Override
    public void startRow(int rowIndex) { //每一行的开始   rowIndex代表的是每一个sheet的行索引
        if(rowIndex==0){
            user = null;
        }else{
            user = new User();
        }
    }
    @Override  //处理每一行的所有单元格
    public void cell(String cellName, String cellValue, XSSFComment comment) {

        if(user!=null){
            String letter = cellName.substring(0, 1);  //每个单元名称的首字母 A  B  C
            switch (letter){
                case "A":{
                    user.setId(Long.parseLong(cellValue));
                    break;
                }
                case "B":{
                    user.setUserName(cellValue);
                    break;
                }
            }
        }
    }
    @Override
    public void endRow(int rowIndex) { //每一行的结束
        if(rowIndex!=0){
            System.out.println(user);
        }

    }
}

自定义解析

在今天的资料中提供了这个类,直接可以拿过来使用

package com.itheima.test;

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.model.StylesTable;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import java.io.InputStream;

/**
 * 自定义Excel解析器
 */
public class ExcelParser {

    public void parse (String path) throws Exception {
        //1.根据Excel获取OPCPackage对象
        OPCPackage pkg = OPCPackage.open(path, PackageAccess.READ);
        try {
            //2.创建XSSFReader对象
            XSSFReader reader = new XSSFReader(pkg);
            //3.获取SharedStringsTable对象
            SharedStringsTable sst = reader.getSharedStringsTable();
            //4.获取StylesTable对象
            StylesTable styles = reader.getStylesTable();
            XMLReader parser = XMLReaderFactory.createXMLReader();
            // 处理公共属性:Sheet名,Sheet合并单元格
            parser.setContentHandler(new XSSFSheetXMLHandler(styles,sst, new SheetHandler(), false));
            XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) reader.getSheetsData();
            while (sheets.hasNext()) {
                InputStream sheetstream = sheets.next();
                InputSource sheetSource = new InputSource(sheetstream);
                try {
                    parser.parse(sheetSource);
                } finally {
                    sheetstream.close();
                }
            }
        } finally {
            pkg.close();
        }
    }
}

用户模式下读取测试Excel文件直接内存溢出,测试Excel文件映射到内存中还是占用了不少内存;事件模式下可以流畅的运行。

测试

使用事件模型解析

public class POIDemo5 {
    public static void main(String[] args) throws Exception{
        new ExcelParser().parse("C:\\Users\\syl\\Desktop\\百万用户数据的导出.xlsx");
    }
}

opencsv操作CSV文件

CSV文件简介

现在好多的网站中导出的文件会出现一种csv文件,我们接下来学习一下csv文件的导出方式。

CSV文件:Comma-Separated Values,中文叫逗号分隔值或者字符分割值,其文件以纯文本的形式存储表格数据。该文件是一个字符序列,可以由任意数目的记录组成,记录间以某种换行符分割。每条记录由字段组成,字段间的分隔符是其他字符或者字符串。所有的记录都有完全相同的字段序列,相当于一个结构化表的纯文本形式。 用文本文件、excel或者类似与文本文件的编辑器都可以打开CSV文件。

为了简化开发,我们可以使用opencsv类库来导出csv文件

需要的依赖

<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>4.5</version>
</dependency>

opencsv常用API

写入到csv文件会用到CSVWriter对象,创建此对象常见API如下

1587911569075

使用CSVWriter对象写入数据常用的方法如下:

1587911679193

读取csv文件会用到CSVReader对象,创建此对象常见API如下

1589507803252

构造器涉及到的三个参数:

  1. reader:读取文件的流对象,常有的是BufferedReader,InputStreamReader。
  2. separator:用于定义前面提到的分割符,默认为逗号CSVWriter.DEFAULT_SEPARATOR用于分割各列。
  3. quotechar:用于定义各个列的引号,有时候csv文件中会用引号或者其它符号将一个列引起来,例如一行可能是:"1","2","3",如果想读出的字符不包含引号,就可以把参数设为:"CSVWriter.NO_QUOTE_CHARACTER "

read方法

1589507858623

导出CSV文件

UserController代码

@GetMapping(value = "/downLoadCSV",name = "导出用户数据到CSV文件中")
public void downLoadCSV(HttpServletResponse response){
    userService.downLoadCSV(response);
}

UserService代码

public void downLoadCSV(HttpServletResponse response) {

    try {
        //            准备输出流
        ServletOutputStream outputStream = response.getOutputStream();
        //            文件名
        String filename="百万数据.csv";
        //            设置两个头 一个是文件的打开方式 一个是mime类型
        response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
        response.setContentType("text/csv");
        //            创建一个用来写入到csv文件中的writer
        CSVWriter writer = new CSVWriter(new OutputStreamWriter(outputStream,"utf-8"));
        //            先写头信息
        writer.writeNext(new String[]{"编号","姓名","手机号","入职日期","现住址"});

        //            如果文件数据量非常大的时候,我们可以循环查询写入
        int page = 1;
        int pageSize=200000;
        while (true) {  //不停地查询
            List<User> userList = this.findPage(page, pageSize);
            if (CollectionUtils.isEmpty(userList)) {  //如果查询不到就不再查询了
                break;
            }
            //                把查询到的数据转成数组放入到csv文件中
            for (User user : userList) {
                writer.writeNext(new String[]{user.getId().toString(),user.getUserName(),user.getPhone(),simpleDateFormat.format(user.getHireDate()),user.getAddress()});
            }
            writer.flush();
            page++;
        }
        writer.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

读取CSV文件

读取刚才导出的CSV文件

package com.itheima.test;

import com.itheima.pojo.User;
import com.opencsv.CSVReader;

import java.io.FileReader;
import java.text.SimpleDateFormat;
import java.time.Year;
import java.util.List;

//读取百万级数据的csv文件
public class CsvDemo {

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public static void main(String[] args) throws Exception {
        CSVReader csvReader = new CSVReader(new FileReader("d:\\百万用户数据的导出.csv"));
        String[] titles = csvReader.readNext(); //读取到第一行 是小标题
//        "编号","姓名","手机号","入职日期","现住址"
        User user = null;
        while (true){
            user = new User();
            String[] content = csvReader.readNext();
            if(content==null){
                break;
            }
            user.setId(Long.parseLong(content[0]));
            user.setUserName(content[1]);
            user.setPhone(content[2]);
            user.setHireDate(simpleDateFormat.parse(content[3]));
            user.setAddress(content[4]);
            System.out.println(user);
        }
    }
}

1589525008048

easyPOI

简介

以上在导出导出excel、导出csv、word时代码有点过于繁琐,好消息是近两年在开发市场上流行一种简化POI开发的类库:easyPOI。从名称上就能发现就是为了简化开发。

能干什么?

Excel的快速导入导出,Excel模板导出,Word模板导出,可以仅仅5行代码就可以完成Excel的导入导出,修改导出格式简单粗暴,快速有效。

为谁而开发?

不太熟悉poi的 不想写太多重复太多的 只是简单的导入导出的 喜欢使用模板的 都可以使用easypoi

目标是什么? Easypoi的目标不是替代poi,而是让一个不懂导入导出的快速使用poi完成Excel和word的各种操作,而不是看很多api才可以完成这样工作。

再次强调一下easyPOI完全替代不了POI!

需要的依赖

或SpringBoot

<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-spring-boot-starter</artifactId>
    <version>4.1.0</version>
</dependency>

注解方式导出

第一步:修改实体类,添加注解

其中主要用到的注解是@Excel注解,更详细的说明请看这里 (按住ctrl点击)

此处注意必须要有空构造函数,否则会报错“对象创建错误”

package com.itheima.pojo;
import cn.afterturn.easypoi.excel.annotation.Excel;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.opencsv.bean.CsvBindByName;
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
import java.util.List;
/**
 * 员工
 */
@Data
@Table(name="tb_user")
public class User {
    @Id
    @KeySql(useGeneratedKeys = true)
    @Excel(name = "编号", orderNum = "0", width = 5)
    private Long id;         //主键
    @Excel(name = "员工名", orderNum = "1", width = 15)
    private String userName; //员工名
    @Excel(name = "手机号", orderNum = "2", width = 15)
    private String phone;    //手机号
    @Excel(name = "省份名", orderNum = "3", width = 15)
    private String province; //省份名
    @Excel(name = "城市名", orderNum = "4", width = 15)
    private String city;     //城市名
    @Excel(name = "工资", orderNum = "5", width = 10)
    private Integer salary;   // 工资
    @JsonFormat(pattern="yyyy-MM-dd")
    @Excel(name = "入职日期",  format = "yyyy-MM-dd",orderNum = "6", width = 15)
    private Date hireDate; // 入职日期
    private String deptId;   //部门id
    @Excel(name = "出生日期",  format = "yyyy-MM-dd",orderNum = "7", width = 15)
    private Date birthday; //出生日期
    @Excel(name = "照片", orderNum = "10",width = 15,type = 2)
    private String photo;    //一寸照片
    @Excel(name = "现在居住地址", orderNum = "9", width = 30)
    private String address;  //现在居住地址

    private List<Resource> resourceList; //办公用品

}

第二步:UserController添加方法

@GetMapping(value = "/downLoadWithEasyPOI",name = "使用EasyPOI下载Excel")
public void downLoadWithEasyPOI(HttpServletRequest request,HttpServletResponse response) throws Exception{
    userService.downLoadXlsxWithEayPoi(request,response);
}

第三步:UserService实现方法

public void downLoadXlsxWithEayPoi(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //        查询用户数据
    List<User> userList = userMapper.selectAll();
    //指定导出的格式是高版本的格式
    ExportParams exportParams = new ExportParams("员工信息", "数据",ExcelType.XSSF);
    //        直接使用EasyPOI提供的方法
    Workbook workbook = ExcelExportUtil.exportExcel(exportParams, User.class, userList);
    String filename="员工信息.xlsx";
    //            设置文件的打开方式和mime类型
    ServletOutputStream outputStream = response.getOutputStream();
    response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    workbook.write(outputStream);
}

注解方式导入

有导出就应该有导入,我们就把刚才导出的数据库导入到表中

Excel导入时需要的参数类ImportParams常用设置说明

  1. 读取指定的sheet 比如要读取上传得第二个sheet 那么需要把startSheetIndex = 1 就可以了
  2. 读取几个sheet 比如读取前2个sheet,那么 sheetNum=2 就可以了
  3. 读取第二个到第五个sheet 设置 startSheetIndex = 1 然后sheetNum = 4
  4. 读取全部的sheet sheetNum 设置大点就可以了
  5. 保存Excel 设置 needVerfiy = true,默认保存的路径为upload/excelUpload/Test/yyyyMMddHHmss 保存名称上传时间五位随机数 如果自定义路径 修改下saveUrl 就可以了,同时saveUrl也是图片上传时候的保存的路径
  6. 判断一个Excel是不是合法的Excel importFields 设置下值,就是表示表头必须至少包含的字段,如果缺一个就是不合法的excel,不导入
  7. 图片的导入

有图片的导出就有图片的导入,导入的配置和导出是一样的,但是需要设置保存路径 1.设置保存路径saveUrl 默认为"upload/excelUpload" 可以手动修改 ImportParams 修改下就可以了

第一步:修改实体类,表明哪些需要导入

package com.itheima.pojo;
import cn.afterturn.easypoi.excel.annotation.Excel;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;

import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
import java.util.List;
/**
 * 员工
 */
@Data
@Table(name="tb_user")
public class User {
    @Id
    @KeySql(useGeneratedKeys = true)
    @Excel(name = "编号", orderNum = "0", width = 5)
    private Long id;         //主键
    @Excel(name = "员工名", orderNum = "1", width = 15,isImportField="true")
    private String userName; //员工名
    @Excel(name = "手机号", orderNum = "2", width = 15,isImportField="true")
    private String phone;    //手机号
    @Excel(name = "省份名", orderNum = "3", width = 15,isImportField="true")
    private String province; //省份名
    @Excel(name = "城市名", orderNum = "4", width = 15,isImportField="true")
    private String city;     //城市名
    @Excel(name = "工资", orderNum = "5", width = 10, type=10, isImportField="true") //type=10表示会导出数字
    private Integer salary;   // 工资
    @JsonFormat(pattern="yyyy-MM-dd")
    @Excel(name = "入职日期",  format = "yyyy-MM-dd",orderNum = "6", width = 15,isImportField="true")
    private Date hireDate; // 入职日期
    private String deptId;   //部门id
    @Excel(name = "出生日期",  format = "yyyy-MM-dd",orderNum = "7", width = 15,isImportField="true")
    private Date birthday; //出生日期
    @Excel(name = "照片", orderNum = "10",width = 15,type = 2,isImportField="true",savePath = "D:\\java_report\\workspace\\user_management\\src\\main\\resources\\static\\user_photos\\")
    private String photo;    //一寸照片
    @Excel(name = "现在居住地址", orderNum = "9", width = 30,isImportField="true")
    private String address;  //现在居住地址

    private List<Resource> resourceList; //办公用品

}

第二步:修改UserController中的导入方法

@PostMapping(value = "/uploadExcle", name = "上传用户数据")
public void uploadExcle(MultipartFile file) throws Exception{
    //        userService.uploadExcle(file);
    userService.uploadExcleWithEasyPOI(file);
}

第三步:在UserService中添加使用easyPOI导入的方法

public void uploadExcleWithEasyPOI(MultipartFile file) throws Exception {

    ImportParams importParams = new ImportParams();
    importParams.setTitleRows(1); //有多少行的标题
    importParams.setHeadRows(1);//有多少行的头
    List<User> userList = ExcelImportUtil.importExcel(file.getInputStream(),User.class,importParams);

    System.out.println(userList);
    for (User user : userList) {
        user.setId(null);
        userMapper.insertSelective(user);
    }
}

模板方式导出数据

模板是处理复杂Excel的简单方法,复杂的Excel样式,可以用Excel直接编辑,完美的避开了代码编写样式的雷区,同时指令的支持,也提了模板的有效性 采用的写法是{{}}代表表达式,然后根据表达式里面的数据取值

关于样式问题 easypoi不会改变excel原有的样式

需求:导出用户的详细信息,这个功能我们做过,今天我们使用easyPOI的方式再做一次

第一步:制作模板

这个模板和我们做的userInfo2.xlsx模板一样,只是这个变量使用了{{}}包起来了

1588175549536

第二步:放到项目中

1588175607058

第三步:改写UserController中导出用户信息的方法

@GetMapping(value = "/download",name = "导出用户详细信息")
public void downLoadUserInfoWithTempalte(Long id,HttpServletRequest request,HttpServletResponse response) throws Exception{
    //        userService.downLoadUserInfoWithTempalte(id,request,response);
    //        userService.downLoadUserInfoWithTempalte2(id,request,response);
    userService.downLoadUserInfoWithEastPOI(id,request,response);
}

第四步:完成UserService中的方法

public void downLoadUserInfoWithEastPOI(Long id, HttpServletRequest request, HttpServletResponse response) throws Exception  {
    //        获取模板的路径
    File rootPath = new File(ResourceUtils.getURL("classpath:").getPath()); //SpringBoot项目获取根目录的方式
    File templatePath = new File(rootPath.getAbsolutePath(),"/excel_template/userInfo3.xlsx");
    //        读取模板文件
    TemplateExportParams params = new TemplateExportParams(templatePath.getPath(),true);

    //        查询用户,转成map
    User user = userMapper.selectByPrimaryKey(id);
    Map<String, Object> map = EntityUtils.entityToMap(user);
    ImageEntity image = new ImageEntity();
    //        image.setHeight(640); //测试发现 这里设置了长度和宽度在合并后的单元格中没有作用
    //        image.setWidth(380);
    image.setRowspan(4);//向下合并三行
    image.setColspan(2);//向右合并两列
    image.setUrl(user.getPhoto());
    map.put("photo", image);
    Workbook workbook = ExcelExportUtil.exportExcel(params, map);

    //            导出的文件名称
    String filename="用户详细信息数据.xlsx";
    //            设置文件的打开方式和mime类型
    ServletOutputStream outputStream = response.getOutputStream();
    response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    workbook.write(outputStream);
}

导出CSV

csv的导出基本上和excel的导出一致,大体参数也是一致的

CsvExportParams 的参数描述如下

属性 类型 默认值 功能
encoding String UTF8 文件编码
spiltMark String , 分隔符
textMark String “ 字符串识别,可以去掉,需要前后一致
titleRows int 0 表格头,忽略
headRows int 1 标题
exclusions String[] 0 忽略的字段

需求:改写之前使用OpenCSV导出csv文件

第一步:修改UserController方法

@GetMapping(value = "/downLoadCSV",name = "导出用户数据到CSV文件中")
public void downLoadCSV(HttpServletResponse response) throws Exception{
    //        userService.downLoadCSV(response);
    userService.downLoadCSVWithEasyPOI(response);
}

第二步:完成UserService方法

public void downLoadCSVWithEasyPOI(HttpServletResponse response) throws Exception {
        ServletOutputStream outputStream = response.getOutputStream();
//            文件名
        String filename="百万数据.csv";
//            设置两个头 一个是文件的打开方式 一个是mime类型
        response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
        response.setContentType("application/csv");
//            创建一个用来写入到csv文件中的writer
        CsvExportParams params = new CsvExportParams();
//        设置忽略的列
        params.setExclusions(new String[]{"照片"}); //这里写表头 中文
        List<User> list = userMapper.selectAll();
        CsvExportUtil.exportCsv(params, User.class, list, outputStream);
    }

说明:从上述的代码中你会发现,如果需要导出几百万数据时不可能全部加载到一个List中的,所以easyPOI的方式导出csv是支持不了太大的数据量的,如果导出几百万条数据还是得选择OpenCSV方式导出。

Excel注解详细

属性 类型 类型 说明
name String null 列名
needMerge boolean fasle 纵向合并单元格
orderNum String "0" 列的排序,支持name_id
replace String[] {} 值得替换 导出是{a_id,b_id} 导入反过来
savePath String "upload" 导入文件保存路径
type int 1 导出类型 1 是文本 2 是图片,3 是函数,10 是数字 默认是文本
width double 10 列宽
height double 10 列高,后期打算统一使用@ExcelTarget的height,这个会被废弃,注意
isStatistics boolean fasle 自动统计数据,在追加一行统计,把所有数据都和输出这个处理会吞没异常,请注意这一点
isHyperlink boolean false 超链接,如果是需要实现接口返回对象
isImportField boolean true 校验字段,看看这个字段是不是导入的Excel中有,如果没有说明是错误的Excel,读取失败,支持name_id
exportFormat String "" 导出的时间格式,以这个是否为空来判断是否需要格式化日期
importFormat String "" 导入的时间格式,以这个是否为空来判断是否需要格式化日期
format String "" 时间格式,相当于同时设置了exportFormat 和 importFormat
databaseFormat String "yyyyMMddHHmmss" 导出时间设置,如果字段是Date类型则不需要设置 数据库如果是string类型,这个需要设置这个数据库格式,用以转换时间格式输出
numFormat String "" 数字格式化,参数是Pattern,使用的对象是DecimalFormat
imageType int 1 导出类型 1 从file读取 2 是从数据库中读取 默认是文件 同样导入也是一样的
suffix String "" 文字后缀,如% 90 变成90%
isWrap boolean true 是否换行 即支持\n
mergeRely int[] {} 合并单元格依赖关系,比如第二列合并是基于第一列 则{1}就可以了
mergeVertical boolean fasle 纵向合并内容相同的单元格

EasyPOI导出word

Word模板和Excel模板用法基本一致,支持的标签也是一致的,仅仅支持07版本的word也是只能生成后缀是docx的文档,poi对doc支持不好所以easyPOI中就没有支持doc,我们就拿docx做导出

这里得好好说说模板中标签的用法:

下面列举下EasyPoi支持的指令以及作用,最主要的就是各种fe的用法

三元运算 {{test ? obj:obj2}}
n: 表示 这个cell是数值类型 {{n:}}
le: 代表长度{{le:()}} 在if/else 运用{{le:() > 8 ? obj1 : obj2}}
fd: 格式化时间 {{fd:(obj;yyyy-MM-dd)}}
fn: 格式化数字 {{fn:(obj;###.00)}}
fe: 遍历数据,创建row
!fe: 遍历数据不创建row
$fe: 下移插入,把当前行,下面的行全部下移.size()行,然后插入
#fe: 横向遍历
v_fe: 横向遍历值
!if: 删除当前列 {{!if:(test)}}
单引号表示常量值 ‘’ 比如’1’ 那么输出的就是 1
&NULL& 空格
&INDEX& 表示循环中的序号,自动添加
]] 换行符 多行遍历导出
sum: 统计数据

第一步:根据上述的指令要求,我们制作模板如下

1588232898749

第二步:把制作好的模板放入到项目中

1588233148747

第三步:修改原来的导出合同的方法

UserController

@GetMapping(value = "/downloadContract",name = "导出用户合同")
public void downloadContract(Long id,HttpServletResponse response) throws Exception{
//        userService.downloadContract(id,response);
   userService.downloadContractWithEasyPOI(id,response);
}

UserService添加方法

public void downloadContractWithEasyPOI(Long id,HttpServletResponse response) throws Exception {

    File rootPath = new File(ResourceUtils.getURL("classpath:").getPath()); //SpringBoot项目获取根目录的方式
    File templatePath = new File(rootPath.getAbsolutePath(),"/word_template/contract_template2.docx");

    //        先获取导出word需要的数据
    User user = this.findById(id);
    //        把需要的数据放到map中,方便替换
    Map<String,Object> params = new HashMap<String,Object>();
    params.put("userName",user.getUserName());
    params.put("hireDate",simpleDateFormat.format(user.getHireDate()));
    params.put("address",user.getAddress());

    //        下面是表格中需要的数据
    List<Map> maplist = new ArrayList<>();
    Map<String,Object> map = null;
    for (Resource resource : user.getResourceList()) {
        map = new HashMap<String,Object>();
        map.put("name",resource.getName());
        map.put("price",resource.getPrice());
        map.put("needReturn",resource.getNeedReturn());
        ImageEntity image = new ImageEntity();
        image.setHeight(180);
        image.setWidth(240);
        image.setUrl(rootPath.getPath()+"\\static"+resource.getPhoto());
        map.put("photo",image);
        maplist.add(map);
    }
    //        把组建好的表格需要的数据放到大map中
    params.put("maplist",maplist);
    //        根据模板+数据 导出文档
    XWPFDocument xwpfDocument = WordExportUtil.exportWord07(templatePath.getPath(), params);
    String filename=user.getUserName()+"_合同.docx";
    //            设置文件的打开方式和mime类型
    ServletOutputStream outputStream = response.getOutputStream();
    response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
    response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
    xwpfDocument.write(outputStream);
}

导出结果如下:

1588248502117

PDF

Word转PDF

场景说明

把Word转成PDF目前最简单的方式就是调用office的方法,本质上就是打开Word后另存为成pdf,通过什么方式来调用呢?

使用 jacob,速度上还是可以的,Word中的原样式也不会丢失。

环境准备

最基本的环境:1、本机上安装了2007以上的office软件 2、jdk1.6以上的版本

第一步:从官网上下载资料(课程资料中已有)

https://sourceforge.net/projects/jacob-project/files/jacob-project/

1588818336322

第二步:把下载的资料中的jar放入到本地仓库

这个jar从中央仓库中没有找到,需要我们自己打到本地仓库中

进入到jar所在的目录执行以下命令:

mvn install:install-file -DgroupId=com.jacob -DartifactId=jacob -Dversion=1.19 -Dfile=jacob.jar -Dpackaging=jar

1588818965958

第三步:把dll文件放入到 jre\bin 目录下 64位的放x64文件,32位的放x86文件

如果不太确定是32位还是64位的就把两个dll都放进去

1588819793541

第四步:在项目的pom中添加依赖

<dependency>
    <groupId>com.jacob</groupId>
    <artifactId>jacob</artifactId>
    <version>1.9</version>
</dependency>

代码测试

需求:把我D盘下的Word转成PDF

1588819258566

Junit测试代码:

package com.itheima.demo;

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;

public class JacobDemo {
    public static void main(String[] args) {
        String source = "D:\\李四_合同.docx";
        String target = "D:\\李四_合同.pdf";
        System.out.println("Word转PDF开始启动...");
        ActiveXComponent app = null;
        try {
            //            调用window中的程序
            app = new ActiveXComponent("Word.Application");
            //            调用的时候不显示窗口
            app.setProperty("Visible", false);
            // 获得所有打开的文档
            Dispatch docs = app.getProperty("Documents").toDispatch();
            Dispatch doc = Dispatch.call(docs, "Open", source).toDispatch();
            System.out.println("转换文档到PDF:" + target);
            // 另存为,将文档保存为pdf,其中Word保存为pdf的格式宏的值是17
            Dispatch.call(doc, "SaveAs", target, 17);
            Dispatch.call(doc, "Close");
        } catch (Exception e) {
            System.out.println("Word转PDF出错:" + e.getMessage());
        } finally {
            // 关闭office
            if (app != null) {
                app.invoke("Quit", 0);
            }
        }
    }
}

结果:

1588820480874

打开后查看内容中的样式基本上没有错乱:

1588820460373

iText导出PDF(了解)

上面的例子是根据word转成的PDF,下面我们让java代码直接导出PDF

目前世面上比较流行的制作PDF报表的工具如下:

  1. iText PDF
  2. Jasper Report

我们先说一下 iText

iText是著名的开放项目,是用于生成PDF文档的一个java类库。通过iText不仅可以生成PDF或rtf的文档,而且可以将XML、Html文件转化为PDF文件。 代码编写太复杂!!!

举例:

1、用iText生成PDF文档需要5个步骤:

//建立com.lowagie.text.Document对象的实例。
Document document = new Document(); 
//建立一个书写器(Writer)与document对象关联,通过书写器(Writer)可以将文档写入到磁盘中。
PDFWriter.getInstance(document, new FileOutputStream("test.PDF")); 
//打开文档。
document.open(); 
//向文档中添加内容。
document.add(new Paragraph("Hello World")); 
//关闭文档。
document.close(); 

2、如果有表格

Table table = new Table(3);
table.setBorderWidth(1);
table.setBorderColor(new Color(0, 0, 255));
table.setPadding(5);
table.setSpacing(5);
Cell cell = new Cell("header");
cell.setHeader(true);
cell.setColspan(3);
table.addCell(cell);
table.endHeaders();
cell = new Cell("example cell with colspan 1 and rowspan 2");
cell.setRowspan(2);
cell.setBorderColor(new Color(255, 0, 0));
table.addCell(cell);
table.addCell("1.1");
table.addCell("2.1");
table.addCell("1.2");
table.addCell("2.2");
table.addCell("cell test1");
cell = new Cell("big cell");
cell.setRowspan(2);
cell.setColspan(2);
table.addCell(cell);
table.addCell("cell test2"); 

3、 中文字体的处理

默认的iText字体设置不支持中文字体,需要下载亚洲字体包iTextAsian.jar,否则不能往PDF文档中输出中文字体。通过下面的代码就可以在文档中使用中文了:

Font font = getPdfChineseFont();

font.setColor(new BaseColor(0xff0000));
font.setSize(16);
font.setStyle("bold");
font.setStyle("italic");
font.setStyle("underline");

public static Font getPdfChineseFont() throws Exception {
    BaseFont bfChinese = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H",
            BaseFont.NOT_EMBEDDED);
    Font fontChinese = new Font(bfChinese, 12, Font.NORMAL);
    return fontChinese;
} 

4、样式代码

pdfCell.setHorizontalAlignment(Element.ALIGN_CENTER);
pdfCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
pdfCell.setBackgroundColor(new BaseColor(0xdd7e6b));

pdfCell.setBorderWidthTop(0.1f);
pdfCell.setBorderWidthBottom(0.1f);
pdfCell.setBorderWidthLeft(0.1f);
pdfCell.setBorderWidthRight(0.1f);
pdfCell.setBorderColorBottom(new BaseColor(0x674ea7));
pdfCell.setBorderColorLeft(new BaseColor(0x674ea7));
pdfCell.setBorderColorRight(new BaseColor(0x674ea7));
pdfCell.setBorderColorTop(new BaseColor(0x674ea7));

5、画线的代码就更复杂了

所以,综上我们如果直接使用iText导出一个较为复杂的PDF的时候,代码量是非常大的,不推荐使用这种方式,那怎么办呢?我们使用一个封装了iText的一个类库:JasperReport

JasperReport导出PDF

JasperReport简介

JasperReport是一个强大、灵活的报表生成工具,能够展示丰富的页面内容,并将之转换成PDF,HTML,或者XML格式。该库完全由Java写成,可以用于在各种Java应用程序,包括J2EE,Web应用程序中生成动态内容。只需要将JasperReport引入工程中即可完成PDF报表的编译、显示、输出等工作。

在开源的JAVA报表工具中,JasperReport发展是比较好的,比一些商业的报表引擎做得还好,如支持了十字交叉报表、统计报表、图形报表,支持多种报表格式的输出,如PDF、RTF、XML、CSV、XHTML、TEXT、DOCX以及OpenOffice。

数据源支持更多,常用 JDBCSQL查询、XML文件、CSV文件、HQL(Hibernate查询),HBase,JAVA集合等。还允许你义自己的数据源,通过JASPER文件及数据源,JASPER就能生成最终用户想要的文档格式。

5.2 JasperReport的开发步骤

5.2.1 生命周期

通常我们提到PDF报表的时候,浮现在脑海中的是最终的PDF文档文件。在JasperReports中,这只是报表生命周期的最后阶段。通过JasperReports生成PDF报表一共要经过三个阶段,我们称之为 JasperReport的生命周期,

这三个阶段为:

设计(Design)阶段、

执行(Execution)阶段

输出(Export)阶段,如下图所示:

 

设计阶段(Design):所谓的报表设计就是创建一些模板,模板包含了报表的布局与设计,包括执行计算的复杂公式、可选的从数据源获取数据的查询语句、以及其它的一些信息。模板设计完成之后,我们将模板保存为JRXML文件(JR代表JasperReports),其实就是一个XML文件。

**执行阶段(Execution):**使用以JRXML文件编译为可执行的二进制文件(即.Jasper文件)结合数据进行执行,填充报表数据

输出阶段(Export):数据填充结束,可以指定输出为多种形式的报表

5.2.2 执行流程

1、JRXML:报表填充模板,本质是一个XML.

JasperReport已经封装了一个dtd,只要按照规定的格式写这个xml文件,那么jasperReport就可以将其解析最终生成报表,但是jasperReport所解析的不是我们常见的.xml文件,而是.jrxml文件,其实跟xml是一样的,只是后缀不一样。

2、Jasper:由JRXML模板编译生成的二进制文件,用于代码填充数据。

解析完成后JasperReport就开始编译.jrxml文件,将其编译成.jasper文件,因为JasperReport只可以对.jasper文件进行填充数据和转换,这步操作就跟我们java中将java文件编译成class文件是一样的

3、Jrprint:当用数据填充完Jasper后生成的文件,用于输出报表。

这一步才是JasperReport的核心所在,它会根据你在xml里面写好的查询语句来查询指定是数据库,也可以控制在后台编写查询语句,参数,数据库。在报表填充完后,会再生成一个.jrprint格式的文件(读取jasper文件进行填充,然后生成一个jrprint文件)

4、Exporter:决定要输出的报表为何种格式,报表输出的管理类。

5、JasperReport可以输出多种格式的报表文件,常见的有Html,PDF,xls等

综上我们得知,对于使用JasperReport进行开发,我们重点关注只有如下四点:

制作报表模板
模板编译
构造数据
填充模板数据

模板工具Jaspersoft Studio

概述

​ Jaspersoft Studio是JasperReports库和JasperReports服务器的基于Eclipse的报告设计器; 它可以作为Eclipse插件或作为独立的应用程序使用。Jaspersoft Studio允许您创建包含图表,图像,子报表,交叉表等的复杂布局。您可以通过JDBC,TableModels,JavaBeans,XML,Hibernate,大数据(如Hive),CSV,XML / A以及自定义来源等各种来源访问数据,然后将报告发布为PDF,RTF, XML,XLS,CSV,HTML,XHTML,文本,DOCX或OpenOffice。

​ Jaspersoft Studio 是一个可视化的报表设计工具,使用该软件可以方便地对报表进行可视化的设计,设计结果为格式.jrxml 的XML 文件,并且可以把.jrxml 文件编译成.jasper 格式文件方便 JasperReport 报表引擎解析、显示。

安装配置

到 JasperReport官网下载 https://community.jaspersoft.com/community-download

1589506112709

当然我们可以直接使用下载好的这个软件

双击安装今天资料中的安装包1588823265607

下一步下一步的安装即可

​

5.3.3 面板介绍

1588839355496

Report editing area (主编辑区域)中,您直观地通过拖动,定位,对齐和通过 Designer palette(设计器调色板)对报表元素调整大小。JasperSoft Studio 有一个多标签编辑器,Design,Source和Preview:

Design tab:当你打开一个报告文件,它允许您以图形方式创建报表选中

Source tab:包含用于报表的 JRXML 源代码。

Preview tab:允许在选择数据源和输出格式后,运行报表预览。

Repository Explorer view:包含 JasperServer 生成的连接和可用的数据适配器列表

Project Explorer view:包含JasperReports 的工程项目清单

Outline view:在大纲视图中显示了一个树的形式的方式报告的完整结构。

Properties view:通常是任何基于 Eclipse 的产品/插件的基础之一。它通常被填充与实际所选元素的属性的信息。这就是这样,当你从主设计区域(即:一个文本字段)选择一个报表元素或从大纲,视图显示了它的信息。其中一些属性可以是只读的,但大部分都是可编辑的,对其进行修改,通常会通知更改绘制的元素(如:元素的宽度或高度)。

Problems view:显示的问题和错误,例如可以阻断报告的正确的编译。

Report state summary 提供了有关在报表编译/填充/执行统计用户有用的信息。错误会显示在这里 

基本使用

模板制作

(1)打开Jaspersoft Studio ,新建一个project, 步骤: File -> New -> Project-> JasperReportsProject

 

(2) 新建一个Jasper Report模板,在 Stidio的左下方Project Explorer 找到刚才新建的Project (我这里新建的是DemoReport),步骤:项目右键 -> New -> Jasper Report

 

(3)选择 Blank A4 (A4纸大小的模板),然后 Next 命名为test01.jrxml.

如图所示,报表模板被垂直的分层,每一个部分都是一个Band,每一个Band的特点不同:

 

Title(标题):只在整个报表的第一页的最上端显示。只在第一页显示,其他页面均不显示。

Page Header(页头):在整个报表中每一页都会显示。在第一页中,出现的位置在 Title Band的下面。在除了第一页的其他页面中Page Header 的内容均在页面的最上端显示。

Page Footer(页脚):在整个报表中每一页都会显示。显示在页面的最下端。一般用来显示页码。

Detail 1(详细):报表内容,每一页都会显示。

Column Header(列头):Detail中打印的是一张表的话,这Column Header就是表中列的列头。

Column Footer(列脚):Detail中打印的是一张表的话,这Column Footer就是表中列的列脚。

Summary(统计):表格的合计段,出现在整个报表的最后一页中,在Detail 1 Band后面。主要是用来做报表的合计显示。

编译模板

右键单机模板文件 -> compile Report 对模板进行编译,生成.jasper文件

入门案例

需求:

1589607017208

环境准备

1、导入依赖

<dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports</artifactId>
    <version>6.5.0</version>
</dependency>
<dependency>
    <groupId>org.olap4j</groupId>
    <artifactId>olap4j</artifactId>
    <version>1.2.0</version>
</dependency>
<dependency>
    <groupId>com.lowagie</groupId>
    <artifactId>itext</artifactId>
    <version>2.1.7</version>
</dependency>

2、准备中文字体资源文件

jasperReports本身对中文的支持不够好,所以如果涉及到中文,需要自己准备中文的资源,现在已“华文宋体为例”

把图中的文件夹和properties文件直接拷贝到项目的resources文件夹下

1588849878297

如下效果:

1588849969516

导出一个最基本的PDF文件

第一步:使用Jaspersoft Studio制作一个简单的模板

1、 创建新模板,删除不需要的Band

1588855668749

2、画边框和线

1588856146579

3、添加几个静态的文本

1588856091369

4、添加变量

1588855981754

在右下角修改变量名称

1588856219979

5、使用变量

1588856305142

效果:

1588856331868

6、有中文的设置字体为华文宋体

1588856369417

7、保存后编辑

1588856607184

第二步:把test01.jasper文件放到磁盘的某个位置,比如我直接放到D盘下

1588856747181

第三步:测试代码

package com.itheima.demo;

import net.sf.jasperreports.engine.*;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

public class PDFDemo1 {
    /**
     *  基于parameters以Map的形式填充数据
     */
    public static void main(String[] args) throws Exception {
            String filePath = "D:\\test01.jasper";
//        文件的输入流
            InputStream inputStream = new FileInputStream(filePath);
            //2.创建JasperPrint,向jasper文件中填充数据
            FileOutputStream os = new FileOutputStream("d:\\demo1.pdf");
            try {
                Map parameters = new HashMap<>();
                //设置参数 参数的key = 模板中使用的parameters参数的name
                parameters.put("name","张三");
                parameters.put("phone","13800000000");
                //3.将JasperPrint已PDF的形式输出
                JasperPrint jasperPrint = JasperFillManager.fillReport(inputStream,parameters,new JREmptyDataSource());
                //导出
                JasperExportManager.exportReportToPdfStream(jasperPrint,os);
            } catch (JRException e) {
                e.printStackTrace();
            }finally {
                os.flush();
            }
    }
}

效果:

1588856789542

导出用户列表

1588863280914

数据直接从数据库中获取

第一步:制作模板

1、创建新模板,删除不需要的Band

1588857705649

2、配置数据连接

使用JDBC数据源填充数据:使用Jaspersoft Studio 先要配置一个数据库连接

填写数据源的类型,选择“DatabaseJDBC Connection”

配置数据库信息

1588858034916

这一步,需要: (1)给创建的这个数据连接起个名字; (2)根据数据库选择驱动类型; Jaspersoft Studio 已经内置了很多常用数据库的驱动,使用的时候直接选就可以了。当然,如果这还满足不了你的话,你还可以添加你指定的 JDBC 驱动 jar 包。

3、读取表中属性

左下角右击模板名称选择 Dataset and Query

1588858271693

点击后:

1588858238782

点击“OK”按钮

左下角的Fields中就有了我们想要的属性字段了

1588858354193

4、模板如下

1588858724520

修改标题,并且要使用华文宋体

1588858830679

第二步:把编译后的jasper文件放入到项目中

1588861008331

第三步:完成代码

1、Controller中添加方法

@GetMapping(value = "/downLoadPDF",name = "导出PDF")
public void  downLoadPDF(HttpServletResponse response) throws Exception{
    userService.downLoadPDFByDB(response);
}

2、Service添加方法

@Autowired
private HikariDataSource hikariDataSource;

public void downLoadPDFByDB(HttpServletResponse response) throws Exception {
    //        获取模板文件
    String ctxPath = ResourceUtils.getURL("classpath:").getPath();

    String filePath =  ctxPath+"/pdf_template/userList_db.jasper";
    ServletOutputStream outputStream = response.getOutputStream();
    //            文件名
    String filename="用户列表数据.pdf";
    //            设置两个头 一个是文件的打开方式 一个是mime类型
    response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
    response.setContentType("application/pdf");
    //        文件的输入流
    InputStream inputStream = new FileInputStream(filePath);
    //2.创建JasperPrint,向jasper文件中填充数据
    Map parameters = new HashMap<>();
    //3.将JasperPrint已PDF的形式输出
    JasperPrint jasperPrint = JasperFillManager.fillReport(inputStream,parameters,hikariDataSource.getConnection());
    //导出
    JasperExportManager.exportReportToPdfStream(jasperPrint,outputStream);
}

效果如下:

1588859954162

数据从后台获取

从上面效果来看,导出的日期格式有问题,其实数据库表中的数据很多情况下都需要做一些业务处理才能被使用的,所以再学习一下数据经过处理后再导出。

第一步:创建模板

1、创建新模板,删除不需要的Band

1588862296356

2、创建4个field

这4个field的名称要和User实体类中的属性名称要保持一致,但是注意这里有一个hireDateStr

1588862580300

3、制作如下模板

1588862737055

第二步:把编译的后的模板文件放入到项目中

1588861368876

第三步:修改UserController中的代码

@GetMapping(value = "/downLoadPDF",name = "导出PDF")
public void  downLoadPDF(HttpServletResponse response) throws Exception{
    //userService.downLoadPDFByDB(response);
    userService.downLoadPDF(response);
}

第四步:完成UserService代码

1、在User中添加一个hireDateStr字段

@JsonIgnore //转json时不考虑这个字段
@Transient //表示非数据库字段
private String hireDateStr;

2、UserService添加方法

public void downLoadPDF(HttpServletResponse response) throws Exception {
//        获取模板文件
        String ctxPath = ResourceUtils.getURL("classpath:").getPath();
        String filePath =  ctxPath+"/pdf_template/userList.jasper";
        ServletOutputStream outputStream = response.getOutputStream();
        //            文件名
        String filename="用户列表数据.pdf";
//            设置两个头 一个是文件的打开方式 一个是mime类型
        response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
        response.setContentType("application/pdf");
//        文件的输入流
        InputStream inputStream = new FileInputStream(filePath);
        //2.创建JasperPrint,向jasper文件中填充数据
        Map parameters = new HashMap<>();

//        查询所有数据
        List<User> userList = userMapper.selectAll();
//        给hireDateStr赋值
        userList = userList.stream().map(user -> {
            user.setHireDateStr(simpleDateFormat.format(user.getHireDate()));
            return user;
        }).collect(Collectors.toList());
        JRBeanCollectionDataSource dataSource = new JRBeanCollectionDataSource(userList);
        //3.将JasperPrint已PDF的形式输出
        JasperPrint jasperPrint = JasperFillManager.fillReport(inputStream,parameters,dataSource);
        //导出
        JasperExportManager.exportReportToPdfStream(jasperPrint,outputStream);
    }

1588863195579

导出用户详细信息

第一步:创建模板

1、创建新模板,删除不需要的Band

1588863385052

2、创建parameter

1588864510817

3、设置模板基本信息

1588864040713

4、添加图片数据

a)、在JasperReport 拖拽image 到detail中

1588865758007

弹出 create new image element选择Image creation mode 中的最后一个No image

1588865411074

b)、选中刚才添加的image . 在右侧Properties -> Image中,Expression 设置刚才定义的photo

1588866688915

最终的模板如下:

1588866710198

第二步:把编译后的模板放入到项目中

![1588865919073]assets\1588865919073.png)

第三步:代码实现

1、修改UserController中下载个人信息的方法

@GetMapping(value = "/download",name = "导出用户详细信息")
public void downLoadUserInfoWithTempalte(Long id,HttpServletRequest request,HttpServletResponse response) throws Exception{
    userService.downLoadUserInfoPDF(id,request,response);
}

2、在UserService中添加方法

public void downLoadUserInfoPDF(Long id, HttpServletRequest request, HttpServletResponse response) throws Exception{
    //        获取模板路径
    String ctxPath = ResourceUtils.getURL("classpath:").getPath();

    String filePath =  ctxPath+"/pdf_template/userInfo.jasper";
    ServletOutputStream outputStream = response.getOutputStream();
    //            文件名
    String filename="用户详细数据.pdf";
    //            设置两个头 一个是文件的打开方式 一个是mime类型
    response.setHeader( "Content-Disposition", "attachment;filename="  + new String(filename.getBytes(),"ISO8859-1"));
    response.setContentType("application/pdf");
    //        文件的输入流
    InputStream inputStream = new FileInputStream(filePath);
    //2.创建JasperPrint,向jasper文件中填充数据
    User user = userMapper.selectByPrimaryKey(id);
    Map<String, Object> parameters = EntityUtils.entityToMap(user);
    parameters.put("salary",user.getSalary().toString()); //因为模板中的salary字段是字符串
    parameters.put("photo",ctxPath+user.getPhoto());
    //3.将JasperPrint已PDF的形式输出
    JasperPrint jasperPrint = JasperFillManager.fillReport(inputStream,parameters,new JREmptyDataSource());
    //导出
    JasperExportManager.exportReportToPdfStream(jasperPrint,outputStream);
}

导出的效果:

1588866885828

avatar

yuanyourdomain

写代码,做研究,记录生活。

RECOMMENDED

MyBatis 动态 SQL

2026-07-08

Nginx 基础入门

2026-07-08

Maven 多模块与私服

2026-07-08