Java报表技术POI实战

前言

报表[forms for reporting to the higher organizations],就是向上级报告情况的表格。简单的说:报表就是用表格、图表等格式来动态显示数据,可以用公式表示为:“报表 = 多样的格式 + 动态的数据”。

1、开发环境搭建

功能说明:整个案例我们操作用户表,做一个企业员工(用户)数据的导入导出。

我们使用SpringBoot+通用mapper+vue方式搭建开发环境

第一步:准备数据库,把资料文件夹下中的sql脚本直接执行

《report_manager_db.sql》

第二步:打开idea或Eclipse,导入资料文件夹中准备好的《user_management》项目

以idea为例,导入后整体结构如下

第三步:启动引导类,浏览器访问 http://localhost:8080/list.html

2、Excel说明

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

2.1 Excel的两种版本

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

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

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

​ 操作效率更高

2.2 常见的Excel操作工具

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

2.2.1 JXL

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

2.2.2 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 : 2003版本,XSSFCell : 2007级以上)

3、使用JXL导出excel

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

3.1 使用jxl导出基本知识点

通过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();// 关闭文件

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

第一步: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();}}

导出内容如下:

4、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>

4.1、POI操作Excel高低版本区别

在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"));}
}

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

4.2、实现用户数据的导入

4.2.1、需求

把资料中的《用户导入测试数据.xlsx》文档中的数据导入到系统中,

内容如下:

数据的导入就是读取excel中的内容,转成对象插入到数据库中

4.2.2 、思路

一般来说,即将导入的文件,每个列代表什么意思基本上都是固定的,比如第1列就是用户姓名,最后一列就是用户的现住址,并且在做excel时对每个列的类型都是有要求的,这样就可以给我们开发带来很大的简便。

最终的目标就是读取每一行数据,把数据转成用户的对象,保存到表中

实现的步骤:1、根据上传的文件创建Workbook

​ 2、获取到第一个sheet工作表

​ 3、从第二行开始读取数据

​ 4、读取每一个单元格,把内容放入到用户对象的相关的属性中

4.2.3、代码实现

第一步:在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()); //根据上传的输入流创建workbookSheet 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);}}

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

4.3、实现用户数据的导出

4.3.1、需求

我们先来一个简单的excel的导出,不要求有什么样式。就是和jxl导出的内容一样就可以

4.3.2、基本思路

1、创建一个全新的工作薄

2、在新的工作薄中创建一个新的工作表

3、在工作表创建第一行作为标题行,标题固定

4、从第二行循环遍历创建,有多少条用户数据就应该创建多少行

5、把每一个user对象的属性放入到相应的单元格中

4.3.3、代码实现

第一步:在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);}

4.4、导出时样式的设置

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

通过上图可以看出有些样式需要我们来设置,来看一下都有哪些知识点:
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);//右侧线

2.合并单元格

//合并单元格 起始行, 结束行, 起始列, 结束列
sheet.addMergedRegion(new CellRangeAddress(0,0,0,4));

3.设置行高

/**
设置行高
*/
sheet.getRow(1).setHeight((short)500);

4.设置表格的对齐方式和字体

//*设置对齐方式和字体***/
//内容部分的样式
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);//单元格设置标题样式

其实真正要用代码实现我们最终想要的效果的话,难道不大,但是代码写起来非常麻烦,所以明天给大家介绍一种非常简单的方式,并且还带有样式。

1、基于模板导出列表数据

1.1、需求

按照以下样式导出excel

1.2、思路

首先准备一个excel模板,这个模板把复杂的样式和固定的内容先准备好并且放入到项目中,然后读取到模板后向里面放入数据。

1.3、实现

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

第一个sheet:

第二个sheet:

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

第三步: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带样式
}

2、导出用户详细数据

2.1、 需求

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

2.2、思路

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

2.3、实现

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

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

第三步: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);}

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

接下来处理一下头像照片和司龄

3、导出数据带图片、公式

3.1、导出图片

个人信息的导出中包含了头像照片,需要用到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坐标,一般可设0col1 - 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.//图片右下角所在行

3.2、导出公式

应用场景说明,在导出用户详细数据时有一个司龄的显示,这里的司龄就是截止到现在入职到本公司的时间,为了学习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中操作,如下:

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

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

ps:其实在正常开发时应该在模板中直接设置好公式,这样打开直接导出的excel文档时公式会直接运行出我们想要的结果

4、自定义导出详细数据的引擎

4.1、说明

看我们刚才导出时写的代码,必须要提前知道要导出数据在哪一行哪一个单元格,但是如果模板一旦发生调整,那么我们的java代码必须要修改,我们可以自定义个导出的引擎,有了这个引擎即使模板修改了我们的java代码也不用修改

4.2、思路

在制作模板时,在需要插入数据的位置我们坐上标记,在导出时,对象的属性要和标记做对应,如果对应匹配一样,就把值赋值到相应的位置。

4.3、实现

第一步:制作模板,命名 userInfo2.xlsx

第二步:添加到项目中

第三步:实现导出的引擎代码

package com.itheima.utils;import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.tomcat.util.http.fileupload.ByteArrayOutputStream;
import org.springframework.util.ResourceUtils;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;public class ExcelExportEngine {private static SimpleDateFormat  sdf = new SimpleDateFormat("yyyy-MM-dd");public  static Workbook writeToExcel(Object object, Workbook workbook,String photoPath) throws Exception{//先把bean转成mapMap<String, Object> map = EntityUtils.entityToMap(object);//循环遍历每一对数据,把日期型的转成字符串,方便导出for (String key : map.keySet()) {Object vlaue = map.get(key);if(vlaue instanceof Date){System.out.println(sdf.format(vlaue));map.put(key,sdf.format(vlaue));}}//获取第一个sheet,整体的思路是循环100个行的100个单元格Sheet sheet = workbook.getSheetAt(0);Cell cell =null;Row row = null;for (int i = 0; i < 100; i++) {row = sheet.getRow(i); //获取到空行为止if(row==null){break;}else{for (int j = 0; j < 100; j++) {cell = row.getCell(j);//获取到空单元格不处理if(cell!=null){writeCell(cell,map); //开始向单元格中写内容}}}}if(StringUtils.isNotBlank(photoPath)){File rootPath = new File(ResourceUtils.getURL("classpath:").getPath()); //SpringBoot项目获取根目录的方式ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
//        BufferedImage是一个带缓冲区图像类,主要作用是将一幅图片加载到内存中BufferedImage bufferImg = ImageIO.read(new File(rootPath + photoPath));ImageIO.write(bufferImg, "jpg", byteArrayOut);Drawing patriarch = sheet.createDrawingPatriarch();Sheet sheet2 = workbook.getSheetAt(1);row = sheet2.getRow(0);int col1 = ((Double) row.getCell(0).getNumericCellValue()).intValue();int row1 = ((Double) row.getCell(1).getNumericCellValue()).intValue();int col2 = ((Double) row.getCell(2).getNumericCellValue()).intValue();int row2 = ((Double) row.getCell(3).getNumericCellValue()).intValue();
//            锚点,固定点ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0,  col1, row1, col2, row2);patriarch.createPicture(anchor, workbook.addPicture(byteArrayOut.toByteArray(), Workbook.PICTURE_TYPE_JPEG));workbook.removeSheetAt(1);}return workbook;}private static void writeCell(Cell cell, Map<String, Object> map) {CellType cellType = cell.getCellType();switch (cellType){case FORMULA:{  //如果是公式就直接放行了break;}default:{String cellValue = cell.getStringCellValue();//就是判断一下获取到单元格中的值是否和map中的key保持一致if(StringUtils.isNotBlank(cellValue)){for (String key : map.keySet()) {if(key.equals(cellValue)){cell.setCellValue(map.get(key).toString());}}}}}}
}

第四步:修改UserService的方法

public void downLoadUserInfoWithTempalte2(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/userInfo2.xlsx");//        读取模板文件产生workbook对象,这个workbook是一个有内容的工作薄Workbook workbook  = new XSSFWorkbook(templatePath);// 查询用户信息User user = userMapper.selectByPrimaryKey(id);// 这里使用引擎直接导出workbook = ExcelExportEngine.writeToExcel(user,workbook,user.getPhoto());//            导出的文件名称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);
}

5、百万数据导出

5.1、概述

​ 我们都知道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的风险。

5.2、解决方案分析

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

java代码解析xml

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

SAX:逐行加载,逐行解析

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

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

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

5.3、原理分析

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

5.4、百万数据的导出

5.4.1、模拟数据

第一步、创建表

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()            -- 创建存储过程BEGINDECLARE n int DEFAULT 1;                    -- 定义变量n=1SET 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秒左右

5.4.2、思路分析

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

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

1、不能使用模板

2、不能使用太多的样式

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

5.4.3、代码实现

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

5.4.4、测试结果

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

eateRow(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);
}

### 5.4.4、测试结果导出的这个文档大概需要3-5分钟的时间,有105 MB,内容如下[外链图片转存中...(img-ihwJO6HN-1657588691375)]

Java报表技术POI实战相关推荐

  1. java报表技术-图形报表

    目录 JFreechart 简介 入门案例 Highcharts 简介 下载安装 入门案例 准备页面 统计各部门人数(柱状图) 月份入职人数统计(折线图) 员工地方来源统计(可下钻的饼状图) Echa ...

  2. Java 报表Apache POI API与实现数据行分组折叠

    官方地址:http://poi.apache.org/apidocs/ Apache POI - Javadocs Apache POI Javadocs 可以在此处在线访问 Apache POI的最 ...

  3. 全新java架构技术框架Quarkus实战神仙文档

    前言 Quarkus是一款有别于传统Java架构的新技术框架.它建立在我们熟知的技术栈上,使用了诸多成熟的技术,如JPA.JAX-RS.EclipseVert.x.Eclipse MicroProfi ...

  4. Java技术开发实战分享

    Java 是一种强大的编程语言,广泛应用于企业级软件开发.它具有跨平台.面向对象.可移植性强等特点,成为了企业应用程序开发的首选语言.在本篇博客中,我将分享我在 Java 开发方面的经验和见解,并提供 ...

  5. 报表技术之Excel格式报表生成(POI)

    报表技术之Excel格式报表生成(POI) 1.找到你的页面的导出Excel的按钮 2.给导出按钮添加事件 3. 编写 ReportAction 添加 exportXls 方法 POI 生成 Exce ...

  6. java网络编程技术学习笔记(b站【狂神说Java】网络编程实战讲解)

    b站视频链接:[狂神说Java]网络编程实战讲解 文章目录 网络编程 1.1.概述 1.2.网络通信的要素 1.3.IP 1.4.端口 1.5.通信协议 1.6.TCP 文件发送 1.7.UDP 1. ...

  7. JAVA应用开发MQ实战最佳实践——Series2:消息队列RocketMQ性能测试案例

    简介:JAVA应用开发MQ实战最佳实践--Series2:消息队列RocketMQ性能测试案例 往期内容 JAVA应用开发MQ实战最佳实践--Series1:RocketMQ综述及代码设计 1. 消息 ...

  8. 【直播回顾】云栖社区特邀专家徐雷Java Spring Boot开发实战系列课程(第19讲):Java Spring Cloud微服务架构模式与开发实战...

    主讲人:徐雷(云栖社区特邀Java专家) 徐雷,花名:徐雷frank:资深架构师,MongoDB中文社区联席主席,吉林大学计算机学士,上海交通大学硕士.从事了 10年+开发工作,专注于分布式架构,Ja ...

  9. 主流Java报表工具的比较

    从部署.展示.设计等方面,全面比较Java报表工具,如 润乾 , 杰表 ,Style Report,Jasper等,以及声称支持Java的Crystal Report,数巨等. Java报表工具,首先 ...

最新文章

  1. 淘宝、美团、滴滴分别如何搭建大数据平台?
  2. zookeeper 运维管理
  3. Ubuntu16.04下Hadoop 2.7.3的安装与配置
  4. xml中的常用操作示例
  5. 8个你应该了解的正则表达式
  6. Xcode 不用签名编译程序
  7. Classes in JScript – Part III 类的继承与封装
  8. java sound api_Java Sound API
  9. java中 静态方法与成员方法何时使用
  10. 深度学习语义分割理论与实战指南.pdf
  11. 基于狄利克雷-多项式分布做文档聚类代码(dirichlet multinomial mixture model)
  12. Android学习随笔 -- draw9patch的使用
  13. Linux下的NFS网络文件系统
  14. python列表分组的技巧
  15. X 射线成像 新型数字技术 —— CMOS 探测器
  16. [转载]生活在 Emacs 中
  17. [20180423]表空间闪回与snapshot standby
  18. 浅谈Android事件分发
  19. pdf转换成jpg python_Python 将pdf转成图片
  20. dragonfly 踩坑指南

热门文章

  1. MMALL ADMIN 后台管理总结
  2. 一个屌丝程序猿的人生(八十四)
  3. R语言Kruskal-Wallis检验检验多组组独立样本数据是否来自同分布(均值是否相同):为研究4种不同药物对儿童咳嗽的治疗效果,相似的病人随机分为4组、使用不同药物进行治疗、判断治疗效果是否相同?
  4. 时间序列之自相关函数
  5. welearn自动答题脚本
  6. Matlab/Simulink中的数据输出到Matlab工作空间中
  7. qemu运行虚拟机无反应,只输出一行提示信息:VNC server running on 127.0.0.1:5900
  8. Java SE是什么?Java各个版本区别
  9. 推荐 :降维是数据科学家的必由之路
  10. 给定一个只由 0、1、、|和^五种字符组成的字符串express,再给定一个布尔值 desired。返回express能有多少种组合方式,可以达到desired的结果。