当前位置: 首页 > article >正文

用JDBC游标的方式导出mysql数据以及springboot打包成exe程序实践

用JDBC实现游标查询,关键代码在于 Statement 的 fetchSize 属性的设置。

  • ExportDataService
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.csv.CsvUtil;
import cn.hutool.core.text.csv.CsvWriter;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.File;
import java.nio.charset.Charset;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;

@Slf4j
@Service
@RequiredArgsConstructor
public class ExportDataService {

    private final JdbcTemplate jdbcTemplate;

    @Async
    public void exportDataToFile(String sql, String filePath) {
        long beginTime = System.currentTimeMillis();
        // 给文件先加个.ing后缀, 表示数据正在导出
        File file = FileUtil.newFile(filePath + ".ing");
        AtomicInteger readCnt = new AtomicInteger();
        try (CsvWriter csvWriter = CsvUtil.getWriter(file, Charset.defaultCharset())) {
            jdbcTemplate.query(conn -> buildStmt(conn, sql), rs -> {
                handlerRow(rs, csvWriter, readCnt);
            });
            csvWriter.flush();
        }
        log.info("导出完成, 共计: {}, 耗时: {} MS", readCnt.get(), System.currentTimeMillis() - beginTime);
        // 全部导出完成后, 重命名文件, 把.ing后缀去掉
        FileUtil.rename(file, StrUtil.subBefore(file.getName(), ".ing", true), true);
    }
    
    /**
     * 构建预执行SQL模板
     *
     * @param conn 数据连接
     * @param sql  SQL
     * @return java.sql.PreparedStatement
     * @author 敖癸
     * @since 2024/3/15 - 15:37
     */
    private static PreparedStatement buildStmt(Connection conn, String sql) throws SQLException {
        PreparedStatement stmt = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
        // 固定写法, 因为mysql比较特殊, 要开启游标查询, 需要设置fetchSize为Integer.MIN_VALUE。其它数据库根据实际需求来调整大小
        stmt.setFetchSize(Integer.MIN_VALUE);
        stmt.setFetchDirection(ResultSet.FETCH_FORWARD);
        return stmt;
    }

    /** 处理每一行的数据 */
    private static void handlerRow(ResultSet rs, CsvWriter csvWriter, AtomicInteger readCnt) throws SQLException {
        // 获取每一行的字段数量
        int columnCount = rs.getMetaData().getColumnCount();
        // 按顺序获取每个字段的值
        String[] fields = IntStream.rangeClosed(1, columnCount).mapToObj(i -> getFieldValue(rs, i)).toArray(String[]::new);
        // 将一行数据append写入到csv文件
        csvWriter.writeLine(fields);
        // 每5000行清空一次缓冲区, 将数据持久化到磁盘
        if (readCnt.incrementAndGet() % 5000 == 0) {
            csvWriter.flush();
            log.info("已导出: {}", readCnt.get());
        }
    }

    private static String getFieldValue(ResultSet rs, int i) {
        try {
            return rs.getString(i);
        } catch (SQLException e) {
            log.warn("字段解析失败", e);
            return "N/A";
        }
    }
}

项目启动后,发起请求
在这里插入图片描述
源码: https://gitee.com/DimonHo/export-data2csv.git

扩展知识

因为是直接导出数据写入到本地文件,所以这个程序不能部署到服务器,如果其它的小伙伴也想要用这个功能怎么办?
方案1. 要么修改一下代码,导出后先把文件写入到服务器,然后再下载到客户端,这样就会多走一层网络传输,如果导出的数据很大,到时候要从服务器上下载几个G的文件,对网络带宽和效率都是一种消耗。
方案2. 把项目打包成jar包,再把jar分发给需要的人,让它们自己在本机启动java,这种方式需要使用者的电脑上安装jre运行环境,如果是非技术人员,使用门槛较高。
方案3. 把项目打包成exe可执行文件,双击即可使用。

前两个方案就不说了,比较简单,大家应该都会。第三种方案怎么玩?
百度一下通常有两种方案,针对jdk14之前和之后分别有一个不同的解决方案。
jdk14之前,你可能需要用到 exe4j 这个工具,这个我没试过,看了下别人写的教程,感觉步骤有点麻烦,就不赘叙了,毕竟现在已经有更简单的办法了。
jdk14之后,jdk提供了一个jpackage命令工具,用这个工具,我们就可以用简单的几个命令直接打包成绿色版exe程序。下面来看看具体怎么玩。

  1. 首先,我们仍然需要先把源码编译成jar包,用 mvn package命令编译打包即可。
  2. 然后在执行jpackage命令把jar打包成exe程序
jpackage --type app-image --name exportData2Csv --input target --main-jar .\export-data2csv.jar --win-console --dest D:/apps

–type可选值有:app-image | exe | msi,这里我们使用app-image即可
–name:exe文件的名称
–input:输入目录,写jar包所在的目录即可
–main-jar: 指定主类的jar包
–dest: 打包后的目的地

运行完成后,在D:/apps目录下面就会多出一个exportData2Csv目录,进入这个目录,就能看到exportData2Csv.exe文件了
在这里插入图片描述
注意:exportData2Csv.exe不可单独拿出来运行,这里其实是基本把整个jre打包进来了,在runtime目录下。
我们可以把D:/apps/exportData2Csv整个目录压缩到一个zip包里面,就可以把这个绿色版的数据导出程序分发给别人了。

要做的更好用点,就在打包前在exportData2Csv目录下面在加一个readme.md文件,里面简单的写上使用教程,比如数据源配置如何修改。
说到这里,数据源的配置也有两种方式可以修改。
一种是运行这个exe文件时,指定参数文件路径 --spring.config.location=./application.yml
就像这样:
在这里插入图片描述

另一种方法是jpackage打包时,指定--arguments --spring.config.location=./application.yml参数,打包完成后,再手动把application.yml复制到exportData2Csv目录。
就像这样:
在这里插入图片描述
再添加readme.md说明文件
在这里插入图片描述
添加说明
在这里插入图片描述
最后打包成压缩包,把zip分发给别人使用
在这里插入图片描述

tips: 程序启动后,自动打开swagger ui页面,可以在main方法中加入这段代码

        try {
            // 使用默认浏览器打开swagger ui页面
            new ProcessBuilder("cmd", "/c", "start", "http://localhost:8080/swagger-ui.html").start();
        } catch (Exception e) {
            log.warn("打开客户端主页失败", e);
        }

在这里插入图片描述
在这里插入图片描述
是不是更好用了?


http://www.kler.cn/news/273673.html

相关文章:

  • 每天一个数据分析题(二百一十六)
  • SpringBoot2.7集成Swagger3
  • AcWing 1510:楼梯 ← 浮点数二分
  • 基于Matlab的视频人面检测识别,Matalb实现
  • CTF 题型 SSRF攻击例题总结
  • Linux/Ubuntu/Debian的终端中和的区别
  • Android学习进阶
  • 3种场景探讨ChatGPT如何改变投资者对测试管理初创企业的看法
  • 【Flask开发实战】防火墙配置文件解析(一)
  • JVM中对象创建过程
  • Java微服务轻松部署服务器
  • [LLM]大语言模型文本生成—解码策略(Top-k Top-p Temperature)
  • C# Winform实现数据双向绑定
  • 大模型-Prompt
  • 【07】进阶html5
  • C语言黑魔法第三弹——动态内存管理
  • k8s client-java创建pod常见问题
  • CentOS 7 编译安装 Nginx
  • spring-boot-starter-thymeleaf加载外部html文件
  • 什么是通用人工智能(AGI)?
  • Vim替换时区分大小写
  • 解决爬虫特殊解码的问题
  • 二叉树遍历(牛客网)
  • Apache Doris 2.1 核心特性 Variant 数据类型技术深度解析
  • 王老吉药业开拓数字经济“新蓝海”,成立数字经济研究所,科技赋能新品压片糖
  • 华为机试题-最小矩阵
  • RPM与DNF的操作实践
  • Dubbo是什么?请简要描述其主要功能。Dubbo的架构是怎样的?请解释其核心组件及其作用。
  • 微服务篇-C 深入理解第一代微服务(SpringCloud)_IV 深入理解Hystrix断路器
  • 华为OD机试真题实战应用【赛题代码篇】-素数伴侣(附Java、C++和python代码实现)