java jdbc导出数据库大量数据

背景

java 导出数据库数据,在数据量比较大的情况下,如果全部将数据读到内存中再执行写文件,很容易遇到内存溢出;为了避免内存溢出的问题,使用游标的方式,边读边写;

分页的方式,当数据量较大的情况下,需要花费一些建立数据库连接的消耗,也不是很推荐,所以可以尝试使用游标的方式;

数据准备

BEGIN
	FOR i IN 1..2000000
	LOOP
-- 	插入数据SQL
	INSERT INTO ......
-- 	
IF
	i MOD 10000 = 0 THEN
	COMMIT;

END IF;

END LOOP;
END;

通过循环的方式插入200W数据,为了提高插入效率,1万条提交一次;

导出数据方法

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.*;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;

@Slf4j
public class DataExportTest {


    public static final String URL = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
    public static final String DRIVER = "oracle.jdbc.OracleDriver";
    public static final String PASSWORD = "youdasi";
    public static final String USERNAME = "youdasi";

    @Test
    public void exportTest() throws Exception {

        //初始化数据库连接
        Connection connection = initConnection();
        Statement statement = connection.createStatement();

        //FETCH_SIZE 的大小视具体情况而定
        statement.setFetchSize(1000);

        ResultSet resultSet = statement.executeQuery("SELECT * FROM TEST");
        ResultSetMetaData metaData = resultSet.getMetaData();
        
        long begin = System.currentTimeMillis();
        //数据库每行数据
        StringBuilder rowData = new StringBuilder();

        //单次写入文件的数据(可以删掉)
        List<String> pageData = new LinkedList<String>();

        //result 为数据库
        int j = 0;
        int num = metaData.getColumnCount();
        while (resultSet.next()){
            j++;
            //组装单行数据
            for (int i = 1; i< num ; i++){
                Object object = resultSet.getObject(i);
                if (null != object){
                    rowData.append(object.toString());
                }
                rowData.append("|");
            }

            pageData.add(rowData.toString());//添加到写文件集合
            rowData.setLength(0);//清空行数据

            //10W条数据写一次,视情况而定
            if (j % 100000 == 0){
                log.info(j + "");
                writeFile(pageData,"C:/temp/dat/test.dat");//写数据
                pageData.clear(); //清空list
            }
        }

        if (j % 100000 != 0){
            writeFile(pageData,"C:/temp/dat/test.dat");//写数据
        }

        long end = System.currentTimeMillis();
        log.info("time: {}",end - begin);
    }


    /**
     * 初始化数据库连接
     */
    private Connection initConnection(){

        Connection connection = null;

        try {
            Class.forName(DRIVER);
            connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
        } catch (ClassNotFoundException e) {
            log.error("error load jdbc driver");
        } catch (SQLException e) {
            log.error("error get connection");
        }
        return connection;
    }

    /**
     * 写数据到文件
     */
    private void writeFile(List<String> data,String path) throws IOException {

        if (null == data || data.size() == 0){
            return;
        }

        File file = new File(path);

        if (!file.exists()){
            file.createNewFile();
        }

        FileOutputStream fileOutputStream = new FileOutputStream(path, true);
        OutputStreamWriter write = new OutputStreamWriter(fileOutputStream, "UTF-8");
        BufferedWriter writer = new BufferedWriter(write);
        for (String str:data){
            writer.write(str);
            writer.flush();
        }
        writer.close();
        write.close();
        fileOutputStream.close();
    }

}

代码中,有下面几个地方可以思考下:

  • FETCH_SIZE 取多大合适,我本地虚拟机docker起的ORACLE数据库,测试出1000比较合适,但是还是需要自己测试选择合适的;
  • PAGE_SIZE 取多少合适,也是需要视情况而定
  • 可以看出代码中有List pageData,这个其实可以直接省略掉,只用一个String,这样做其实只是让逻辑看起来清楚些,但是损耗了性能
  • 写文件的方法,每次新建了流再关闭,其实可以使用一个流,每次flush,最后一次再关闭就可以了,这样也可以提升部分效率
  • 代码中存在字符串的拼接,这个其实可以放到SQL中来做,这样就把字符拼接的工作从程序转移到数据库,至于哪个更好,不确定
SELECT '字段名'||'字段名' AS RES_STR FROM '表名' WHERE ...;
  • 游标读取数据的时候,有用map封装的情况,改成数组效率会高很多,比如
        List<Map<Integer, String>> bufferData = new LinkedList<>();
            int rowNum = 0;
            while (resultSet.next()) {
                rowNum++;
                Map<Integer, String> rowData = new HashMap<>(num);
                for (int i = 1; i < num; i++) {
                    rowData.put(i, resultSet.getString(i));
                }
                bufferData.add(rowData);

            }

改为

            List<String[]> bufferData = new LinkedList<>();
            int rowNum = 0;
            while (resultSet.next()) {
                String[] rowData = new String[num];
                rowNum++;
                for (int i = 1; i < num; i++) {
                    rowData[i] = resultSet.getString(i);
                }
                bufferData.add(rowData);

            }

最终运行结果

  • 导出纪录数:200W
  • 导出文件大小:544 MB
  • 耗时 :29079ms=29s

代码

https://gitee.com/youdasixls/jdbc_export

猜你喜欢

转载自blog.csdn.net/qasxcvgh/article/details/83719520