简介这是一份面向Java开发者与数据处理人员的实用工具资源聚焦于将Excel表格数据自动解析并生成可执行的SQL脚本再通过JDBC连接写入数据库适用于数据迁移、批量导入与数据分析等场景。压缩包共56个文件约24.48MB包含21个jar依赖、20个class与11个java源码另有classpath配置、xls测试模板及可执行jar结构完整便于直接运行与二次开发。资源核心围绕Apache POI解析Excel、构造INSERT等SQL语句、配置数据库驱动并执行连接操作展开源码可帮助理解从工作簿读取到字段映射、再到JDBC执行与异常处理的完整链路。目前已有979人学习下载适合熟悉Java与关系型数据库、希望提升数据操作效率的开发者参考借鉴。1. 读取Excel生成Sql脚本从手工拼SQL到一键导出你有没有遇到过这种场景业务方丢过来一个 Excel里面几十上百行数据让你导入数据库。手动写 INSERT 语句复制粘贴到凌晨三点还容易把和,搞混。这个项目就是来解决这个问题的——读取 Excel 文件自动解析表头和每一行数据生成可直接执行的 SQL 脚本。它适合经常做数据迁移、批量导入、测试数据构造的后端开发和数据工程同学。技术栈上核心依赖 JDBC 连接数据库做元数据校验用 POI 或 EasyExcel 解析表格再通过模板引擎拼装 SQL。整个流程不复杂但细节很多比如字段类型映射、空值处理、特殊字符转义这些才是真正决定脚本能不能跑通的关键。2. 项目定位与技术选型为什么不用 Navicat 导入2.1 这个工具解决的具体问题Navicat、DBeaver 这类工具确实有导入功能但它们通常要求你先建好表而且对 Excel 的格式要求很严格——表头必须和列名完全一致日期格式稍微不对就报错。更麻烦的是当你要把 Excel 数据生成 SQL 脚本发给别人执行时这些工具没法直接导出成可读的.sql文件。这个项目的定位很明确把 Excel 当作数据源把 SQL 脚本当作交付物。你不需要提前建表工具会根据 Excel 的表头自动推断字段名结合 JDBC 读取目标表的元数据如果表已存在生成带类型转换的 INSERT 语句。常见做法是先用 JDBC 的DatabaseMetaData拿到列类型再把 Excel 单元格的值转成对应的 SQL 字面量。2.2 技术栈拆解组件选型作用Excel 解析Apache POI / EasyExcel读取 .xlsx 和 .xls 文件数据库连接JDBC获取表结构、校验字段SQL 生成字符串模板 / MyBatis 动态 SQL拼装 INSERT 语句构建工具Maven / Gradle依赖管理运行方式命令行 / 单元测试传入文件路径和连接参数选 POI 还是 EasyExcel如果 Excel 文件不大几千行以内POI 的XSSFWorkbook足够用API 也直观。如果动辄几万行EasyExcel 的流式读取更稳内存占用低。我一般会先问清楚数据量再决定用哪个。2.3 核心流程设计整个工具的执行链路可以拆成四步读取 Excel加载文件拿到第一个 Sheet解析表头行和数据行。连接数据库通过 JDBC 建立连接查询目标表是否存在。类型映射根据数据库列类型把 Excel 单元格的值转成 SQL 字面量。生成脚本按行拼装 INSERT 语句写入.sql文件。这四步里第三步最容易翻车。比如 Excel 里看起来是数字的单元格POI 可能读出来是1.0而不是1日期单元格可能读出来是一个数字序列号。这些坑后面会专门讲。3. 核心模块拆解从 Excel 单元格到 SQL 字面量3.1 Excel 解析与表头处理先看读取 Excel 的代码。这里用 POI 举例重点是处理不同类型的单元格public ListMapString, Object readExcel(String filePath) throws IOException { ListMapString, Object rows new ArrayList(); try (FileInputStream fis new FileInputStream(filePath); Workbook workbook new XSSFWorkbook(fis)) { Sheet sheet workbook.getSheetAt(0); Row headerRow sheet.getRow(0); ListString headers new ArrayList(); // 解析表头去掉空格和特殊字符 for (Cell cell : headerRow) { String header cell.getStringCellValue().trim() .replaceAll([^a-zA-Z0-9_], _); headers.add(header); } // 从第二行开始读数据 for (int i 1; i sheet.getLastRowNum(); i) { Row row sheet.getRow(i); if (row null) continue; MapString, Object rowData new LinkedHashMap(); for (int j 0; j headers.size(); j) { Cell cell row.getCell(j); rowData.put(headers.get(j), getCellValue(cell)); } rows.add(rowData); } } return rows; }这段代码有几个关键点表头做了字符替换把中文和特殊符号转成下划线避免生成的 SQL 列名不合法用LinkedHashMap保持列的顺序这样生成的 INSERT 语句字段顺序和 Excel 一致方便核对。getCellValue方法需要根据单元格类型分别处理private Object getCellValue(Cell cell) { if (cell null) return null; switch (cell.getCellType()) { case STRING: return cell.getStringCellValue().trim(); case NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { return new java.sql.Date(cell.getDateCellValue().getTime()); } double num cell.getNumericCellValue(); // 处理 1.0 变成 1 的问题 if (num Math.floor(num) !Double.isInfinite(num)) { return (long) num; } return num; case BOOLEAN: return cell.getBooleanCellValue(); case BLANK: return null; default: return cell.toString(); } }数字类型的处理是血泪经验Excel 里输入100POI 读出来是100.0如果直接拼进 SQL数据库里就变成浮点数了。所以这里判断如果是整数就转成long。日期类型也要单独处理否则会得到一个像45123这样的序列号。3.2 JDBC 获取表结构与类型映射生成 SQL 之前最好先连上数据库看看目标表长什么样。这样能知道每个字段的真实类型避免把字符串塞进 INT 列public MapString, String getTableColumns(Connection conn, String tableName) throws SQLException { MapString, String columns new LinkedHashMap(); DatabaseMetaData metaData conn.getMetaData(); try (ResultSet rs metaData.getColumns(null, null, tableName, null)) { while (rs.next()) { String columnName rs.getString(COLUMN_NAME); String columnType rs.getString(TYPE_NAME); columns.put(columnName.toLowerCase(), columnType); } } return columns; }拿到列类型后在拼 SQL 时就可以做针对性处理。比如VARCHAR类型的值要加单引号并转义INT类型直接拼数字DATE类型要格式化成2024-01-15这种形式。转义函数长这样private String escapeSqlValue(Object value, String columnType) { if (value null) return NULL; if (columnType.contains(INT) || columnType.contains(DECIMAL) || columnType.contains(FLOAT) || columnType.contains(DOUBLE)) { return value.toString(); } if (columnType.contains(DATE) || columnType.contains(TIME)) { return value.toString() ; } // 字符串类型转义单引号和反斜杠 String str value.toString() .replace(\\, \\\\) .replace(, ); return str ; }注意字符串转义这里用了而不是\因为标准 SQL 里单引号转义就是写两个单引号这样兼容性更好。3.3 生成 INSERT 脚本最后一步是把前面拿到的数据拼成完整的 SQL 语句public void generateSql(ListMapString, Object rows, MapString, String columns, String tableName, String outputPath) throws IOException { try (BufferedWriter writer new BufferedWriter(new FileWriter(outputPath))) { for (MapString, Object row : rows) { StringBuilder sql new StringBuilder(INSERT INTO ) .append(tableName).append( (); // 字段名 sql.append(String.join(, , row.keySet())); sql.append() VALUES (); // 字段值 ListString values new ArrayList(); for (Map.EntryString, Object entry : row.entrySet()) { String colType columns.getOrDefault( entry.getKey().toLowerCase(), VARCHAR); values.add(escapeSqlValue(entry.getValue(), colType)); } sql.append(String.join(, , values)); sql.append();); writer.write(sql.toString()); writer.newLine(); } } }生成的脚本每行一条 INSERT末尾带分号可以直接在数据库客户端里批量执行。如果数据量很大可以考虑每 1000 条包一个事务或者用INSERT INTO ... VALUES (...), (...), ...的批量语法减少网络往返。4. 避坑与常见问题排查4.1 数字变成 1.0 或者科学计数法现象Excel 里明明是100生成的 SQL 里变成100.0或者长数字变成1.23456789E8。原因POI 把所有数字都当double读这是 Excel 底层的存储方式决定的。解决在getCellValue里判断如果是整数就转long如果是小数就用BigDecimal格式化避免科学计数法。对于手机号、身份证号这种长数字建议在 Excel 里就设成文本格式读取时按字符串处理。4.2 日期字段读出来是一串数字现象Excel 里是2024-01-15读出来是45306。原因Excel 内部用序列号存日期1900 年 1 月 1 日是 1。解决用DateUtil.isCellDateFormatted(cell)判断如果是日期格式就用cell.getDateCellValue()拿Date对象再格式化成yyyy-MM-dd字符串。4.3 表头有空格或特殊字符导致 SQL 报错现象生成的 SQL 执行时报Unknown column user name。原因Excel 表头里带了空格、中文括号、斜杠等字符。解决解析表头时统一做清洗把非字母数字下划线的字符替换成下划线连续下划线合并成一个。如果表头是中文要么手动映射成英文列名要么在数据库里用反引号包住列名。4.4 空单元格处理不当导致 NOT NULL 约束失败现象数据库报Column name cannot be null。原因Excel 里空单元格读出来是null直接拼成NULL塞进 SQL。解决在生成 SQL 前检查目标列的约束如果是NOT NULL且没有默认值要么给一个空字符串要么跳过这一行并记录日志。我一般会生成一个error.log把有问题的行号和数据打出来方便人工核对。4.5 大数据量导致内存溢出现象读取几万行的 Excel 时程序卡死或抛OutOfMemoryError。原因XSSFWorkbook会把整个文件加载到内存。解决换成 EasyExcel 的流式读取或者用 POI 的SXSSFWorkbook但它是写用的。读大文件推荐 EasyExcel它基于 SAX 解析内存占用稳定。如果坚持用 POI可以调大 JVM 的-Xmx但治标不治本。5. 二次开发与扩展思路5.1 支持多 Sheet 和自定义列映射现在的代码只读了第一个 Sheet实际项目里经常需要指定 Sheet 名或者按索引读取。改造起来不难把workbook.getSheetAt(0)换成workbook.getSheet(sheetName)就行。更实用的是加一个列映射配置比如用 JSON 文件定义{Excel表头: 数据库列名}这样业务方改表头也不用改代码。{ 用户姓名: user_name, 手机号: phone, 注册日期: register_date }读取映射后在解析表头时做一次转换后面的流程完全不用动。5.2 生成批量 INSERT 提升执行效率逐条 INSERT 在数据量大时执行很慢可以改成每 N 条合并成一条语句// 每 500 条拼一个 INSERT StringBuilder batchSql new StringBuilder(INSERT INTO tableName (); batchSql.append(String.join(, , columns.keySet())).append() VALUES ); ListString valueGroups new ArrayList(); for (MapString, Object row : batch) { ListString values new ArrayList(); for (String col : columns.keySet()) { values.add(escapeSqlValue(row.get(col), columns.get(col))); } valueGroups.add(( String.join(, , values) )); } batchSql.append(String.join(, , valueGroups)).append(;);注意批量插入时单条 SQL 不能太长MySQL 默认max_allowed_packet是 4MB超过会报错。500 条一批通常比较安全具体看字段数量和值的大小。5.3 增加数据校验和错误报告生产环境用的时候最怕生成一堆 SQL 结果执行到一半报错。建议在生成脚本前加一层校验检查必填字段是否为空、数字字段是否合法、日期格式是否正确。校验不通过的行不生成 SQL而是输出到错误报告里。ListString errors new ArrayList(); for (int i 0; i rows.size(); i) { MapString, Object row rows.get(i); if (row.get(user_name) null || row.get(user_name).toString().isEmpty()) { errors.add(第 (i 2) 行user_name 为空); continue; } // 其他校验... } if (!errors.isEmpty()) { Files.write(Paths.get(error.log), errors); }这样跑完一遍能执行的 SQL 已经生成好了有问题的行也列得清清楚楚直接发给业务方核对就行。5.4 一个容易忽略的细节事务和编码生成的 SQL 文件默认用系统编码写入如果数据库是 UTF-8 而系统是 GBK中文会乱码。建议在FileWriter里显式指定StandardCharsets.UTF_8。另外如果生成的脚本要在命令行里执行记得在文件开头加SET NAMES utf8mb4;避免客户端编码不一致。从那以后我每次生成 SQL 脚本都会先拿 10 行数据跑一遍确认字段类型、日期格式、中文编码都没问题再全量生成。这个习惯帮我省了很多返工的时间。希望帮到你。本文还有配套的精品资源点击获取