Generate Large XLSX USING Spring Framework & Apache POI

Para este ejemplo use la librería Apache POI que permite generar documentos de Office, el problema principal de la solución fue bajar los datos de una consulta a una BD que podia regresar muchos registros, por lo que no podíamos dejar esperando al usuario a que se generara completo el archivo Excel para que pudiera ser descargado.
 La solución fue generar un archivo excel vacío con la cabecera necesaria para enviar el setContentType y el navegador queda esperando a que el ServletOutputStream termine de enviar datos Bytes x Bytes.


mvc-dispatcher-servlet.xml


    
        
        
        
    
    
    
    
    
    
    
    

    
    
    

    
    
        
    


    
    
    
    
        
        
        
        
        
        
        
        
            
                unchecked
            
        
    

    
    
        
        
            
                classpath:message
            
        
        
    

    
        
    

    
        
    
    

    
  
    
        
            
            
                
                
                
                
                
     
            
            
        
    
     



Encabezado de archivos XLSX, como se puede ver en la imagen todo archivo XLS tiene como Header los Byte: {0x50, 0x4b, 0x03, 0x04, 0x14, 0x00}; 



Metodo que Baja el archivo Bytes x Bytes
 
 private void doDownload(String accion, String fecha, String fechainicio, String fechafinal, String compania, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String ftemporal = fecha;
        //XLS
        SXSSFWorkbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory, exceeding rows will be flushed to disk
        String mensaje = accion.equals("B") == true ? "Bloqueo" : "Desbloqueo";
        String[] ftempo = ftemporal.split("/"); //{DD,MM,YYYY}

        Sheet sheet = wb.createSheet("ReporteMora_" + mensaje + "_" + ftempo[2] + ftempo[1] + ftempo[0]);
        OutputStream out = response.getOutputStream();
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=utf8");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=\"" + sheet.getSheetName() + ".xlsx\"");
        final byte headerBytes[] = new byte[]{0x50, 0x4b, 0x03, 0x04, 0x14, 0x00};
        out.write(headerBytes);
        response.flushBuffer();
        //encabezado
        //data
        Row encabezado = sheet.createRow(0);
        sheet.autoSizeColumn(0);
        Cell cell = encabezado.createCell(0);
        cell.setCellValue((String) "IMEI");
        cell = encabezado.createCell(1);
        cell.setCellValue((String) "MARCA");
        cell = encabezado.createCell(2);
        cell.setCellValue((String) "MODELO");
        cell = encabezado.createCell(3);
        cell.setCellValue((String) "FECHA_INGRESO");
        cell = encabezado.createCell(4);
        cell.setCellValue((String) "ACCION");
        cell = encabezado.createCell(5);
        cell.setCellValue((String) "TIPO_TECNOLOGIA");
        cell = encabezado.createCell(6);
        cell.setCellValue((String) "USUARIO");

        ReportesDelegate reporteEstados = new ReportesDelegate();
        int rownum = 1;
        out.flush();
        try {
            /*
             Metodo que recibe la fecha en formato dd/mm/YYYY
             finicio en formato dd/mm/yy hh24:mi:ss
             ffin en fomrato dd/mm/yy hh24:mi:ss
             accion B o D //bloqueo desbloqueo
             compania en codigo_compania x ej 219 claro
             retorna una lista del reporte cobranza en detalle.
             */
           
            List resultadoEstado = reporteEstados.getDetalleCobranza(fecha, fechainicio + " 00:00:00", fechafinal + "23:59:59", accion, compania);
            int tamaniolista=resultadoEstado.size();
            for (ReporteCobranza r : resultadoEstado) {
 
                Row row = sheet.createRow(rownum++);

                Cell cellda = row.createCell(0);
                cellda.setCellValue(r.getImei().toString());
                cellda = row.createCell(1);
                cellda.setCellValue((String) r.getMarca());
                cellda = row.createCell(2);
                cellda.setCellValue((String) r.getModelo());
                cellda = row.createCell(3);
                cellda.setCellValue((String) r.getFechaIngreso());
                cellda = row.createCell(4);
                cellda.setCellValue((String) r.getAccion());
                cellda = row.createCell(5);
                cellda.setCellValue((String) r.getTipoTecnologia());
                cellda = row.createCell(6);
                cellda.setCellValue((String) r.getUsuario());

            }
            for (int i = 0; i < tamaniolista; i++) {
                sheet.autoSizeColumn(i);
            }
        } catch (Exception e) {
            System.out.println("Error reporte:" + e);
        }

        CustomOutputStream out2 = new CustomOutputStream(out, headerBytes.length);
        wb.write(out2);
        out.flush();
        out.close();
        wb.dispose();
    }



Considere el uso de la versión Streaming de POI. Esto cargará un subconjunto del archivo en la memoria según sea necesario. Es el método recomendado cuando se trata de archivos de gran tamaño:
SXSSFWorkbook wb = new SXSSFWorkbook(100); // keep 100 rows in memory, exceeding rows will be flushed to disk




Al ecribir el encabezado uso el "sheet.autoSizeColumn", pero solo lo hago en el encabezado, ya que si se integra en el ciclo de creacion este reduce considerablemente la rapidez con que se genera el archivo EXCEL. Consultar Documentacion relacionada del metodo
En las siguientes lineas creo un OutputStream que ocupare para enviar solo el encabezado del archivo y despues cuando empieze a llegar las ROW las enviare al navegador, la velocidad de descarga dependera de la conexion entre el servidor de Aplicaciones y la aplicacion y el tiempo de la Base de datos, considerando la cantidad de informacion que manejaba se pueden estimar en archivos de 150 MB promedio. Generar un archivo asi en una aplicaciones WEB es un tiempo considerable que se le debe hacer esperar al usuario, la mejor solucion fue el CustomOutputStream.
Por ultimo el " response.flushBuffer" con esto se vacia el buffer y se envia al response la informacion. En la primera vuelco de buffer es solo el HEADER generico de un archivo EXCEL XLSX.

        OutputStream out = response.getOutputStream();
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=utf8");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=\"" + sheet.getSheetName() + ".xlsx\"");
        final byte headerBytes[] = new byte[]{0x50, 0x4b, 0x03, 0x04, 0x14, 0x00};
        out.write(headerBytes);
        response.flushBuffer();

Controlador de SPRING que activa la Descarga

import cl.claro.aplicaciones.webberr.modelo.CobranzaModel;
import cl.clarochile.aplicaciones.berr.configuracion.dao.entities.Compania;
import cl.clarochile.aplicaciones.berr.configuracion.dao.entities.Estado;
import cl.clarochile.aplicaciones.berr.configuracion.delegate.ConfiguracionDelegate;
import cl.clarochile.aplicaciones.berr.consultas.to.HistoricoVistaTO;
import cl.clarochile.aplicaciones.berr.reportes.to.CobranzaResumen;
import cl.clarochile.aplicaciones.berr.reportes.to.DatoBitacoraProceso;
import cl.clarochile.aplicaciones.berr.reportes.to.ReporteCobranza;
import cl.clarochile.aplicaciones.berr.service.delegate.ReportesDelegate;
import cl.clarochile.infraestructura.exceptions.BussinesFault;
import cl.clarochile.infraestructura.exceptions.TechnicalFault;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.xssf.streaming.SXSSFWorkbook;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

     @RequestMapping(value = "/descargaDetalleCobranza")
    public void descargaDetalleCobranza(@RequestParam(value = "compania") String compania,
            @RequestParam(value = "fecha") String fecha,
            @RequestParam(value = "fechainicio") String fechainicio,
            @RequestParam(value = "fechafinal") String fechafinal,
            @RequestParam(value = "accion") String accion,
            HttpServletRequest request, HttpServletResponse response) throws Exception {

        doDownload(accion, fecha, fechainicio, fechafinal, compania, request, response);

    }


Clase que extiende ServletOutputStream para evitar volver a enviar el Header del archivo XLSX
class CustomOutputStream extends ServletOutputStream {

        OutputStream os;
        int skip = 0;
        int n = 0;

        public CustomOutputStream(OutputStream out, int skip) {
            this.os = out;
            this.skip = skip;
        }

        @Override
        public void write(int b) throws IOException {
            if (n < skip) {
                n++;
                return;
            }

            os.write(b);
        }
    }


ReporteCobranza.jsp

 Excel
                                                                            

Resumen de lo aprendido en la ONT Day 2014

Bruno Borjes – Building Java EE 7 Applications with WebLogic 12c


Optimizer Master Class with Tom Kyte

Turbo Mobile Development with Oracle …presentin David Peake BLOG David Peake

Dana Singleterry – HOL – Developing a Web Dashboard & ADF WEB Y MOVILE

Blog Dana Singleterry

Problemas de activación WIFI6 en LG_OLED55CXPSA

  Mi experiencia con este TV  ah sido impecable hasta ahora, llevaba un uso normal y no tenia que usar la conexión por wifi ya que tengo una...