什么是POI

POI简介(Apache POI),Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。

Apache POI官网

  • HSSF - 提供读写Microsoft Excel格式档案的功能。(.xls)
  • XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。(.xlsx)
  • HWPF - 提供读写Microsoft Word格式档案的功能。
  • HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
  • HDGF - 提供读写Microsoft Visio格式档案的功能。

环境准备

  • 创建普通Maven项目
  • 导入相关依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

<dependencies>
<!--xls(03)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>

<!--xlsx(07)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>

<!--日期格式化工具-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>

<!--test-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>

写Excel功能

Excel的文件格式分为Excel2003【后缀名为xls】和Excel2007【后缀名为xlsx】两种,这两种的代码基本上一致的.

  • Excel2003写功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Test
public void testWrite03() throws IOException {
// 创建新的Excel 工作簿
Workbook workbook = new HSSFWorkbook();
// 在Excel工作簿中建一工作表,其名为缺省值 Sheet0
//Sheet sheet = workbook.createSheet();
// 如要新建一名为"会员登录统计"的工作表,其语句为:
Sheet sheet = workbook.createSheet("疫苗接种统计");
// 创建行(row 1)
Row row1 = sheet.createRow(0);

// 创建单元格(col 1-1)
Cell cell11 = row1.createCell(0);
cell11.setCellValue("今日人数");

// 创建单元格(col 1-2)
Cell cell12 = row1.createCell(1);
cell12.setCellValue(666);

// 创建行(row 2)
Row row2 = sheet.createRow(1);

// 创建单元格(col 2-1)
Cell cell21 = row2.createCell(0);
cell21.setCellValue("统计时间");

//创建单元格(第三列)
Cell cell22 = row2.createCell(1);
String dateTime = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
cell22.setCellValue(dateTime);

// 新建一输出文件流(注意:要先创建文件夹)
FileOutputStream out = new FileOutputStream("d:/test-write03.xls");
// 把相应的Excel 工作簿存盘
workbook.write(out);
// 操作结束,关闭文件
out.close();

System.out.println("文件生成成功");
}
  • Excel2007写功能
1
2
3
4
5
6
7
8
9
@Test
public void testWrite07() throws IOException {
// 创建新的Excel 工作簿
Workbook workbook = new XSSFWorkbook();
...
// 新建一输出文件流(注意:要先创建文件夹)
FileOutputStream out = new FileOutputStream("d:/test-write07.xlsx");
...
}

大文件写入

  • 使用HSSF【Excel2003】

缺点:最多只能处理65536行,否则会抛出异常

java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)

优点:过程中写入缓存,不操作磁盘,最后一次性写入磁盘,速度快

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Test
public void testWrite03BigData() throws IOException {
//记录开始时间
long begin = System.currentTimeMillis();

//创建一个SXSSFWorkbook
Workbook workbook = new HSSFWorkbook();

//创建一个sheet
Sheet sheet = workbook.createSheet();

//xls文件最大支持65536行
for (int rowNum = 0; rowNum < 65537; rowNum++) {
//创建一个行
Row row = sheet.createRow(rowNum);
for (int cellNum = 0; cellNum < 10; cellNum++) {//创建单元格
Cell cell = row.createCell(cellNum);
cell.setCellValue(cellNum);
}
}

System.out.println("done");
FileOutputStream out = new FileOutputStream("d:/test-write03-bigdata.xls");
workbook.write(out);
// 操作结束,关闭文件
out.close();

//记录结束时间
long end = System.currentTimeMillis();
System.out.println((double)(end - begin)/1000);
}
  • 使用XSSF【Excel2007】

缺点:写数据时速度非常慢,非常耗内存,也会发生内存溢出,如100万条

优点:可以写较大的数据量,如20万条

1
2
3
4
5
6
7
8
9
@Test
public void testWrite07BigData() throws IOException {
...
//创建一个SXSSFWorkbook
Workbook workbook = new XSSFWorkbook();
...
FileOutputStream out = new FileOutputStream("d:/test-write07-bigdata.xlsx");
...
}
  • 使用SXSSF

优点:可以写非常大的数据量,如100万条甚至更多条,写数据速度快,占用更少的内存

注意:

过程中会产生临时文件,需要清理临时文件

默认由100条记录被保存在内存中,如果查过这数量,则最前面的数据被写入临时文件

如果想自定义内存中数据的数量,可以使用new SXSSFWorkbook(数量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testWrite07BigDataFast() throws IOException {
//记录开始时间
long begin = System.currentTimeMillis();
//创建一个SXSSFWorkbook
Workbook workbook = new SXSSFWorkbook();
...
FileOutputStream out = new FileOutputStream("d:/test-write07-bigdata-fast.xlsx");
workbook.write(out);
// 操作结束,关闭文件
out.close();
//清除临时文件
((SXSSFWorkbook)workbook).dispose();
//记录结束时间
long end = System.currentTimeMillis();
System.out.println((double)(end - begin)/1000);
}
1
2
3
SXSSFWorkbook-来至官方的解释:实现“BigGridDemo”策略的流式XSSFWorkbook版本。这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。

请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能,例如合并区域,注释......仍然只存储在内存中,因此如果广泛使用,可能需要大量内存。

读Excel功能

  • Excel2003读功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void testRead03() throws Exception{
InputStream is = new FileInputStream("d:/test-write03.xls");

Workbook workbook = new HSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);

// 读取第一行第一列
Row row = sheet.getRow(0);
Cell cell = row.getCell(0);

// 输出单元内容
System.out.println(cell.getStringCellValue());

// 操作结束,关闭文件
is.close();
}
  • Excel2007读功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void testRead07() throws Exception{

InputStream is = new FileInputStream("d:/test-write07.xlsx");

Workbook workbook = new XSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);

// 读取第一行第一列
Row row = sheet.getRow(0);
Cell cell = row.getCell(0);

// 输出单元内容
System.out.println(cell.getStringCellValue());

// 操作结束,关闭文件
is.close();
}
  • 读取不同类型的数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@Test
public void testCellType() throws Exception {

InputStream is = new FileInputStream("d:/商品信息表.xlsx");

Workbook workbook = new XSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);
// 读取标题所有内容
Row rowTitle = sheet.getRow(0);
if (rowTitle != null) {// 行不为空
// 读取cell
int cellCount = rowTitle.getPhysicalNumberOfCells();
for (int cellNum = 0; cellNum < cellCount; cellNum++) {
Cell cell = rowTitle.getCell(cellNum);
if (cell != null) {
String cellValue = cell.getStringCellValue();
System.out.print(cellValue + "|");
}
}
System.out.println();
}

// 读取商品列表数据
int rowCount = sheet.getPhysicalNumberOfRows();
for (int rowNum = 1; rowNum < rowCount; rowNum++) {

Row rowData = sheet.getRow(rowNum);
if (rowData != null) {// 行不为空
// 读取cell
int cellCount = rowTitle.getPhysicalNumberOfCells();
for (int cellNum = 0; cellNum < cellCount; cellNum++) {
System.out.print("【" + (rowNum + 1) + "-" + (cellNum + 1) + "】");
Cell cell = rowData.getCell(cellNum);
if (cell != null) {
CellType cellType = cell.getCellType();
//判断单元格数据类型
String cellValue = "";
if(CellType.STRING.equals(cellType)){
System.out.print("【STRING】");
cellValue = cell.getStringCellValue();
}else if(CellType.BOOLEAN.equals(cellType)){
System.out.print("【BOOLEAN】");
cellValue = String.valueOf(cell.getBooleanCellValue());
}else if(CellType.NUMERIC.equals(cellType)){
System.out.print("【NUMERIC】");
if(DateUtil.isCellDateFormatted(cell)){
System.out.print("【日期】");
Date date = cell.getDateCellValue();
cellValue = new DateTime(date).toString("yyyy-MM-dd");
}else{
System.out.print("【转换成字符串】");
cellValue = String.valueOf(cell.getNumericCellValue());
}
}else if(CellType.BLANK.equals(cellType)){
System.out.print("【BLANK】");
}else{
System.out.println(cellType);
}
System.out.println(cellValue);
}
}
}
}

is.close();
}

Web环境集成Excel (权限管理)

  • 添加POI依赖
  • 完成导出功能

页面添加按钮和点击事件

1
2
3
<a href="#" class="btn btn-warning btn-input" style="margin: 10px" onclick="exportOP()">
<span class="glyphicon glyphicon-save"></span> 导出
</a>
1
2
3
function exportOP() {
window.open("/permission/export")
}

后台代码

  • Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @RequestMapping("/export")
    @ResponseBody
    public void export(HttpServletResponse response) throws IOException {
    // filename 表示文件名称
    response.setHeader("Content-Disposition","attachment;filename=permissionData.xlsx");
    Workbook workbook = permissionService.export();
    workbook.write(response.getOutputStream());
    workbook.close();
    }
  • Service

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Override
    public Workbook export() {
    List<Permission> permissions = permissionMapper.selectAll();
    Workbook workbook = new XSSFWorkbook();
    // 设置页名
    Sheet sheet = workbook.createSheet("权限列表");
    // 创建行
    Row row = sheet.createRow(0);
    // 设置列
    row.createCell(0).setCellValue("权限名称");
    row.createCell(1).setCellValue("权限表达式");
    // 遍历设置行和列
    for (int i = 0; i < permissions.size(); i++) {
    row = sheet.createRow(i + 1);
    Permission permission = permissions.get(i);
    row.createCell(0).setCellValue(permission.getName());
    row.createCell(1).setCellValue(permission.getExpression());
    }
    return workbook;
    }
  • 完成导入功能

添加按钮

1
2
3
<a href="#" class="btn btn-info btn-input" style="margin: 10px" onclick="importOP()">
<span class="glyphicon glyphicon-open"></span> 导入
</a>

添加模块框(Bootstrap官网)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<#-- 模态框 -->
<div class="modal fade" id="importModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="myModalLabel">导入</h4>
</div>
<form class="form-horizontal" enctype="multipart/form-data" method="post" id="importForm">
<div class="modal-body">
<div class="form-group" style="margin-top: 10px;">
<label for="name" class="col-sm-3 control-label"></label>
<div class="col-sm-6">
<!-- 文件上传框 -->
<input id="uploadFile" type="file" name="file"/>
</div>
</div>
<div class="form-group" style="margin-top: 10px;">
<div class="col-sm-3"></div>
<div class="col-sm-6">
<a href="#" onclick="downloadTemplateOP()" class="btn btn-success">
<span class="glyphicon glyphicon-download"></span> 下载模板
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" onclick="importSave()" class="btn btn-primary btn-submit">保存</button>
</div>
</form>
</div>
</div>
</div>

添加点击事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function importOP() {
$("#importModal").modal("show");
}

function downloadTemplateOP() {
// 在静态资源下存放文件, 点击即可下载
window.open("/templat/permissionTemplate.xlsx")
}

function importSave() {
var $file1 = $("#uploadFile").val(); //用户文件内容(文件)
// 判断文件是否为空
if ($file1 == "") {
Swal.fire({
text: "请选择上传的目标文件! ",
icon: 'warning',
})
return false;
}
var formData = new FormData(); //这里需要实例化一个FormData来进行文件上传
formData.append("file", $("#uploadFile")[0].files[0]);
$.ajax({
type: "post",
url: "/permission/importExcel",
data: formData,
processData: false,
contentType: false,
success: function (data) {
if (data.success) {
Swal.fire({
text: data.msg,
icon: 'success',
})
$("#importModal").modal("hide");
$('#table').bootstrapTable('refresh');
} else {
Swal.fire({
text: "请选择上传的目标文件! ",
icon: 'warning',
})
}
}
});
}

后台代码

  • Controller
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @RequestMapping("/importExcel")
    @ResponseBody
    public JsonResult importExcel(MultipartFile file) {
    try {
    int count = permissionService.importExcel(file);
    return new JsonResult(true, "成功导入:" + count + "条数据");
    } catch (Exception e) {
    return new JsonResult(false, "导入失败!");
    }
    }
  • Service
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    @Override
    public int importExcel(MultipartFile file) throws IOException {
    Workbook workbook = new XSSFWorkbook(file.getInputStream());
    Sheet sheet = workbook.getSheetAt(0);
    int lastRowNum = sheet.getLastRowNum();
    int insertCount = 0;
    Row row = null;
    Cell c1 = null;
    Cell c2 = null;
    //跳过第一行
    for (int i = 1; i <= lastRowNum; i++) {
    row = sheet.getRow(i);
    c1 = row.getCell(0);
    c2 = row.getCell(1);
    //判断单元格为空就不处理
    if (c1 != null && c2 != null) {
    String name = c1.getStringCellValue();
    String expression = c2.getStringCellValue();
    //判断是否有内容
    if (StringUtils.hasText(name) && StringUtils.hasText(expression)) {
    //判断表达式是否已经存在
    int count = permissionMapper.getCountByExpression(expression);
    if (count == 0) {
    Permission p = new Permission();
    p.setName(name);
    p.setExpression(expression);
    permissionMapper.insert(p);
    insertCount++;
    }
    }
    }
    }
    return insertCount;
    }