一、实现方式
在Java中,导出数据到Excel有多种方式,每种方式都有其优缺点,适用于不同的场景。以下是常见的几种方式及其特点:
1.1 Apache POI
Apache POI 是 Java 中最流行的库,支持读写 Excel 文件(包括 .xls 和 .xlsx 格式)。
- 特点:
- 支持 .xls(HSSFWorkbook)和 .xlsx(XSSFWorkbook、SXSSFWorkbook)。
- 功能强大,支持样式、公式、图表等。
- SXSSFWorkbook 支持流式写入,适合大数据量导出。
- 适用场景:
- 需要导出复杂格式的 Excel 文件。
- 大数据量导出(使用 SXSSFWorkbook)。
1.2 EasyExcel
EasyExcel 是阿里巴巴开源的 Excel 操作库,基于 Apache POI 封装,专注于大数据量导出和导入。
- 特点:
- 支持流式读写,内存占用低。
- API 简单易用。
- 支持 .xlsx 格式。
- 适用场景:
- 大数据量导出(百万级数据)。
- 需要高性能和低内存占用的场景。
1.3 OpenCSV
虽然不是直接导出 Excel 文件,但 CSV 文件可以被 Excel 直接打开,适合简单的数据导出。
- 特点:
- 轻量级,速度快。
- 文件格式简单,不支持样式、公式等。
- 适合纯数据导出。
- 适用场景:
- 数据量较大,且不需要复杂格式。
- 需要快速导出和导入。
二、使用SXSSFWorkbook
2.1 添加依赖
在pom.xml中添加Apache POI依赖:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
2.2 定义数据模型
创建一个 Java 类表示导出的数据模型。
public class DataModel {
private Long id;
private String name;
private Double value;
// 构造方法、Getter 和 Setter
public DataModel(Long id, String name, Double value) {
this.id = id;
this.name = name;
this.value = value;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
}
2.3 分页查询数据
使用Spring Data JPA或MyBatis进行分页查询。
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@Service
public class DataService {
@Autowired
private DataRepository dataRepository;
public Page<DataEntity> getDataByPage(int page, int size) {
Pageable pageable = PageRequest.of(page, size);
return dataRepository.findAll(pageable);
}
}
2.4 多线程导出数据
使用线程池并行处理分页查询和数据写入。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.*;
@Service
public class ExcelExportService {
@Autowired
private DataService dataService;
// 线程池配置
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
public void exportLargeDataToExcel(String filePath, int pageSize) throws IOException, InterruptedException, ExecutionException {
// 创建 SXSSFWorkbook,设置内存中保留的行数(默认100)
try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) {
Sheet sheet = workbook.createSheet("Sheet1");
// 创建表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("ID");
headerRow.createCell(1).setCellValue("Name");
headerRow.createCell(2).setCellValue("Value");
// 计算总页数
long totalCount = dataService.getTotalCount();
int totalPages = (int) Math.ceil((double) totalCount / pageSize);
// 使用 CountDownLatch 等待所有线程完成
CountDownLatch latch = new CountDownLatch(totalPages);
// 提交任务到线程池
for (int page = 0; page < totalPages; page++) {
final int currentPage = page;
executorService.submit(() -> {
try {
// 分页查询数据
Page<DataModel> dataPage = dataService.getDataByPage(currentPage, pageSize);
List<DataModel> dataList = dataPage.getContent();
// 写入当前页数据
synchronized (sheet) {
int startRow = currentPage * pageSize + 1; // 数据从第2行开始
for (int i = 0; i < dataList.size(); i++) {
DataModel data = dataList.get(i);
Row row = sheet.createRow(startRow + i);
row.createCell(0).setCellValue(data.getId());
row.createCell(1).setCellValue(data.getName());
row.createCell(2).setCellValue(data.getValue());
}
}
} finally {
latch.countDown(); // 任务完成
}
});
}
// 等待所有线程完成
latch.await();
// 写入文件
try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
workbook.write(outputStream);
}
// 清理临时文件
workbook.dispose();
}
// 关闭线程池
executorService.shutdown();
}
}
2.5 调用导出方法
在Controller或Service中调用导出方法。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
@RestController
@RequestMapping("/export")
public class ExportController {
@Autowired
private ExcelExportService excelExportService;
@GetMapping("/excel")
public String exportToExcel() throws IOException, InterruptedException, ExecutionException {
String filePath = "large_data_export.xlsx";
excelExportService.exportLargeDataToExcel(filePath, 10000); // 每页查询10000条数据
return "Export successful! File saved at: " + filePath;
}
}
三、使用EasyExcel
3.1 添加依赖
在 pom.xml 中添加 EasyExcel 依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
3.2 定义数据模型
创建一个 Java 类表示导出的数据模型。
import com.alibaba.excel.annotation.ExcelProperty;
public class DataModel {
@ExcelProperty("ID")
private Long id;
@ExcelProperty("Name")
private String name;
@ExcelProperty("Value")
private Double value;
// 构造方法、Getter 和 Setter
public DataModel(Long id, String name, Double value) {
this.id = id;
this.name = name;
this.value = value;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
}
3.3 分页查询数据
使用 Spring Data JPA 或 MyBatis 进行分页查询。
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@Service
public class DataService {
@Autowired
private DataRepository dataRepository;
public Page<DataModel> getDataByPage(int page, int size) {
Pageable pageable = PageRequest.of(page, size);
return dataRepository.findAll(pageable);
}
}
3.4 多线程导出数据
使用线程池并行处理分页查询和数据写入。
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.metadata.WriteSheet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.*;
@Service
public class ExcelExportService {
@Autowired
private DataService dataService;
// 线程池配置
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
public void exportLargeDataToExcel(HttpServletResponse response, int pageSize) throws IOException, InterruptedException, ExecutionException {
// 设置响应头
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String fileName = "large_data_export.xlsx";
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
// 创建 Excel 写入器
WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").head(DataModel.class).build();
// 计算总页数
int totalPages = (int) Math.ceil((double) dataService.getTotalCount() / pageSize);
// 使用 CountDownLatch 等待所有线程完成
CountDownLatch latch = new CountDownLatch(totalPages);
// 提交任务到线程池
for (int page = 0; page < totalPages; page++) {
final int currentPage = page;
executorService.submit(() -> {
try {
// 分页查询数据
Page<DataModel> dataPage = dataService.getDataByPage(currentPage, pageSize);
List<DataModel> dataList = dataPage.getContent();
// 写入当前页数据
EasyExcel.write(response.getOutputStream(), DataModel.class)
.sheet("Sheet1")
.doWrite(dataList);
} catch (IOException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 任务完成
}
});
}
// 等待所有线程完成
latch.await();
// 关闭线程池
executorService.shutdown();
}
}
3.5 Controller 调用导出方法
在 Controller 中调用导出方法。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
@RestController
@RequestMapping("/export")
public class ExportController {
@Autowired
private ExcelExportService excelExportService;
@GetMapping("/excel")
public void exportToExcel(HttpServletResponse response) throws IOException, InterruptedException, ExecutionException {
excelExportService.exportLargeDataToExcel(response, 10000); // 每页查询10000条数据
}
}
四、使用OpenCSV
4.1 添加依赖
在 pom.xml 中添加 OpenCSV 依赖:
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.8</version>
</dependency>
4.2 定义数据模型
创建一个 Java 类表示导出的数据模型。
public class DataModel {
private Long id;
private String name;
private Double value;
// 构造方法、Getter 和 Setter
public DataModel(Long id, String name, Double value) {
this.id = id;
this.name = name;
this.value = value;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
}
4.3 分页查询数据
使用 Spring Data JPA 或 MyBatis 进行分页查询。
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@Service
public class DataService {
@Autowired
private DataRepository dataRepository;
public Page<DataModel> getDataByPage(int page, int size) {
Pageable pageable = PageRequest.of(page, size);
return dataRepository.findAll(pageable);
}
public long getTotalCount() {
return dataRepository.count();
}
}
4.4 多线程导出数据
使用线程池并行处理分页查询和数据写入。
import com.opencsv.CSVWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
@Service
public class CsvExportService {
@Autowired
private DataService dataService;
// 线程池配置
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
public void exportLargeDataToCsv(String filePath, int pageSize) throws IOException, InterruptedException, ExecutionException {
// 创建 CSV 写入器
try (CSVWriter writer = new CSVWriter(new FileWriter(filePath))) {
// 写入表头
writer.writeNext(new String[]{"ID", "Name", "Value"});
// 计算总页数
long totalCount = dataService.getTotalCount();
int totalPages = (int) Math.ceil((double) totalCount / pageSize);
// 使用 Future 保存每个线程的查询结果
List<Future<List<DataModel>>> futures = new ArrayList<>();
// 提交任务到线程池
for (int page = 0; page < totalPages; page++) {
final int currentPage = page;
Future<List<DataModel>> future = executorService.submit(() -> {
Page<DataModel> dataPage = dataService.getDataByPage(currentPage, pageSize);
return dataPage.getContent();
});
futures.add(future);
}
// 合并所有线程的查询结果并写入 CSV
for (Future<List<DataModel>> future : futures) {
List<DataModel> dataList = future.get();
for (DataModel data : dataList) {
writer.writeNext(new String[]{
String.valueOf(data.getId()),
data.getName(),
String.valueOf(data.getValue())
});
}
}
}
// 关闭线程池
executorService.shutdown();
}
}
4.5 Controller 调用导出方法
在 Controller 中调用导出方法。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
@RestController
@RequestMapping("/export")
public class ExportController {
@Autowired
private CsvExportService csvExportService;
@GetMapping("/csv")
public String exportToCsv() throws IOException, InterruptedException, ExecutionException {
String filePath = "large_data_export.csv";
csvExportService.exportLargeDataToCsv(filePath, 10000); // 每页查询10000条数据
return "Export successful! File saved at: " + filePath;
}
}