Utilice EasyExcel para importar y exportar millones de datos

prefacio

En el desarrollo de proyectos, a menudo es necesario utilizar la importación y exportación de datos: importar es importar de Excel a la base de datos y exportar es consultar datos de la base de datos y luego usar POI para escribirlos en Excel.

Creo que todos encontrarán la importación y exportación de big data en el desarrollo y las entrevistas diarias.

Siempre que se resuelvan muchos problemas esta vez, siempre se revisarán y registrarán para que puedan resolverse fácilmente si se encuentran con los mismos problemas más adelante. Bien, sin más preámbulos, ¡comencemos con el texto principal!

1. Comparación de ventajas y desventajas de las versiones tradicionales de PDI

De hecho, cuando piensas en la importación y exportación de datos, es natural que te vengan a la mente los problemas con la tecnología POI de Apache y la versión de Excel.

  • La clase de implementación HSSFWorkbook
    es el objeto que más utilizamos al principio y puede operar todas las versiones de Excel anteriores a Excel 2003 (incluido el 2003). Antes de 2003, el sufijo de la versión de Excel todavía era .xls.
  • La clase de implementación XSSFWorkbook
    se puede encontrar y aún utilizar en muchas empresas, es una versión que opera entre Excel 2003 y Excel 2007. La extensión de Excel es .xlsx.
  • La clase de implementación SXSSFWorkbook
    solo está disponible en versiones posteriores a POI3.8. Puede operar todas las versiones de Excel posteriores a Excel 2007. La extensión es .xlsx.

Libro de trabajo HSSF

Sin embargo, es el método más utilizado en la versión POI:

  • Su desventaja es que solo puede exportar hasta 65535 filas, es decir, si la función de datos exportados excede estos datos, se informará un error;
  • Su ventaja es que no informará desbordamiento de memoria. (Debido a que la cantidad de datos es inferior a 7w, la memoria generalmente es suficiente. En primer lugar, debe comprender claramente que este método consiste en leer los datos en la memoria primero y luego operar)

Libro de trabajo XSSF

  • Ventajas: Este formulario parece superar el límite de 65535 filas de HSSFWorkbook y apuntar a las 1048576 filas y 16384 columnas de la versión Excel 2007. Puede exportar hasta 104w piezas de datos;
  • Desventajas: Surgieron los problemas que lo acompañaron: aunque el número de filas de datos exportados aumentó muchas veces, el problema de desbordamiento de memoria que siguió también se convirtió en una pesadilla. Debido a que los libros, hojas, filas, celdas, etc. que crea se almacenan en la memoria antes de escribirse en Excel (esto no incluye algunos estilos y formatos de Excel, etc.), se puede imaginar que la memoria no desbordamiento ¡Es un poco poco científico! ! !

SXSSFLibro de trabajo

A partir de la versión 3.8 de POI, se proporciona un método SXSSF de baja memoria basado en XSSF:
Ventajas:

  • Este método generalmente no causa desbordamiento de memoria (utiliza el disco duro a cambio de espacio en la memoria).
  • Es decir, cuando los datos en la memoria alcanzan un cierto nivel, los datos se conservarán en el disco duro y se almacenarán, y los datos más recientes se almacenarán en la memoria).
  • Y admite la creación de archivos Excel de gran tamaño (más que suficiente para almacenar millones de datos).
    defecto:
  • Dado que parte de los datos persisten en el disco duro y no se pueden ver ni acceder a ellos, provocará,
  • Solo podemos acceder a una cierta cantidad de datos en el mismo momento, es decir, los datos almacenados en la memoria, el método Sheet.clone() ya no será compatible, aún por razones de persistencia;
  • La evaluación de fórmulas ya no es compatible o, por motivos de persistencia, los datos del disco duro no se pueden leer en la memoria para realizar el cálculo;
  • Cuando se utiliza el método de plantilla para descargar datos, el encabezado de la tabla no se puede cambiar o, debido a problemas de persistencia, no se puede cambiar después de escribirlo en el disco duro;

2. El método de uso depende de la situación.

Después de comprender las ventajas y desventajas de estos tres tipos de libros de trabajo, el método específico a utilizar depende de la situación:
generalmente hago análisis y selección en función de las siguientes situaciones:
1. Cuando a menudo importamos y exportamos datos no más de 7w en el
En el caso
de Checked 7w, necesitamos operar encabezados, estilos, fórmulas, etc. en Excel. En este momento, podemos usar XSSFWorkbook para realizar consultas por lotes y escribir en Excel en lotes;

3. Millones de importaciones y exportaciones de datos

Si queremos resolver un problema, primero debemos entender ¿cuál es el problema que encontramos?
1. La cantidad de datos que encontré es extremadamente grande. Usar el método tradicional de PDI para completar la importación y exportación obviamente causará un desbordamiento de la memoria y la eficiencia será muy baja; 2. Si la cantidad de datos es grande, use directamente select *
from tableName definitivamente no funcionará. Se puede descubrir de inmediato. Exportar 3 millones de datos definitivamente será muy lento;
3. Al exportar 3 millones de datos a Excel, definitivamente no puedes escribirlos todos en una hoja, lo que ser muy ineficiente, se estima que tomará varios minutos para abrirse 4. Exportar 3 millones de
datos a Excel definitivamente no se puede hacer línea por línea Exportar a Excel. Las operaciones IO frecuentes definitivamente no funcionarán;
5. Al importar, almacenar 3 millones de datos en la base de datos definitivamente no funcionará si los inserta uno por uno en un bucle;
6. Al importar 3 millones de datos, si usa la inserción por lotes de Mybatis, Definitivamente no funcionará, porque la inserción por lotes de Mybatis es en realidad un bucle SQL; también es muy lento.
Idea de solución:
Para 1:
De hecho, el problema es el desbordamiento de la memoria. Solo necesitamos usar el método de PDI presentado anteriormente. El problema principal es que el PDI nativo es bastante problemático de resolver.
Después de revisar la información, encontré EasyExcel, una herramienta de empaquetado de puntos de interés de Alibaba, y los problemas anteriores están esperando ser resueltos;
para 2:
no se pueden consultar todos los datos a la vez, podemos consultar en lotes, pero el problema es consultar varias veces, y hay muchos complementos de paginación en el mercado. Este problema es fácil de resolver.
Objetivo 3:
Puede escribir 3 millones de datos en diferentes Hojas y solo escribir 1 millón para cada Hoja.
Respecto a 4:
No es posible escribir datos en Excel fila por fila, podemos escribir datos de consultas por lotes en Excel en lotes.
Para 5:
Al importar a la base de datos, podemos almacenar los datos leídos en Excel en una colección y, cuando alcanzan una cierta cantidad, se pueden insertar directamente en la base de datos en lotes.
Para 6:
no puede usar la inserción por lotes de Mybatis, podemos usar la inserción por lotes de JDBC y cooperar con las transacciones para completar la inserción por lotes en la base de datos. Es decir, Excel lee lotes + inserciones de lotes JDBC + transacciones.

3.1 Simular la exportación de 5 millones de datos

Requisito: utilice EasyExcel para completar la exportación de 5 millones de datos.
Ideas de soluciones para exportar 5 millones de datos:

  • Primero, en el nivel de la base de datos de consultas, las consultas deben realizarse en lotes (por ejemplo, 200.000 por consulta).
  • Cada vez que se completa una consulta, utilice la herramienta EasyExcel para escribir los datos una vez;
  • Cuando una Hoja esté llena con 1 millón de datos, comience a escribir los datos consultados en otra Hoja;
  • Este ciclo continúa hasta que todos los datos se exportan a Excel.
    PD: Necesitamos calcular la cantidad de hojas y la cantidad de escrituras en bucle. Especialmente la cantidad de escrituras en la última hoja.
    Debido a que no sabe cuántos datos se escribirán en la última hoja, puede ser 1 millón o 25 W. Debido a que los 5 millones que tenemos aquí son solo datos simulados, los datos exportados pueden ser más o menos que 5 millones. PD: Necesitamos calcular el número de escrituras
    . Debido a que utilizamos consultas de paginación, debemos prestar atención al número de escrituras.
    De hecho, la cantidad de veces que consulta la base de datos es la cantidad de veces que escribe.
    Trabajo de preparación
    1. Cree un proyecto springboot basado en maven e introduzca las dependencias de easyexcel. Aquí estoy usando la versión 3.0
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>easyexcel</artifactId>
   <version>3.0.5</version>
</dependency>

2. Cree scripts SQL para datos masivos

CREATE TABLE dept( /*部门表*/
deptno MEDIUMINT   UNSIGNED  NOT NULL  DEFAULT 0,
dname VARCHAR(20)  NOT NULL  DEFAULT "",
loc VARCHAR(13) NOT NULL DEFAULT ""
) ;

#创建表EMP雇员
CREATE TABLE emp
(empno  MEDIUMINT UNSIGNED  NOT NULL  DEFAULT 0, /*编号*/
ename VARCHAR(20) NOT NULL DEFAULT "", /*名字*/
job VARCHAR(9) NOT NULL DEFAULT "",/*工作*/
mgr MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,/*上级编号*/
hiredate DATE NOT NULL,/*入职时间*/
sal DECIMAL(7,2)  NOT NULL,/*薪水*/
comm DECIMAL(7,2) NOT NULL,/*红利*/
deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 /*部门编号*/
) ;

#工资级别表
CREATE TABLE salgrade
(
grade MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
losal DECIMAL(17,2)  NOT NULL,
hisal DECIMAL(17,2)  NOT NULL
);

#测试数据
INSERT INTO salgrade VALUES (1,700,1200);
INSERT INTO salgrade VALUES (2,1201,1400);
INSERT INTO salgrade VALUES (3,1401,2000);
INSERT INTO salgrade VALUES (4,2001,3000);
INSERT INTO salgrade VALUES (5,3001,9999);

delimiter $$

#创建一个函数,名字 rand_string,可以随机返回我指定的个数字符串
create function rand_string(n INT)
returns varchar(255) #该函数会返回一个字符串
begin
#定义了一个变量 chars_str, 类型  varchar(100)
#默认给 chars_str 初始值   'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'
 declare chars_str varchar(100) default
   'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ'; 
 declare return_str varchar(255) default '';
 declare i int default 0; 
 while i < n do
    # concat 函数 : 连接函数mysql函数
   set return_str =concat(return_str,substring(chars_str,floor(1+rand()*52),1));
   set i = i + 1;
   end while;
  return return_str;
  end $$


 #这里我们又自定了一个函数,返回一个随机的部门号
create function rand_num( )
returns int(5)
begin
declare i int default 0;
set i = floor(10+rand()*500);
return i;
end $$

 #创建一个存储过程, 可以添加雇员
create procedure insert_emp(in start int(10),in max_num int(10))
begin
declare i int default 0;
#set autocommit =0 把autocommit设置成0
 #autocommit = 0 含义: 不要自动提交
 set autocommit = 0; #默认不提交sql语句
 repeat
 set i = i + 1;
 #通过前面写的函数随机产生字符串和部门编号,然后加入到emp表
 insert into emp values ((start+i) ,rand_string(6),'SALESMAN',0001,curdate(),2000,400,rand_num());
  until i = max_num
 end repeat;
 #commit整体提交所有sql语句,提高效率
   commit;
 end $$

 #添加8000000数据
call insert_emp(100001,8000000)$$

#命令结束符,再重新设置为;
delimiter ;

3. clase de entidad

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp implements Serializable {
    
    
    @ExcelProperty(value = "员工编号")
    private Integer empno;

    @ExcelProperty(value = "员工名称")
    private String ename;

    @ExcelProperty(value = "工作")
    private String job;

    @ExcelProperty(value = "主管编号")
    private Integer mgr;

    @ExcelProperty(value = "入职日期")
    private Date hiredate;

    @ExcelProperty(value = "薪资")
    private BigDecimal sal;

    @ExcelProperty(value = "奖金")
    private BigDecimal comm;

    @ExcelProperty(value = "所属部门")
    private Integer deptno;

}

clase 4.vo

@Data
public class EmpVo {
    
    

    @ExcelProperty(value = "员工编号")
    private Integer empno;

    @ExcelProperty(value = "员工名称")
    private String ename;

    @ExcelProperty(value = "工作")
    private String job;

    @ExcelProperty(value = "主管编号")
    private Integer mgr;

    @ExcelProperty(value = "入职日期")
    private Date hiredate;

    @ExcelProperty(value = "薪资")
    private BigDecimal sal;

    @ExcelProperty(value = "奖金")
    private BigDecimal comm;

    @ExcelProperty(value = "所属部门")
    private Integer deptno;

}

Exportar código central

@Resource
private EmpService empService;
/**
 * 分批次导出
 */
@GetMapping("/export")
public void export() throws IOException {
    
    
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    empService.export();
    stopWatch.stop();
    System.out.println("共计耗时: " + stopWatch.getTotalTimeSeconds()+"S");
}
public class ExcelConstants {
    
    
 //一个sheet装100w数据
    public static final Integer PER_SHEET_ROW_COUNT = 1000000;
    //每次查询20w数据,每次写入20w数据
    public static final Integer PER_WRITE_ROW_COUNT = 200000;
}
@Override
public void export() throws IOException {
    
    
    OutputStream outputStream =null;
    try {
    
    
        //记录总数:实际中需要根据查询条件进行统计即可
        //LambdaQueryWrapper<Emp> lambdaQueryWrapper = new QueryWrapper<Emp>().lambda().eq(Emp::getEmpno, 1000001);
        Integer totalCount = empMapper.selectCount(null);
        //每一个Sheet存放100w条数据
        Integer sheetDataRows = ExcelConstants.PER_SHEET_ROW_COUNT;
        //每次写入的数据量20w,每页查询20W
        Integer writeDataRows = ExcelConstants.PER_WRITE_ROW_COUNT;
        //计算需要的Sheet数量
        Integer sheetNum = totalCount % sheetDataRows == 0 ? (totalCount / sheetDataRows) : (totalCount / sheetDataRows + 1);
        //计算一般情况下每一个Sheet需要写入的次数(一般情况不包含最后一个sheet,因为最后一个sheet不确定会写入多少条数据)
        Integer oneSheetWriteCount = sheetDataRows / writeDataRows;
        //计算最后一个sheet需要写入的次数
        Integer lastSheetWriteCount = totalCount % sheetDataRows == 0 ? oneSheetWriteCount : (totalCount % sheetDataRows % writeDataRows == 0 ? (totalCount / sheetDataRows / writeDataRows) : (totalCount / sheetDataRows / writeDataRows + 1));

        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletResponse response = requestAttributes.getResponse();
        outputStream = response.getOutputStream();
        //必须放到循环外,否则会刷新流
        ExcelWriter excelWriter = EasyExcel.write(outputStream).build();

        //开始分批查询分次写入
        for (int i = 0; i < sheetNum; i++) {
    
    
            //创建Sheet
            WriteSheet sheet = new WriteSheet();
            sheet.setSheetName("测试Sheet1"+i);
            sheet.setSheetNo(i);
            //循环写入次数: j的自增条件是当不是最后一个Sheet的时候写入次数为正常的每个Sheet写入的次数,如果是最后一个就需要使用计算的次数lastSheetWriteCount
            for (int j = 0; j < (i != sheetNum - 1 ? oneSheetWriteCount : lastSheetWriteCount); j++) {
    
    
                //分页查询一次20w
                Page<Emp> page = empMapper.selectPage(new Page(j + 1 + oneSheetWriteCount * i, writeDataRows), null);
                List<Emp> empList = page.getRecords();
                List<EmpVo> empVoList = new ArrayList<>();
                for (Emp emp : empList) {
    
    
                    EmpVo empVo = new EmpVo();
                    BeanUtils.copyProperties(emp, empVo);
                    empVoList.add(empVo);
                }
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "员工信息" + (i + 1)).head(EmpVo.class)
                        .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).build();
                //写数据
                excelWriter.write(empVoList, writeSheet);
            }
        }
        // 下载EXCEL
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止浏览器端导出excel文件名中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode("员工信息", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        excelWriter.finish();
        outputStream.flush();
    } catch (IOException e) {
    
    
        e.printStackTrace();
    } catch (BeansException e) {
    
    
        e.printStackTrace();
    }finally {
    
    
        if (outputStream != null) {
    
    
            outputStream.close();
        }
    }
}

Este es el uso de memoria y de CPU de mi computadora durante la prueba. Por supuesto, se abrieron algunas otras aplicaciones.
Insertar descripción de la imagen aquí
Se puede considerar que el tiempo total necesario para exportar 5 millones de datos es de aproximadamente 400 segundos. Por supuesto, también se deben considerar la complejidad del negocio y la configuración de la computadora. Solo estoy exportando una demostración aquí y no involucra otra lógica de negocios. En el desarrollo real , puede llevar más tiempo que esto. Algunos.
Insertar descripción de la imagen aquí
Eche un vistazo al efecto de exportación. Mi script anterior insertó 5 millones de datos en la hoja de 1 millón, por lo que hay exactamente cinco.
Insertar descripción de la imagen aquí

3.2 Simular la importación de 5 millones de datos

Ideas de soluciones para importar datos de 500W

1. Primero, lea 5 millones de datos en Excel en lotes. EasyExcel tiene su propia solución para esto. Podemos consultar la demostración. Solo necesitamos aumentar su parámetro de lote 5000.
2. El segundo paso es insertar los 200.000 datos en la base de datos. ¿Cómo insertar estos 200.000 datos? Por supuesto, no puede realizar un bucle uno por uno. Debe insertar estos 200.000 datos en lotes. Tampoco puede Utilice el lenguaje de inserción por lotes de Mybatis porque la eficiencia también es baja.
3. Utilice operaciones por lotes de transacciones JDBC+ para insertar datos en la base de datos. (Lectura por lotes + inserción por lotes JDBC + control manual de transacciones)
El código implementa
la interfaz de prueba de la capa del controlador

@Resource
private EmpService empService;

@GetMapping("/importData")
public void importData() {
    
    
    String fileName = "C:\\Users\\asus\\Desktop\\员工信息.xlsx";
    //记录开始读取Excel时间,也是导入程序开始时间
    long startReadTime = System.currentTimeMillis();
    System.out.println("------开始读取Excel的Sheet时间(包括导入数据过程):" + startReadTime + "ms------");
    //读取所有Sheet的数据.每次读完一个Sheet就会调用这个方法
    EasyExcel.read(fileName, new EasyExceGeneralDatalListener(empService)).doReadAll();
    long endReadTime = System.currentTimeMillis();
    System.out.println("------结束读取Excel的Sheet时间(包括导入数据过程):" + endReadTime + "ms------");
    System.out.println("------读取Excel的Sheet时间(包括导入数据)共计耗时:" + (endReadTime-startReadTime) + "ms------");
}

Monitoreo de eventos de importación de Excel

// 事件监听
public class EasyExceGeneralDatalListener extends AnalysisEventListener<Map<Integer, String>> {
    
    
    /**
     * 处理业务逻辑的Service,也可以是Mapper
     */
    private EmpService empService;

    /**
     * 用于存储读取的数据
     */
    private List<Map<Integer, String>> dataList = new ArrayList<Map<Integer, String>>();

    public EasyExceGeneralDatalListener() {
    
    
    }

    public EasyExceGeneralDatalListener(EmpService empService) {
    
    
        this.empService = empService;
    }

    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
    
    
        //数据add进入集合
        dataList.add(data);
        //size是否为100000条:这里其实就是分批.当数据等于10w的时候执行一次插入
        if (dataList.size() >= ExcelConstants.GENERAL_ONCE_SAVE_TO_DB_ROWS) {
    
    
            //存入数据库:数据小于1w条使用Mybatis的批量插入即可;
            saveData();
            //清理集合便于GC回收
            dataList.clear();
        }
    }

    /**
     * 保存数据到DB
     *
     * @param
     * @MethodName: saveData
     * @return: void
     */
    private void saveData() {
    
    
        empService.importData(dataList);
        dataList.clear();
    }

    /**
     * Excel中所有数据解析完毕会调用此方法
     *
     * @param: context
     * @MethodName: doAfterAllAnalysed
     * @return: void
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
    
    
        saveData();
        dataList.clear();
    }
}

Código comercial central

public interface EmpService {
    
    
    void export() throws IOException;

    void importData(List<Map<Integer, String>> dataList);

}
    /*
     * 测试用Excel导入超过10w条数据,经过测试发现,使用Mybatis的批量插入速度非常慢,所以这里可以使用 数据分批+JDBC分批插入+事务来继续插入速度会非常快
    */
    @Override
    public void importData(List<Map<Integer, String>> dataList) {
    
    
        //结果集中数据为0时,结束方法.进行下一次调用
        if (dataList.size() == 0) {
    
    
            return;
        }
        //JDBC分批插入+事务操作完成对20w数据的插入
        Connection conn = null;
        PreparedStatement ps = null;
        try {
    
    
            long startTime = System.currentTimeMillis();
            System.out.println(dataList.size() + "条,开始导入到数据库时间:" + startTime + "ms");
            conn = JDBCDruidUtils.getConnection();
            //控制事务:默认不提交
            conn.setAutoCommit(false);
            String sql = "insert into emp (`empno`, `ename`, `job`, `mgr`, `hiredate`, `sal`, `comm`, `deptno`) values";
            sql += "(?,?,?,?,?,?,?,?)";
            ps = conn.prepareStatement(sql);
            //循环结果集:这里循环不支持lambda表达式
            for (int i = 0; i < dataList.size(); i++) {
    
    
                Map<Integer, String> item = dataList.get(i);
                ps.setString(1, item.get(0));
                ps.setString(2, item.get(1));
                ps.setString(3, item.get(2));
                ps.setString(4, item.get(3));
                ps.setString(5, item.get(4));
                ps.setString(6, item.get(5));
                ps.setString(7, item.get(6));
                ps.setString(8, item.get(7));
                //将一组参数添加到此 PreparedStatement 对象的批处理命令中。
                ps.addBatch();
            }
            //执行批处理
            ps.executeBatch();
            //手动提交事务
            conn.commit();
            long endTime = System.currentTimeMillis();
            System.out.println(dataList.size() + "条,结束导入到数据库时间:" + endTime + "ms");
            System.out.println(dataList.size() + "条,导入用时:" + (endTime - startTime) + "ms");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            //关连接
            JDBCDruidUtils.close(conn, ps);
        }
    }

}

clase de herramienta jdbc

//JDBC工具类
public class JDBCDruidUtils {
    
    
    private static DataSource dataSource;

    /*
   创建数据Properties集合对象加载加载配置文件
    */
    static {
    
    
        Properties pro = new Properties();
        //加载数据库连接池对象
        try {
    
    
            //获取数据库连接池对象
            pro.load(JDBCDruidUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
            dataSource = DruidDataSourceFactory.createDataSource(pro);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    /*
    获取连接
     */
    public static Connection getConnection() throws SQLException {
    
    
        return dataSource.getConnection();
    }


    /**
     * 关闭conn,和 statement独对象资源
     *
     * @param connection
     * @param statement
     * @MethodName: close
     * @return: void
     */
    public static void close(Connection connection, Statement statement) {
    
    
        if (connection != null) {
    
    
            try {
    
    
                connection.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
        if (statement != null) {
    
    
            try {
    
    
                statement.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    /**
     * 关闭 conn , statement 和resultset三个对象资源
     *
     * @param connection
     * @param statement
     * @param resultSet
     * @MethodName: close
     * @return: void
     */
    public static void close(Connection connection, Statement statement, ResultSet resultSet) {
    
    
        close(connection, statement);
        if (resultSet != null) {
    
    
            try {
    
    
                resultSet.close();
            } catch (SQLException e) {
    
    
                e.printStackTrace();
            }
        }
    }

    /*
    获取连接池对象
     */
    public static DataSource getDataSource() {
    
    
        return dataSource;
    }

}

archivo de configuración druid.properties

Aquí creo el archivo en la ruta de clases. Cabe señalar que al conectarse a la base de datos MySQL, debe especificar rewriteBatchedStatements=true para que el procesamiento por lotes surta efecto. De lo contrario, la eficiencia de insertar uno por uno será baja. enableMultiQueries = true significa que puede haber múltiples inserciones en la declaración SQL o actualizar la declaración (con punto y coma entre declaraciones), que se pueden ignorar aquí.

# druid.properties配置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/llp?autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8&allowMultiQueries=true&rewriteBatchedStatements=true
username=root
password=root
initialSize=10
maxActive=50
maxWait=60000

Resultados de la prueba

------开始读取Excel的Sheet时间(包括导入数据过程):1674181403555ms------
200000条,开始导入到数据库时间:1674181409740ms
2023-01-20 10:23:29.943  INFO 18580 --- [nio-8888-exec-1] com.alibaba.druid.pool.DruidDataSource   : {
    
    dataSource-1} inited
200000条,结束导入到数据库时间:1674181413252ms
200000条,导入用时:3512ms
200000条,开始导入到数据库时间:1674181418422ms
200000条,结束导入到数据库时间:1674181420999ms
200000条,导入用时:2577ms
.....
200000条,开始导入到数据库时间:1674181607405ms
200000条,结束导入到数据库时间:1674181610154ms
200000条,导入用时:2749ms
------结束读取Excel的Sheet时间(包括导入数据过程):1674181610155ms------
------读取Excel的Sheet时间(包括导入数据)共计耗时:206600ms------

Aquí elimino algunos de los registros. Como puede ver en los resultados de la impresión, se necesitan más de 200 segundos para importar 5 millones de datos en mi computadora. Por supuesto, la lógica empresarial de la empresa es muy compleja, la cantidad de datos es relativamente grande y hay muchos campos en la tabla, por lo que la velocidad de importación y exportación será un poco más lenta que en la prueba actual.

4. Resumen

1. Las operaciones de exportación e importación de una cantidad tan grande de datos ocuparán una gran cantidad de memoria y, en el desarrollo real, el número de operadores debe ser limitado.
2. Al importar grandes lotes de datos, puede utilizar jdbc para abrir transacciones manualmente y enviarlas en lotes.

Supongo que te gusta

Origin blog.csdn.net/houxian1103/article/details/132380410
Recomendado
Clasificación