news 2026/6/13 1:59:52

Java小工具:连上MySQL就能导出表数据到Excel,带好所有依赖包

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java小工具:连上MySQL就能导出表数据到Excel,带好所有依赖包

本文还有配套的精品资源,点击获取

简介:一个开箱即用的Java小工具,不用改配置就能连MySQL、查表、生成.xlsx文件。项目结构清晰,src里是核心代码,bin里有编译好的class,lib目录已经打包好了MySQL JDBC驱动和Apache POI全套jar(包括poi、poi-ooxml、poi-scratchpad),直接运行Main类就能导出。数据库连接参数(URL、用户名、密码、表名)全在代码里标好了位置,改几行就能适配自己的库和表。导出结果是标准Excel格式,支持中文、数字、日期自动识别,不乱码、不丢列。附带README.txt说明步骤,information.xls里还留了示例数据结构参考。适合开发人员快速做数据核对、临时报表、测试数据导出,也方便集成进已有Java项目当一个轻量导出模块,不需要额外装环境或下依赖。

1. 项目概述:为什么这个“小工具”在实际开发中比想象中更难替代

你有没有遇到过这样的场景:测试环境里一张用户表突然数据异常,DBA还在排查,但业务方已经急着要导出最近3天的注册记录做人工核对;或者客户临时提需求,说“能不能把订单表今天的数据发我个Excel看看”,而你手头的报表系统还没上线,Spring Boot项目里又没集成导出模块;再比如,刚接手一个老系统,连数据库字段含义都还没理清,最高效的方式就是先把整张表捞出来,在Excel里用筛选、颜色标记、条件格式快速摸清数据分布规律——这时候,你真正需要的,从来不是一套高大上的BI平台,而是一个能5分钟内启动、30秒内改好参数、1分钟内拿到.xlsx文件的Java小工具。

这个项目就诞生于无数次这样的“救火时刻”。它不叫“数据导出平台”,也不叫“智能报表引擎”,就叫“Java小工具”,名字朴素得近乎简陋,但恰恰是这种克制,让它在真实开发流水中活了下来。它解决的不是“如何构建企业级导出服务”的宏大命题,而是“此刻,我怎么最快把这张表的数据变成Excel”的具体问题。关键词里反复出现的Java导出Excel、MySQL数据导出、POI写Excel、JDBC连接MySQL,不是技术堆砌的标签,而是四道必须同时跨过的门槛:Java环境要能跑起来,MySQL要连得上,Excel要写得对,整个流程要足够傻瓜。很多号称“开箱即用”的方案,往往卡在第一关——你解压完发现缺poi-ooxml,去官网下又提示版本冲突;或者改了数据库URL,运行报ClassNotFoundException: com.mysql.cj.jdbc.Driver,才想起还得手动加载驱动;又或者导出的Excel打开全是乱码,日期列显示成一串数字……这些不是边缘case,而是90%的轻量导出需求里,开发者每天都在重复踩的坑。

所以这个工具的核心价值,不在于它用了什么新框架,而在于它把所有“隐性成本”显性化、固化、打包好了。lib目录下预置的jar包,不是随便凑齐的,而是经过实测兼容的最小集合:mysql-connector-java-8.0.33.jar(适配MySQL 5.7/8.0)、poi-5.2.4.jar(处理.xls兼容层)、poi-ooxml-5.2.4.jar(核心.xlsx支持)、poi-scratchpad-5.2.4.jar(处理旧版Word/PPT嵌入对象,虽本项目不用,但避免某些POI方法调用时因类缺失而抛NoClassDefFoundError)。src里的代码没有一行是“为了设计模式而设计”,Main类里从Connection创建到Workbook生成再到FileOutputStream写盘,逻辑线性、变量命名直白(conn,stmt,rs,wb,sheet),连SQL查询都写死为SELECT * FROM ?,因为绝大多数临时导出,根本不需要动态拼接WHERE条件——你要的是快,不是灵活。README.txt里写的不是“请先阅读文档”,而是“第一步:用记事本打开src/Main.java;第二步:找到第22行,修改url、username、password、tableName四个变量;第三步:双击run.bat(Windows)或执行sh run.sh(Linux)”。它不假设你懂Maven生命周期,不依赖IDE的自动构建,甚至不强制要求你装JDK——bin目录里放着编译好的class文件,只要系统PATH里有java命令,就能跑。

这听起来很“土”,但正是这种“土”,让它在测试机、客户演示机、甚至某些禁用外网下载的生产隔离网段里,依然能成为那个最可靠的“数据搬运工”。它不追求可扩展性,但追求零失败率;不标榜架构先进,但确保每一行代码都有明确归宿。如果你正在找一个能立刻解决问题的工具,而不是一个需要花半天配置的学习项目,那它就是为你写的。

2. 整体设计与思路拆解:为什么选择“静态打包+硬编码参数”而非“配置中心+动态SQL”

很多人看到“参数全在代码里硬编码”,第一反应是“这设计太原始了”。但当你真正站在凌晨两点的工位前,面对一个急需导出的线上问题表,你会明白:可维护性是给未来留的,而可用性是给现在争的。这个项目的整体设计,本质上是一次对“最小可行交付物”的极致压缩,每一个取舍背后,都有明确的现实约束和成本计算。

2.1 架构选型:为什么放弃Spring Boot而坚持纯Java + JDBC

Spring Boot当然可以轻松集成JDBC和POI,还能通过application.yml管理数据库配置,甚至用@Value注入参数。但代价是什么?一个最简Spring Boot Web项目,光是spring-boot-starter-web依赖就会引入超过50个jar包,总大小轻松突破15MB。而本项目整个压缩包(含所有依赖)仅8.2MB,其中lib目录4.7MB,src+bin+文档共3.5MB。更重要的是,Spring Boot启动需要JVM预热、类路径扫描、Bean工厂初始化,冷启动时间通常在1.5~3秒;而本项目的Main类,从java -cp ... Main执行到Excel文件生成完毕,实测平均耗时420毫秒(基于i5-8250U + SSD)。这个差距在日常开发中可能不明显,但在自动化脚本调用、CI/CD流水线中批量导出多个表时,就是10个表×2.5秒 vs 10个表×0.4秒的差别——前者要等25秒,后者不到5秒。我们做过对比测试:用Spring Boot写一个等效功能,打包成fat jar后体积达22MB,首次运行耗时2.8秒;而本项目在同等硬件下,解压即用,无需任何构建步骤,直接执行java -cp "bin;lib/*" Main即可。对于“临时导出”这个场景,启动速度和部署复杂度,远比框架的优雅重要

2.2 依赖管理:为什么lib目录要“全家桶式”预置,而不是用Maven中央仓库

Maven的优雅在于声明式依赖,但它的脆弱在于网络依赖。在以下场景中,Maven会瞬间失效:
- 客户内网环境完全断外网,无法访问repo.maven.apache.org;
- 公司私服配置错误,导致poi-ooxml拉取到旧版(如3.17),与mysql-connector-java-8.0.33的JDK11+字节码不兼容;
- 某个依赖(如commons-collections4)被意外升级,触发POI内部反射调用失败。

本项目lib目录的jar包组合,是经过三轮实测验证的稳定三角:
-mysql-connector-java-8.0.33.jar:这是MySQL官方推荐的8.x系列最新稳定版,完美支持serverTimezone=GMT%2B8时区参数,避免中国用户常见的“时间戳错8小时”问题;
-poi-5.2.4.jar+poi-ooxml-5.2.4.jar:5.2.x是POI首个全面拥抱Java 11+模块化(JPMS)的主版本,XSSFWorkbook构造器不再抛NoClassDefFoundError,且对中文字符的默认字体处理更鲁棒;
-xmlbeans-5.1.1.jar(隐含在poi-ooxml中):这是poi-ooxml的底层XML解析引擎,5.1.1版本修复了Excel单元格合并区域(CellRangeAddress)在超大数据量(>10万行)时内存溢出的bug。

这些细节不会写在Maven pom.xml的注释里,但它们决定了你的导出任务是成功还是崩溃。预置jar包,等于把“已验证的二进制契约”直接交付给你,跳过了所有可能的解析、下载、版本冲突环节。你拿到的不是一份“说明书”,而是一份“已校准的仪器”。

2.3 导出逻辑设计:为什么用XSSFWorkbook而非SXSSFWorkbook,以及“自动识别类型”的真相

POI提供了两种xlsx写入方式:XSSFWorkbook(内存全量)和SXSSFWorkbook(磁盘溢出流式)。很多教程推荐SXSSFWorkbook以节省内存,但本项目坚持用XSSFWorkbook,原因很实在:绝大多数“临时导出”场景,数据量根本达不到内存瓶颈。我们统计了过去一年团队内237次导出请求,92%的表行数<5万,最大单次导出为83,421行(用户行为日志表),在16GB内存的笔记本上,XSSFWorkbook峰值内存占用仅480MB,完全可控。而SXSSFWorkbook的代价是:它会生成临时文件(默认在系统临时目录),如果用户没有写权限,或磁盘空间不足,会静默失败;且它不支持单元格样式实时刷新(如设置边框后立即生效),必须调用flush(),增加了代码复杂度。

至于“支持中文、数字、日期自动识别”,这并非魔法。核心逻辑在ResultSetMetaData的类型映射:

int columnType = rsmd.getColumnType(i); // 获取JDBC类型 switch (columnType) { case Types.VARCHAR: case Types.CHAR: cell.setCellValue(rs.getString(i)); // 字符串直接赋值 break; case Types.INTEGER: case Types.BIGINT: cell.setCellValue(rs.getLong(i)); // 数字转long,避免精度丢失 break; case Types.DATE: case Types.TIMESTAMP: Date date = rs.getDate(i); if (date != null) { cell.setCellValue(date); // POI自动识别Date类型,渲染为Excel日期格式 cell.setCellStyle(dateStyle); // 应用预设的日期样式 } break; }

这里的关键是cell.setCellValue(Date)而非cell.setCellValue(date.toString())。前者让POI将值存为Excel原生的序列号(1900年1月1日起的天数),后者只是字符串,后续在Excel里无法参与日期计算。而dateStyle的创建,使用了CreationHelper

CellStyle dateStyle = wb.createCellStyle(); DataFormat df = wb.getCreationHelper().createDataFormat(); dateStyle.setDataFormat(df.getFormat("yyyy-mm-dd hh:mm:ss")); // 强制指定显示格式

这确保了即使数据库里存的是TIMESTAMP,导出后在Excel里双击单元格,编辑栏显示的也是标准时间,而非一串数字。这个细节,是区分“能导出”和“导得好”的分水岭。

3. 核心细节解析与实操要点:从代码结构到每一处防坑设计

这个小工具的src目录结构极简,只有三个文件:Main.javaDBUtil.javaExcelUtil.java。没有包名,没有抽象接口,所有逻辑平铺直叙。但正是这种“裸奔式”的代码组织,让每一行意图都清晰可见。下面我带你逐层拆解,重点不是“它写了什么”,而是“为什么这么写”以及“你改的时候最容易栽在哪”。

3.1 Main.java:入口类的四步铁律与隐藏陷阱

Main.java是整个流程的指挥官,全文仅128行,但浓缩了所有关键决策点。它的执行流程严格遵循四步铁律:建连接 → 查数据 → 写Excel → 关资源。我们来看最关键的几段:

// 第22-25行:数据库连接参数(这就是你要改的地方) private static final String URL = "jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true"; private static final String USERNAME = "root"; private static final String PASSWORD = "123456"; private static final String TABLE_NAME = "user_info"; // 第45行:核心查询语句 String sql = "SELECT * FROM " + TABLE_NAME; // 第68行:关键!获取元数据,用于动态创建表头 ResultSetMetaData rsmd = rs.getMetaData(); int columnCount = rsmd.getColumnCount(); // 第85行:遍历结果集,逐行写入 while (rs.next()) { Row row = sheet.createRow(rowIndex++); for (int i = 1; i <= columnCount; i++) { // 注意:JDBC列索引从1开始! Cell cell = row.createCell(i - 1); // POI列索引从0开始,必须-1! // ... 类型判断与赋值逻辑(见2.3节) } }

这里埋着三个新手必踩的坑:
1.URL中的serverTimezone=GMT%2B8:这是MySQL 8.0+驱动的强制要求。如果你删掉它,连接会抛The server time zone value 'XXX' is unrecognized异常。GMT%2B8是URL编码后的GMT+8,不能写成GMT+8(会被解析为非法字符)。实测发现,有些老版本驱动(如5.1.x)反而不认这个参数,所以lib里必须配8.0.33。
2.allowPublicKeyRetrieval=true:这是解决MySQL 8.0默认开启caching_sha2_password认证插件的钥匙。如果不加,会报Public Key Retrieval is not allowed。这个参数在生产环境应配合强密码策略使用,但临时导出场景,它是连通性的刚需。
3.JDBC列索引从1开始,POI从0开始rs.getString(i)i必须从1循环到columnCount,而row.createCell(i-1)必须减1。这是JDBC规范与POI API的差异,无数人在这里写出ArrayIndexOutOfBoundsException。我们在代码里用注释// 注意:JDBC列索引从1开始!// POI列索引从0开始,必须-1!双重强调,就是因为它太容易错了。

3.2 DBUtil.java:连接池的“反模式”与单次连接的合理性

DBUtil.java只有两个静态方法:getConnection()closeConnection()。它没有用HikariCP或Druid,而是每次导出都新建一个Connection,用完立刻关闭。这看起来违背了“连接池最佳实践”,但在此场景下,它是最优解:

  • 连接池的收益在于复用:当你的应用每秒处理数百次数据库请求时,连接池能显著降低TCP握手和认证开销。但本工具,一次运行只查一张表,最多建立1个连接,用完即弃。此时连接池不仅没收益,反而增加复杂度(需配置最小/最大连接数、空闲超时等)。
  • 连接泄漏风险更低getConnection()方法里,try-with-resources包裹了Connection,确保即使查询过程抛异常,连接也会被关闭。而连接池如果配置不当(如maxLifetime设得太短),反而可能导致连接在池中老化失效,下次取出时报Connection closed
  • 调试更直观:当连接失败时,错误堆栈直接指向DBUtil.getConnection(),你能一眼看到是URL错、密码错,还是端口不通。而连接池的错误,往往裹着多层代理类,定位更费时。

DBUtil里还有一个精妙设计:static { }静态块中加载驱动:

static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new RuntimeException("MySQL JDBC Driver not found!", e); } }

这是JDBC 4.0+规范的“可选”步骤(驱动jar的META-INF/services/java.sql.Driver会自动注册),但显式调用Class.forName有两个好处:一是兼容极老JDK(如1.6),二是让异常提前暴露——如果lib目录下漏了mysql-connector-java.jar,程序会在main方法执行前就崩溃,而不是等到getConnection()时才报NoClassDefFoundError,极大缩短排错路径。

3.3 ExcelUtil.java:样式、字体与中文乱码的终极解决方案

ExcelUtil.java负责创建Workbook、定义样式、处理特殊字符。它解决了三个高频痛点:

痛点1:中文乱码
很多方案用HSSFWorkbook(.xls)时设setFont(Font font),但对XSSFWorkbook无效。正确做法是:为每个CellStyle显式绑定Font,且字体名称必须是系统已安装的中文字体。代码中:

Font font = wb.createFont(); font.setFontName("微软雅黑"); // 必须是Windows系统默认中文字体名 font.setFontHeightInPoints((short) 10); CellStyle style = wb.createCellStyle(); style.setFont(font);

为什么是“微软雅黑”?因为poi-ooxml在渲染xlsx时,会将字体名写入XML,Excel客户端会按此名称查找本地字体。如果写"SimSun"(宋体),在Mac或Linux上可能找不到,回退为默认英文字体,导致中文显示为方块。而“微软雅黑”在Windows 7+、Office 365、WPS中均预装,兼容性最好。实测中,若将字体名改为"Arial",中文列会全部变为空格。

痛点2:数字列被Excel误判为文本
当数据库某列为VARCHAR但内容全是数字(如身份证号、手机号),POI默认setCellValue(String),Excel会将其识别为“文本格式”,左上角带绿色小三角,且无法参与求和。解决方案是:对纯数字字符串,尝试解析为LongDouble

String value = rs.getString(i); if (value != null && value.matches("\\d+")) { // 简单匹配纯数字 try { cell.setCellValue(Long.parseLong(value)); } catch (NumberFormatException e) { cell.setCellValue(value); // 解析失败,退回字符串 } } else { cell.setCellValue(value); }

这个逻辑放在Main.java的循环体内,确保数字列保持数值属性。

痛点3:长文本自动换行与列宽自适应
Excel默认列宽不足以显示长文本,用户需手动拖拽。ExcelUtil提供autoSizeColumn方法:

for (int i = 0; i < columnCount; i++) { sheet.autoSizeColumn(i, true); // true表示合并单元格也参与计算 }

autoSizeColumn有个致命缺陷:它只根据当前单元格内容宽度计算,如果某行内容特别长(如一篇博客正文),会导致该列极宽,挤压其他列。因此我们在Main.java中做了限制:

// 设置最大列宽为50个字符(约350像素) for (int i = 0; i < columnCount; i++) { sheet.setColumnWidth(i, 50 * 256); // POI单位是1/256个字符宽度 }

然后才调用autoSizeColumn,这样既保证了基本可读性,又防止列宽失控。

4. 实操过程与核心环节实现:从解压到导出的完整链路与参数详解

现在,让我们把前面所有的原理,落地为一份可照着做的操作手册。这不是理论推演,而是我在客户现场、测试机、甚至自己家老旧笔记本上,亲手执行过37次的完整流程。每一步都标注了“为什么这么做”和“不做会怎样”。

4.1 环境准备:最低要求与常见误区

最低硬件要求
- CPU:双核以上(i3-3220即可)
- 内存:4GB(导出<10万行数据)
- 磁盘:空闲空间≥50MB(解压后约12MB)

软件要求(仅此一项)
- JDK 8u202 或更高版本(必须是JDK,不是JRE!因为javac编译命令在JRE里不存在,虽然本项目提供bin/class,但你可能需要修改代码后重新编译)
- 验证方式:命令行输入java -version,输出应包含1.8.0_20211.0.15等字样。如果报'java' is not recognized,说明JAVA_HOME未配置或PATH未包含%JAVA_HOME%\bin

提示:不要试图用JDK 17或21运行!poi-5.2.4官方支持的最高JDK版本是17,但mysql-connector-java-8.0.33在JDK 21上会抛Unsupported major.minor version 61.0(字节码版本不匹配)。本项目经实测,JDK 8u202、11.0.15、17.0.7均可稳定运行,JDK 21暂不支持。

绝对禁止的操作
- ❌ 不要双击pom.xml用IDE自动导入(Maven会尝试联网下载依赖,破坏离线性);
- ❌ 不要在IDE里右键Run As > Java Application(IDE的classpath可能与项目lib目录冲突,导致NoClassDefFoundError);
- ❌ 不要修改lib目录下jar包的文件名(如把mysql-connector-java-8.0.33.jar改成mysql.jar-cp "lib/*"通配符会失效)。

4.2 参数修改:四行代码决定成败

打开src/Main.java,用任意文本编辑器(推荐Notepad++或VS Code),定位到第22-25行:

private static final String URL = "jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true"; private static final String USERNAME = "root"; private static final String PASSWORD = "123456"; private static final String TABLE_NAME = "user_info";

逐项修改指南
-URL:格式为jdbc:mysql://[IP]:[端口]/[数据库名]?参数[IP]填你的MySQL服务器地址,本地填localhost127.0.0.1[端口]默认3306,如修改过需同步;[数据库名]填你要导出表所在的库名(如myapp_prod)。关键参数必须保留useSSL=false(避免SSL握手失败)、serverTimezone=GMT%2B8(解决时区)、allowPublicKeyRetrieval=true(解决认证)。如果MySQL是5.7,可删掉allowPublicKeyRetrieval=true,但8.0+必须保留。
-USERNAMEPASSWORD:填你有SELECT权限的数据库账号。切勿使用root账号在生产环境!建议创建专用账号:CREATE USER 'exporter'@'%' IDENTIFIED BY 'StrongPass123!'; GRANT SELECT ON myapp_prod.* TO 'exporter'@'%'; FLUSH PRIVILEGES;
-TABLE_NAME:填你要导出的表名,不要加数据库前缀(如myapp_prod.user_info是错的,只填user_info)。

注意:所有字符串值必须用英文双引号包裹,末尾必须有分号。改完保存,不要改动其他任何代码。

4.3 执行导出:Windows与Linux双路径

Windows用户(推荐使用run.bat
  1. 确保你已解压项目到某个目录,如D:\tools\sqltoexcel
  2. 进入该目录,双击run.bat(这是一个批处理文件,内容为java -cp "bin;lib/*" Main);
  3. 控制台窗口会快速闪过几行日志,最后显示Export completed! File saved to: D:\tools\sqltoexcel\output\user_info_20240520.xlsx
  4. 打开output文件夹,双击生成的xlsx文件即可查看。
Linux/macOS用户(使用run.sh
  1. 打开终端,cd到项目根目录;
  2. 赋予脚本执行权限:chmod +x run.sh
  3. 执行:./run.sh
  4. 输出路径同上,文件在output/目录。

如果执行失败,看控制台第一行错误
-Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/poi/ss/usermodel/Workbook:说明lib/*通配符没生效,检查lib目录是否存在,jar包名是否被手动修改;
-java.sql.SQLException: Access denied for user 'xxx'@'localhost':用户名或密码错误,或账号无对应库的SELECT权限;
-java.sql.SQLSyntaxErrorException: Unknown database 'testdb':URL里的数据库名不存在,请登录MySQL执行SHOW DATABASES;确认。

4.4 输出文件详解:output目录下的每一个文件都是什么

执行成功后,output目录会生成三个文件:
-user_info_20240520.xlsx:主输出文件,文件名格式为[表名]_[日期].xlsx,日期为执行当天(YYYYMMDD格式)。打开后,第一行为表头(数据库字段名),第二行起为数据。所有列宽已自适应,日期列有yyyy-mm-dd hh:mm:ss格式,数字列可直接求和。
-user_info_20240520.log:日志文件,记录本次导出的详细信息:
[INFO] Start export at: 2024-05-20 14:23:15 [INFO] Connected to: jdbc:mysql://localhost:3306/testdb [INFO] Query executed: SELECT * FROM user_info [INFO] Fetched 12,487 rows [INFO] Workbook created with 1 sheet [INFO] Export completed in 842 ms
当导出异常中断时,这是唯一能帮你定位问题的线索。
-user_info_20240520.schema.txt:表结构快照,内容为DESCRIBE user_info;的结果,方便你核对字段类型与Excel中显示是否一致。例如:
Field Type Null Key Default Extra id bigint(20) NO PRI NULL auto_increment username varchar(50) NO NULL create_time datetime YES NULL

5. 常见问题与排查技巧实录:那些文档里不会写的“血泪经验”

这个工具我带着团队在真实项目中跑了两年,覆盖了金融、电商、政务三类客户环境。下面列出的,不是教科书式的FAQ,而是我们亲手踩过、拍过桌子、熬过夜后总结的“速查清单”。每一条,都对应一个曾让我们抓狂的具体场景。

5.1 连接类问题:90%的失败源于URL和权限

现象错误日志片段根本原因排查与解决
连接超时java.sql.SQLNonTransientConnectionException: Could not create connection to database server.MySQL服务未启动,或防火墙拦截了3306端口在MySQL服务器上执行netstat -an \| findstr :3306(Windows)或sudo lsof -i :3306(Linux),确认端口监听状态;检查Windows防火墙或云服务器安全组规则
拒绝访问Access denied for user 'exporter'@'192.168.1.100'账号授权的Host不匹配,'exporter'@'%'允许所有IP,但'exporter'@'localhost'只允许本机登录MySQL,执行SELECT User,Host FROM mysql.user WHERE User='exporter';,确认Host列值;如需远程访问,授权时用'exporter'@'%'
SSL握手失败SSL error: java.security.ProviderException: Could not derive keyMySQL 8.0+默认启用SSL,但客户端未配置信任证书URL中添加useSSL=false(开发/测试环境安全),或按MySQL官方文档配置SSL证书(生产环境必需)

实操心得:在客户现场,我永远先执行telnet [IP] 3306。如果连不上,后面所有操作都是徒劳。telnet是网络连通性的黄金标准,比任何Java错误日志都可靠。

5.2 数据类问题:中文、日期、数字的“隐形失真”

现象表现根本原因解决方案
中文显示为方块或问号Excel单元格里是□□□或????ExcelUtil.java中字体名错误,或系统未安装对应字体font.setFontName("微软雅黑")改为"SimSun"(宋体),或在Windows上安装“微软雅黑”字体(通常已预装);确保output目录有写权限
日期显示为43210这类数字单元格内容是43210,但编辑栏显示2018-05-10setCellValue()传入了String而非Date对象检查Main.java中日期列的赋值逻辑,必须用rs.getDate(i)获取java.sql.Date,再调用cell.setCellValue(date)
身份证号/手机号前面的0丢失数据库存00123456789,Excel显示123456789Excel将长数字自动转为科学计数法ExcelUtil.java中,为该列单独设置文本样式:cell.setCellStyle(textStyle),其中textStylesetDataFormat(df.getFormat("@"))@代表文本格式)

实操心得:导出后,务必在Excel里按Ctrl+1打开“设置单元格格式”对话框,查看问题列的实际格式。如果是“常规”或“数值”,说明POI没正确识别类型;如果是“文本”,但内容还是错的,那就是数据库字段类型本身有问题(如该存VARCHAR却用了INT)。

5.3 性能与稳定性问题:大数据量下的“温柔一刀”

现象触发条件风险应对策略
导出中途卡死,CPU 100%导出>20万行,且某列含超长文本(如JSON字段)XSSFWorkbook内存溢出,JVM抛OutOfMemoryError启动时增加JVM内存:修改run.batjava -Xmx2g -cp "bin;lib/*" Main(Windows)或java -Xmx2g -cp "bin:lib/*" Main(Linux),将最大堆内存设为2GB
生成的Excel打不开,提示“文件已损坏”使用了poi-scratchpad旧版本(如3.17),且数据库有BLOB字段poi-ooxml在处理<v>标签时XML结构错误确保lib目录下是poi-5.2.4.jar及配套jar,删除所有旧版poi(如poi-3.17.jar
导出速度慢(>10秒)表有50+字段,且含TEXTJSON类型JDBC驱动默认fetchSize为0,一次性拉取所有数据到内存DBUtil.getConnection()后,添加stmt.setFetchSize(1000),让驱动分批获取,减少内存峰值

实操心得:当客户说“这张表有300万行,你们能导吗”,我的标准回答是:“可以,但我们需要15分钟准备——先用SELECT COUNT(*) FROM table_name确认真实行数,再评估是否需要分页导出(本工具暂不支持,但可快速定制)”。诚实比承诺更重要。

5.4 集成到现有项目:三步嵌入法

很多开发者问:“能不能不单独运行,而是作为我Spring Boot项目的一个Service?”当然可以,而且非常简单:

Step 1:复制核心类
- 将src/Main.java重命名为SqlToExcelService.java,放入你的src/main/java/com/yourpackage/util/目录;
- 将DBUtil.javaExcelUtil.java一并复制过去;
- 删除Main.java里的public static void main(String[] args)方法。

Step 2:改造为Spring Bean

@Service public class SqlToExcelService { @Value("${export.db.url}") private String url; @Value("${export.db.username}") private String username; @Value("${export.db.password}") private String password; public File exportTable(String tableName, String outputPath) throws Exception { // 复制Main.java中从getConnection()到writeToFile()的全部逻辑 // 将硬编码的URL/USERNAME/PASSWORD替换为成员变量 // 返回生成的File对象 } }

Step 3:配置与调用
- 在application.yml中添加:
yaml export: db: url: jdbc:mysql://prod-db:3306/myapp?useSSL=false&serverTimezone=GMT%2B8 username: exporter password: StrongPass123!
- 在Controller中调用:
java @GetMapping("/export/{table}") public ResponseEntity<Resource> export(@PathVariable String table) throws Exception { File file = sqlToExcelService.exportTable(table, "/tmp/"); return ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName()) .body(new UrlResource(file.toURI())); }

最后分享一个小技巧:在ExcelUtil.java中,我预留了一个setCustomHeader()方法(目前未调用)。如果你的公司要求所有导出Excel必须带LOGO和标题,只需在Main.javacreateSheet()后调用它,传入图片路径和标题文字,它会自动插入合并单元格和图片——这个扩展点,就是为“下一步”准备的。

本文还有配套的精品资源,点击获取

简介:一个开箱即用的Java小工具,不用改配置就能连MySQL、查表、生成.xlsx文件。项目结构清晰,src里是核心代码,bin里有编译好的class,lib目录已经打包好了MySQL JDBC驱动和Apache POI全套jar(包括poi、poi-ooxml、poi-scratchpad),直接运行Main类就能导出。数据库连接参数(URL、用户名、密码、表名)全在代码里标好了位置,改几行就能适配自己的库和表。导出结果是标准Excel格式,支持中文、数字、日期自动识别,不乱码、不丢列。附带README.txt说明步骤,information.xls里还留了示例数据结构参考。适合开发人员快速做数据核对、临时报表、测试数据导出,也方便集成进已有Java项目当一个轻量导出模块,不需要额外装环境或下依赖。


本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/13 1:57:36

MCP Server开发实战:从零构建Agent可调用的服务

#32 MCP Server开发实战&#xff1a;从零构建Agent可调用的服务「Hermes Agent自进化智能体深度解析」系列 | 模块十一 第2篇理解了MCP协议&#xff0c;但你自己能开发一个MCP Server吗&#xff1f; 上一篇#31&#xff0c;我们把MCP协议拆到了螺丝级别——JSON-RPC 2.0的请求响…

作者头像 李华
网站建设 2026/6/13 1:55:50

AKShare:三分钟搞定金融数据,Python量化分析的终极解决方案

AKShare&#xff1a;三分钟搞定金融数据&#xff0c;Python量化分析的终极解决方案 【免费下载链接】akshare AKShare is an elegant and simple financial data interface library for Python, built for human beings! 开源财经数据接口库 项目地址: https://gitcode.com/g…

作者头像 李华
网站建设 2026/6/13 1:50:57

【计算机毕业设计案例】基于 SpringBoot 的老年帮扶志愿任务调度系统的设计与实现(程序+文档+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/13 1:47:13

AI标书系统私有化部署怎么评估?从DeepSeek算力、知识库到国产化适配

AI写标书正在从“生成工具”走向“投标流程系统”。早期用户更关心生成速度&#xff1a;能不能快速写技术标&#xff1f;能不能减少标书员重复劳动&#xff1f;但在高频投标、内网办公、政企服务、建筑、电力、制造等场景里&#xff0c;企业真正关心的问题正在变得更具体&#…

作者头像 李华