利用Mybatis的resultHandler和POI,实现超大数据量导出到EXCEL。

采用Mybatis持久化框架,查询数据采用List模式返回结果,这种数据返回模式,能满足大多数的在少量返回结果集,或分页结果集的应用场景。

针对要大量返回结果集,特别是需要把超大查询结果集返回或导出的时候,List模式由于需要在应用服务器中构造对象。这不但需要消耗大量内存,而且会引起频繁的垃圾回收,造成性能降低。甚至因为内存无法容纳过大的结果集List,造成OOM异常。

有人采用分页模式,逐页导出的方式来解决。这种解决方式,针对超大结果集,比如数百万数据行时,由于可能会产生上万个分页查询,造成数据库上万次查询,给数据库性能带来灾难性影响。

本文档采用Mybatis的ResultHandler接口,采用ResultHandler回调模式,实现逐条处理,既实现一次查询获得所有数据,不引起数据库性能灾难,又避免应用服务器内存过大消耗的问题。

下面直接上实现代码。

1.依赖的包引入

   

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

2.超大数据集导出EXCEL工具类ExcelGenerator,本类实现了ResultHandler,通过回调,逐条导出。具体代码

 

import java.io.IOException;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.util.Map;

import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
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.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

/**
 * Mybatis查询数据采用List的方式返回结果集,当结果集过大,内存无法将结果集对象全部容纳,抛出OOM异常,
 * 本类主要采用Mybatis提供的ResultHandler接口逐条处理数据的方式读取数据,对数据进行转化。
 * 并将结果集逐行转化为EXCEL,当结果集过大,Workbook对象也会太大,故可以设置isLargeExcel=true,
 * 采用POI缓存机制,达到一定条目,缓存到临时文件的方式,用以实现巨量数据导出为EXCEL。
 * @author tbr
 *
 * @param <T> 转化后,用于输出到EXCEL的对象类
 * @param <E> Mybatis对象类
 */
public abstract class ExcelGenerator<T,E> implements ResultHandler<E> {
    protected String[] titles;
    protected String[] fields;
    protected int colcnt;
    protected Map<String,DecimalFormat> numberFormats;
    protected Map<String,DateFormat> dateFormats;
    
    protected Workbook workbook;
    protected String sheetName;
    protected int sheetIndex;
    protected Sheet sheet;
    protected int rownum = 0;
    protected boolean isLargeExcel=false;
    
    /**
     * 
     * @param titles excel表头名列表
     * @param fields 需要输出的excel列对应的数据对象属性名列表
     * @param numberFormats 需要格式化的数字类属性的输出格式映射表
     * @param dateFormats 需要格式化的日期型字段属性的输出格式映射表
     */
    public ExcelGenerator(String[] titles, String[] fields,
            Map<String,DecimalFormat> numberFormats,
            Map<String,DateFormat> dateFormats) {
        if (titles==null)
            this.titles=fields;
        else
            this.titles=titles;
        this.fields=fields;
        this.colcnt=fields.length;
        this.numberFormats=numberFormats;
        this.dateFormats=dateFormats;
    }
    
    public void initWorkbook(String sheetName,boolean isLargeExcel) throws IOException{
                
        this.isLargeExcel=isLargeExcel;
        this.sheetName=sheetName;

        if (isLargeExcel){
            SXSSFWorkbook wb = new SXSSFWorkbook(-1); 
            wb.setCompressTempFiles(true); 
            workbook=wb;
        }else{
            workbook = new XSSFWorkbook(); 
        }
        sheetIndex=1;        
        sheet = workbook.createSheet(sheetName+"-"+sheetIndex);        
        createHeader();        
    }
    
    public Workbook getWorkbook(){
        return workbook;
    }

    @Override
    public void handleResult(ResultContext<? extends E> resultContext) {
        rownum++;
        try {
            E entity=resultContext.getResultObject();
            if (entity==null){
                return;
            }
            createRow(convert(entity));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 数据转化处理
     */
    public abstract T convert(E entity);
    
    private void createRow(Object data) throws IOException{
        Row row = sheet.createRow(rownum);
        for(int cellnum = 0; cellnum < colcnt; cellnum++){
            createCell(rownum,row,cellnum,data);
        }        

        if (isLargeExcel && rownum % 1000 == 0) {
            // 每100行,缓存一下,减少内存消耗
            //System.out.println("rownum:"+rownum+"-----------"+System.currentTimeMillis());
            ((SXSSFSheet)sheet).flushRows(1000);
        }
        if (rownum % 1000000 == 0) {
            //一百万行,另外创建一个sheet
            sheetIndex++;
            String newSheetName=sheetName+"-"+sheetIndex;
            //System.out.println("rownum:"+rownum+"-----------"+newSheetName);
            sheet=workbook.createSheet(newSheetName);
            createHeader();        
        }
    }    

    private void createCell(int rownum,Row row,int cellnum,Object data) throws IOException{
        Cell cell = row.createCell(cellnum);
        String fieldName=fields[cellnum];
        Object fieldValue=getFieldData(data,fieldName);
        Object cellValue=fieldValue;
        if ( numberFormats != null && numberFormats.containsKey(fieldName) ){
            cellValue=numberFormats.get(fieldName).format(fieldValue);
        }
        if ( dateFormats != null && dateFormats.containsKey(fieldName) ){
            cellValue=dateFormats.get(fieldName).format(fieldValue);
        }
        if (cellValue!=null){
            cell.setCellValue(cellValue.toString());
        }else{
            cell.setCellValue("");
        }
    }
    
    private Object getFieldData(Object data,String fieldName){
        Object ret=data;
        String[] fieldList=fieldName.split("\\.");
        for(String fn:fieldList){
            if (ret==null)
                return ret;
            try {
                ret=BeanUtils.forceGetProperty(ret,fn);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
                ret=null;
            }
        }
        return ret;
    }
    
    private void createHeader(){
        rownum=0;
        //输出表头
        Row row = sheet.createRow(rownum);
        
        Cell cell;        
        for (int cellnum=0;cellnum<titles.length;cellnum++ ){
            cell = row.createCell(cellnum);           
            cell.setCellValue(titles[cellnum]);                
        }        
    }

}


3.使用方法

定义使用ResultHandler的Mapper方法。

import org.apache.ibatis.annotations.*;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.session.ResultHandler;

@Mapper
public interface XXXXMapper  {

    ......

    @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000)
    @ResultType(XXXX.class)
    void query1By1(@Param("params") Map params,ResultHandler<XXXX> handler);
    ......

}


 

4.导出数据例子


   

.......

    @RequestMapping("2excel")
    public void  test2Excel() throws IOException{
        
        String fileName="测试文件";
                
        String[] titles={......};//excel标题列表
        String[] fields={......};//导出到excel的属性列表

        Map<String,DecimalFormat> numberFormats=null;//数字格式map
        Map<String,DateFormat> dateFormats=null;//日期格式map

        String fn=java.net.URLEncoder.encode(fileName, "UTF-8");
        response.setCharacterEncoding("utf-8");
        
        OutputStream out;
        response.setContentType("Content-Type: application/vnd.ms-excel");
        response.setHeader("Content-disposition", "attachment; filename="
                    + fn + ".xlsx");
        out = response.getOutputStream();

        Map<String,Object> paras=new HashMap<>(); 
        
        ExcelGenerator<BizBean,EOBean> deu=new ExcelGenerator<BizBean,EOBean>(titles,fields,numberFormats,dateFormats){
            public BizBean convert(EOBean entity){

                BizBean bizentity=new BizBean();

                .......//对象转化,将数据库实体对象转化为导出所需要的业务对象
                return bizentity;
            }
        };
        deu.initWorkbook(fileName, true);
        appInfoMapper.query1By1(paras,deu);
        Workbook wb=deu.getWorkbook();
        wb.write(out);
        out.flush();
        if (wb instanceof SXSSFWorkbook){
            ((SXSSFWorkbook)wb).dispose();
        }
        wb.close();
        out.close();
        return;
        
    }
......

 到此,你就可以照此开发超大规模数据的导出了。你不但可以导出几百几千的,你甚至可以导出上亿的。只是这个EXCEL太大了,你的OFFICE是否能打开就不知道。

5.写在后面

当然,你也可以修改一下,导出为CSV文件,这个就没有任何数据量的限制了。

是不是很爽

  • 4
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
实现Excel导入导出,需要用到POI库。下面介绍一下使用SpringBoot集成MyBatisPOI实现Excel导入导出的步骤。 1. 引入依赖 在pom.xml文件中添加以下依赖: ``` <!-- SpringBoot MyBatis 依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <!-- POI 依赖 --> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.0.1</version> </dependency> ``` 2. 创建实体类 创建一个实体类,用于映射Excel文件中的数据。 ```java public class User { private Integer id; private String name; private String email; private String phone; // 省略 getter 和 setter 方法 } ``` 3. 创建Mapper 创建一个Mapper,用于操作数据库。 ```java @Mapper public interface UserMapper { List<User> findAll(); void insert(User user); void batchInsert(List<User> userList); } ``` 4. 创建Service 创建一个Service,用于导入和导出Excel文件。 ```java @Service public class UserService { @Autowired private UserMapper userMapper; public List<User> findAll() { return userMapper.findAll(); } public void insert(User user) { userMapper.insert(user); } public void batchInsert(MultipartFile file) throws IOException { List<User> userList = readExcel(file); userMapper.batchInsert(userList); } public void exportExcel(HttpServletResponse response) throws IOException { List<User> userList = userMapper.findAll(); writeExcel(response, userList); } private List<User> readExcel(MultipartFile file) throws IOException { List<User> userList = new ArrayList<>(); Workbook workbook = WorkbookFactory.create(file.getInputStream()); Sheet sheet = workbook.getSheetAt(0); for (int i = sheet.getFirstRowNum() + 1; i <= sheet.getLastRowNum(); i++) { Row row = sheet.getRow(i); User user = new User(); user.setName(row.getCell(0).getStringCellValue()); user.setEmail(row.getCell(1).getStringCellValue()); user.setPhone(row.getCell(2).getStringCellValue()); userList.add(user); } return userList; } private void writeExcel(HttpServletResponse response, List<User> userList) throws IOException { Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("Users"); Row headerRow = sheet.createRow(0); headerRow.createCell(0).setCellValue("Name"); headerRow.createCell(1).setCellValue("Email"); headerRow.createCell(2).setCellValue("Phone"); for (int i = 0; i < userList.size(); i++) { Row row = sheet.createRow(i + 1); User user = userList.get(i); row.createCell(0).setCellValue(user.getName()); row.createCell(1).setCellValue(user.getEmail()); row.createCell(2).setCellValue(user.getPhone()); } response.setHeader("Content-Disposition", "attachment; filename=users.xlsx"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); workbook.write(response.getOutputStream()); } } ``` 5. 创建Controller 创建一个Controller,用于接收导入和导出Excel文件的请求。 ```java @RestController public class UserController { @Autowired private UserService userService; @GetMapping("/users") public List<User> findAll() { return userService.findAll(); } @PostMapping("/users") public void insert(@RequestBody User user) { userService.insert(user); } @PostMapping("/users/import") public void batchInsert(@RequestParam("file") MultipartFile file) throws IOException { userService.batchInsert(file); } @GetMapping("/users/export") public void exportExcel(HttpServletResponse response) throws IOException { userService.exportExcel(response); } } ``` 至此,就完成了SpringBoot集成MyBatisPOI实现Excel导入导出的步骤。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值