Java实现大数据量导出报表

一、实现方式

在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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值