java使用jdbcTemplate查询并插入百万级数据解决方案

这篇具有很好参考价值的文章主要介绍了java使用jdbcTemplate查询并插入百万级数据解决方案。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景:使用JdbcTemplate查询500万数据,然后插入到数据库。

这么多的数据按照普通的方式直接查询然后插入,服务器肯定会挂掉,我尝试过使用分页查询的方式去进行分批查询插入,虽然也能达到保证服务器不挂掉的效果,但是有一个严重的问题,每次查询的数据很难保证顺序性,第一次一查询的数据可能又出现在第N次的查询结果中,虽然可以通过在查询sql中加上排序,可以保证多次查询的顺序不变,但是这种分页查询方式还是不够严谨,因为在多次查询过程中,可能数据有新增或删除,即使保证了排序唯一性,也会导致数据少取或取重复问题。

这个过程中需要解决的问题:

一、内存溢出

使用jdbcTemplate.queryForList查询一次读取500万条数据,会占用大量内存,一般的服务器都会内存溢出报错,jdbcTemplate默认使用RowMapperResultSetExtractor来处理ResultSet结果集,会将数据全部读取到内存:

java查询500万条数据,java,开发语言

java查询500万条数据,java,开发语言

因此我们需要自己写一个实现类继承ResultSetExtractor,去实现读取ResultSet的逻辑。

一、批量插入速度慢

我们使用jdbcTemplate的batchUpdate方法批量保存数据时,要想真正进行批量保存需要几个条件

1.首先要数据库本身要支持批量更新,一般主流数据库都会支持。

2.插入的sql语句不要使用子查询

插入语句只使用insert into table() values()这种,不要在values中使用select语句

3.数据源连接设置rewriteBatchedStatements=true这个参数

在oracle驱动中rewriteBatchedStatements参数默认是开启的,mysql没有开启,需要在数据源url连接中手动设置:

java查询500万条数据,java,开发语言

 自定义ResultSetExtractor如下:

package com.zhou.db.model;


import com.zhou.db.util.SqlUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.support.JdbcUtils;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 查询数据自定义处理ResultSet
 * @author lang.zhou
 * @since 2023/1/9 17:42
 */
@Slf4j
public abstract class DataMapCallBackExtractor implements ResultSetExtractor<List<Map<String,Object>>> {

    /**
     * 每次读取10000条时开始插入
     */
    @Getter
    private int batchQuerySize = 1000;

    @Getter
    private List<DbColumn> columnList  = new ArrayList<>(0);

    /**
     * 数据条数
     */
    @Getter
    private int dataCount = 0;

    public DataMapCallBackExtractor() {
    }

    public DataMapCallBackExtractor(int batchQuerySize) {
        if(batchQuerySize > 1000){
            this.batchQuerySize = batchQuerySize;
        }
    }

    @Override
    public List<Map<String,Object>> extractData(ResultSet rs) throws SQLException, DataAccessException {
        ResultSetMetaData resultSetMetaData = rs.getMetaData();
        //结果集列数
        int count = resultSetMetaData.getColumnCount();
        //已经执行回调的次数
        int times = 0;
        //读取列信息
        for (int i = 1; i < count + 1; i++) {
            columnList.add(SqlUtil.readResultColumn(resultSetMetaData,i));
        }
        //读取列信息后回调
        this.columnInfoCallback(columnList);
        List<Map<String, Object>> list = new ArrayList<>();
        while(rs.next()){
            //总条数增加
            dataCount ++;
            Map<String, Object> e = new LinkedHashMap<>(count);
            //读取这一行的数据
            for (int i = 1; i < count + 1; i++) {
                e.putIfAbsent(JdbcUtils.lookupColumnName(resultSetMetaData, i), JdbcUtils.getResultSetValue(rs, i));
            }
            list.add(e);
            //读取满10000条时开始插入数据
            if(list.size() >= batchQuerySize){
                times ++;

                this.dataCallback(list,times,dataCount);
                //处理完成清空已读取的数据,释放内存
                list.clear();
            }
        }
        //可能最后一次读取不满10000条,插入剩余的数据
        if(list.size() > 0){
            times ++;
            this.dataCallback(list,times,dataCount);
            list.clear();
        }
        return new ArrayList<>(0);
    }

    /**
     * 读取batchQuerySize条数据后自定义处理回调
     */
    public abstract void dataCallback(List<Map<String, Object>> list, int times, int n);

    /**
     * 读取列信息后回调
     */
    public void columnInfoCallback(List<DbColumn> columnList){

    }

}

 我们拿到ResultSet后,每次只读取10000条数据存到List中,然后将这些数据插入数据库,在插入结束之后清空这个List,jvm会回收这些数据释放内存,一直重复这个过程直到结果集读取完毕,这样能保证内存中只流程10000条数据,就避免了内存泄漏的情况产生。

分批插入代码,提升插入速度:

    /**
     * 数据分批插入
     */
    public void batchSizeUpdate(List<Map<String,Object>> list, String sql,NamedParameterJdbcTemplate namedParameterJdbcTemplate, int batchSize){
        int size = list.size();
        int n = size / batchSize;
        int l = size % batchSize;
        if(l > 0){
            n++;
        }
        log.info("总共分"+n+"次插入");
        for (int i = 0; i < n; i++) {
            int start = i*batchSize;
            int end = (i+1)*batchSize;
            if(end > size){
                end = size;
            }
            batchUpdate(list.subList(start,end),sql, namedParameterJdbcTemplate);
            log.info("第"+(i+1)+"次插入完毕");
        }
    }

    private void batchUpdate(List<Map<String,Object>> list, String sql,NamedParameterJdbcTemplate namedParameterJdbcTemplate){
        Map<String,?> [] param = new Map[list.size()];
        for(int c= 0;c<list.size();c++){
            param[c] = list.get(c);
        }
        namedParameterJdbcTemplate.batchUpdate(sql,param);
    }

 最终调用方式:

    //一次读取10000条后进行回调
    DataMapCallBackExtractor extractor = new DataMapCallBackExtractor(10000){
        @Override
        public void dataCallback(List<Map<String, Object>> list, int times, int n) {
            log.info("第{}次读取{}条,共{}条",times,list.size(),n);
            //分批插入,一次1000条
            batchSizeUpdate(list,insertSql,insertJdbcTemplate);
        }

        @Override
        public void columnInfoCallback(List<DbColumn> columnList) {
            //读取结果集之前回调,拿到列信息进行一些处理
            //比如拼接插入sql
        }
    };
    jdbcTemplate.query(sql, new HashMap<>(0),extractor);

 SqlUtil中读取列信息的代码:

    /**
     * 从ResultSet中读取sql列信息
     * @param rs    结果集
     * @param i     列位置
     */
    @SneakyThrows
    public static DbColumn readResultColumn(ResultSetMetaData rs,int i){
        DbColumn c = new DbColumn();
        c.setName(rs.getColumnName(i));
        c.setComments(rs.getColumnLabel(i));
        int type = rs.getColumnType(i);
        c.setDataType(rs.getColumnTypeName(i));

        c.setNullable(rs.isNullable(i) == ResultSetMetaData.columnNoNulls ? "N" : "Y");
        if(type == Types.VARCHAR || type == Types.CHAR || type == Types.LONGVARCHAR || type == Types.CLOB){
            c.setDataLength(rs.getColumnDisplaySize(i));
        }else if(type == Types.NUMERIC || type == Types.INTEGER || type == Types.BIGINT || type == Types.DECIMAL
                || type == Types.DOUBLE || type == Types.FLOAT || type == Types.REAL || type == Types.SMALLINT || type == Types.TINYINT){
            c.setDataLength(rs.getPrecision(i));
        }else if(type == Types.DATE || type == Types.TIMESTAMP){
            c.setDataLength(rs.getPrecision(i));
        }
        c.setDataScale(rs.getScale(i));
        return c;
    }

字段列信息实体:文章来源地址https://www.toymoban.com/news/detail-768619.html


import lombok.Data;

import java.util.Objects;

/**
 * 数据库表字段基本信息
 * @author lang.zhou
 * @since 2022/10/17 14:31
 */
@Data
public class DbColumn {

    private String tableName;
    /**
     * 字段名
     */
    private String name;
    /**
     * 字段描述
     */
    private String comments;
    /**
     * 可为空
     */
    private String nullable = "Y";
    private String dataType = null;
    private Integer isPk = 0;
    private Integer dataLength = 0;
    private Integer dataScale = 0;
    public boolean isPk(){
        return name != null && isPk > 0;
    }
    public boolean isDate(){
        return name != null && ("DATE".equalsIgnoreCase(dataType) || "TIMESTAMP".equalsIgnoreCase(dataType) || "DATETIME".equalsIgnoreCase(dataType));
    }
    public boolean isNumber(){
        return name != null && ("NUMBER".equalsIgnoreCase(dataType) || "DECIMAL".equalsIgnoreCase(dataType) || "INTEGER".equalsIgnoreCase(dataType)
                || "INT".equalsIgnoreCase(dataType)|| "BIGINT".equalsIgnoreCase(dataType)|| "DOUBLE".equalsIgnoreCase(dataType)|| "LONG".equalsIgnoreCase(dataType)));
    }
    public boolean isChar(){
        return name != null && "CHAR".equalsIgnoreCase(dataType);
    }
    public boolean allowNull(){
        return !isPk() && Objects.equals(nullable,"Y");
    }
}

到了这里,关于java使用jdbcTemplate查询并插入百万级数据解决方案的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • QT 实现百万级的数据显示内存消耗几十兆

    用QT 开发了一个上位机的工具用来解析串口的数据,数据量比较大 ,如果QT tableview 控件完全显示,内存消耗较大,所以解析结果先建立sql 数据索引,然后通过垂直滚动条的变化动态地获取数据,每次从数据库中提取50条,测试下来内存消耗较小,可以实现百万或者千万级的

    2024年02月11日
    浏览(29)
  • E往无前 | 海量数据ES 扩展难?腾讯云大数据ES 扩展百万级分片也“So Easy~”

    《E往无前》系列将着重展现腾讯云ES在持续深入优化客户所关心的「省!快!稳!」诉求,能够在低成本的同时兼顾高可用、高性能、高稳定等特性,可以满足微盟、小红书、微信支付等内外部大客户的核心场景需求。 E往无前 | 海量数据ES扩展难?腾讯云ES 扩展百万级分片

    2024年02月06日
    浏览(74)
  • CTO:给我一个SpringBoot实现MySQL百万级数据量导出并避免OOM的解决方案

    动态数据导出是一般项目都会涉及到的功能。它的基本实现逻辑就是从mysql查询数据,加载到内存,然后从内存创建excel或者csv,以流的形式响应给前端。 参考:https://grokonez.com/spring-framework/spring-boot/excel-file-download-from-springboot-restapi-apache-poi-mysql。 SpringBoot下载excel基本都是这

    2023年04月13日
    浏览(39)
  • MySQL 百万级/千万级表 全量更新

    业务需求:今天从生成测试环境迁移了一批百万级/千万级表的数据,领导要求将这批数据进行脱敏处理(将真实姓名 、电话、邮箱、身份证号等敏感信息进行替换)。迁移数据记录数如下(小于百万级的全量更新不是本文重点): 表名 表名含义 行记录数 base_house 房屋表 42

    2024年02月05日
    浏览(31)
  • TCP服务器的演变过程:使用epoll构建reactor网络模型实现百万级并发(详细代码)

    手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。 为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。 本节,在上一章节介绍了如何使用epoll开发高效的服务器,本节将介绍使用epoll构建reactor网络模型,实

    2024年02月01日
    浏览(54)
  • SpringBoot高效批量插入百万数据

    SpringBoot高效批量插入百万数据 前言:我相信很多小伙伴和我一样在初学的时候,面对几万几十万数据插入,不知道如何下手,面对百万级别数据时更是不知所措,我们大部分初学者,很多人都喜欢for循环插入数据,或者是开启多个线程,然后分批使用for循环插入,当我们需

    2024年04月18日
    浏览(25)
  • 美团面试:Kafka如何处理百万级消息队列?

    在今天的大数据时代,处理海量数据已成为各行各业的标配。特别是在消息队列领域,Apache Kafka 作为一个分布式流处理平台,因其高吞吐量、可扩展性、容错性以及低延迟的特性而广受欢迎。但当面对真正的百万级甚至更高量级的消息处理时,如何有效地利用 Kafka,确保数据

    2024年02月20日
    浏览(26)
  • 同屏实时渲染百万级独立的3D可渲染对象

    大规模渲染在游戏、家装、或者其他生产制造相关的环境下有直接的刚需,能独立渲染的3D对象越多,越容易实现复杂的场景需求。 下图是WebGPU版200多万(2 * 1024 * 1024)个可渲染的3D对象,的实时渲染情况截图。

    2024年02月08日
    浏览(37)
  • 窄带高清技术之百万级并发下的演唱会直播细节修复

    史无前例,高清又不卡。 5月,百视TV联合上海人民广播电台、时代峰峻共同出品的《东方风云榜》,绚烂呈现一场三十周年音乐分享会·时代少年团《理想之途》。有人说,这是一场似梦非梦的记忆。 演唱会由“乐园”、“少年”、“乌托邦”三大篇章组成,精心之作引来社

    2024年02月07日
    浏览(25)
  • 万级数据优化EasyExcel+mybatis流式查询导出封装

    时间 更新内容 2023/09/23 fix: 每个sheet大小和存储内存条数一致的bug update: 增大一个sheet的默认容量 我们不妨先给大家讲一个概念,利用此概念我们正好给大家介绍一个数据库优化的小技巧: 需求如下:将一个地市表的数据导出70万条。 如果你不假思索,直接一条sql语句搞上去

    2024年02月11日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包