读取Excel生成Sql脚本:JDBC批量插入与字段映射实战 简介这是一份面向Java开发者与数据库运维人员的数据处理工具源码包用于将Excel表格数据自动解析并生成可执行的SQL脚本再通过JDBC连接写入目标数据库适用于数据迁移、批量导入与数据分析等场景。压缩包共56个文件约24.48MB以21个jar依赖、20个class编译文件、11个java源码为主另含classpath配置、xls测试模板与fatjar打包文件结构完整便于二次开发。资源已有979人学习下载作者为sun_mingtao。读者可从中获取基于Apache POI读取Excel、构造INSERT等SQL语句、配置MySQL或Oracle驱动并管理连接的完整实现思路同时借助示例Excel与可执行jar快速验证流程理解字段映射与异常处理细节对熟悉Java与数据库的开发者具有较高参考价值。1. 读取Excel生成Sql脚本从手工拼SQL到一键导出你有没有遇到过这种场景业务方丢过来一个 Excel里面几十上百行数据让你“导进数据库”。你打开 Navicat 或者 DBeaver准备手工拼 INSERT 语句拼到第十行就开始怀疑人生——字段里有单引号、有换行、有日期格式不统一稍不留神就报语法错误。更麻烦的是这种活往往不是一次性的下周业务方又发来一个新版本你还得重来一遍。“读取Excel生成Sql脚本”这个资源解决的就是这个重复劳动问题。它的核心思路很直接用程序读取 Excel 的每一行按你指定的表名和字段映射自动拼出可执行的 INSERT 语句再通过 JDBC 连接数据库直接写入或者导出成 .sql 文件交给 DBA 审核。适合谁用后端开发、数据迁移工程师、测试人员以及任何需要把表格数据批量入库的从业者。下面我从技术选型和实操两个维度把这份资源拆开讲清楚。2. 技术选型与核心依赖为什么用 JDBC 而不是 ORM2.1 解析 Excel 的两种主流方案对比在 Java 生态里读 Excel 常见的选择是 Apache POI 和 EasyExcel。POI 功能最全能处理 .xls 和 .xlsx支持公式、样式、合并单元格但内存占用高——一个几万行的 xlsx 用 XSSFWorkbook 直接读JVM 很容易 OOM。EasyExcel 是阿里开源的基于 SAX 模式逐行解析内存占用低API 也更简洁适合大数据量场景。这份资源用的是 POI 的 XSSF 模式原因是它需要读取单元格的原始类型字符串、数字、日期、布尔而 EasyExcel 在某些版本里对日期格式的自动识别不够灵活。如果你的 Excel 行数超过 5 万建议换成 EasyExcel 的ReadListener模式或者用 POI 的SXSSFWorkbook做流式读取。方案内存占用日期处理适用行数学习成本POI XSSF高灵活 5万中POI SXSSF低灵活 5万中高EasyExcel低一般 10万低2.2 JDBC 直连 vs ORM 批量插入为什么不直接用 MyBatis 或 JPA因为这份资源的定位是“生成 SQL 脚本”而不是“做业务写入”。JDBC 的优势在于你可以拿到最终拼好的 SQL 字符串先输出到控制台或文件人工确认无误后再执行。ORM 的批量插入虽然方便但 SQL 是框架生成的你很难在入库前做二次审核。资源里用的是PreparedStatement的addBatch()和executeBatch()配合rewriteBatchedStatementstrueMySQL 连接参数能把多条 INSERT 合并成一条网络包发送性能比逐条执行快 5 到 10 倍。如果你要生成的是纯文本 .sql 文件那就用StringBuilder拼接注意转义单引号和反斜杠。// 读取Excel并生成INSERT语句的核心逻辑 public ListString generateSql(String excelPath, String tableName, MapInteger, String columnMap) throws Exception { ListString sqlList new ArrayList(); try (FileInputStream fis new FileInputStream(excelPath); Workbook workbook new XSSFWorkbook(fis)) { Sheet sheet workbook.getSheetAt(0); // 从第二行开始读第一行是表头 for (int i 1; i sheet.getLastRowNum(); i) { Row row sheet.getRow(i); if (row null) continue; StringBuilder sql new StringBuilder(INSERT INTO ) .append(tableName).append( (); // 拼接字段名 ListString columns new ArrayList(columnMap.values()); sql.append(String.join(, , columns)).append() VALUES (); // 拼接字段值 ListString values new ArrayList(); for (Map.EntryInteger, String entry : columnMap.entrySet()) { Cell cell row.getCell(entry.getKey()); values.add(formatCellValue(cell)); } sql.append(String.join(, , values)).append();); sqlList.add(sql.toString()); } } return sqlList; } // 根据单元格类型格式化值处理字符串转义 private String formatCellValue(Cell cell) { if (cell null) return NULL; switch (cell.getCellType()) { case STRING: // 转义单引号和反斜杠防止SQL注入和语法错误 String str cell.getStringCellValue() .replace(\\, \\\\) .replace(, ); return str ; case NUMERIC: if (DateUtil.isCellDateFormatted(cell)) { // 日期格式统一转成 yyyy-MM-dd HH:mm:ss SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm:ss); return sdf.format(cell.getDateCellValue()) ; } // 数字去掉科学计数法 return new BigDecimal(cell.getNumericCellValue()).toPlainString(); case BOOLEAN: return cell.getBooleanCellValue() ? 1 : 0; case BLANK: return NULL; default: return NULL; } }上面这段代码有两个关键点。第一columnMap的 key 是 Excel 的列索引从 0 开始value 是数据库字段名这样你可以灵活映射不用改代码就能适配不同表结构。第二formatCellValue里对字符串做了两次替换先把反斜杠转成双反斜杠再把单引号转成两个单引号。这是 MySQL 的标准转义方式PostgreSQL 也兼容。如果你用的是 Oracle单引号转义规则一样但日期格式需要用TO_DATE函数包一层。2.3 JDBC 连接参数与批量提交配置生成 SQL 之后如果选择直连数据库执行JDBC URL 里要加几个参数。以 MySQL 为例jdbc:mysql://localhost:3306/your_db?useUnicodetruecharacterEncodingutf8rewriteBatchedStatementstrueuseServerPrepStmtstruecachePrepStmtstruerewriteBatchedStatementstrue是核心它让驱动把多条 INSERT 重写成一条INSERT INTO ... VALUES (...), (...), ...减少网络往返。useServerPrepStmtstrue和cachePrepStmtstrue让预编译语句在服务端缓存避免每次重新解析。这三个参数组合起来批量插入 1 万行数据通常能从 30 秒降到 3 秒以内。执行批量插入时每 1000 条调用一次executeBatch()不要一次性攒 10 万条再提交——那样事务日志会膨胀回滚段压力大万一失败全部重来。分批提交还能让你在出错时定位到具体是哪一批数据有问题。3. 从Excel到SQL的完整实操字段映射与类型转换3.1 配置字段映射的三种方式字段映射是这类工具最容易翻车的地方。Excel 的列顺序和数据库字段顺序往往不一致列名也可能带空格或中文。资源里提供了三种映射方式第一种是硬编码在代码里用MapInteger, String直接指定。适合表结构固定、一次性使用的场景。第二种是读配置文件比如 properties 或 YAML把映射关系外置改配置不用重新编译。第三种是自动匹配——读取 Excel 第一行表头和数据库的information_schema.columns做模糊匹配匹配不上的列标记为待确认。我一般用第二种配置文件长这样# excel-column-indexdb-column-name 0user_id 1user_name 2created_at 3amount然后在代码里加载Properties props new Properties(); try (InputStream is new FileInputStream(mapping.properties)) { props.load(is); } MapInteger, String columnMap new TreeMap(); for (String key : props.stringPropertyNames()) { columnMap.put(Integer.parseInt(key), props.getProperty(key)); }用TreeMap是为了保证列顺序按索引升序排列拼出来的 SQL 字段顺序和值顺序才能一一对应。如果你用HashMap遍历顺序不确定拼出来的 SQL 会错位。3.2 日期和数字类型的坑Excel 里的日期本质上是一个数字从 1900 年 1 月 1 日开始的天数。POI 的DateUtil.isCellDateFormatted()能判断单元格是否被格式化为日期但有个坑如果用户在 Excel 里手动输入了2024-01-15但单元格格式是“常规”POI 会把它当成数字读出来得到 45306 这样的序列号。解决办法是加一层判断如果单元格是 NUMERIC 类型且值在 1 到 100000 之间同时列名包含 date、time、created、updated 等关键词就尝试按日期解析。更稳妥的做法是在 Excel 模板里强制设置日期列的格式为yyyy-mm-dd从源头避免歧义。数字类型也有坑。Excel 里的大数字比如订单号 20240115123456789如果单元格格式是“常规”POI 读出来是科学计数法2.02401151234568E16精度丢失。必须用BigDecimal来读BigDecimal bd new BigDecimal(cell.getNumericCellValue()); String value bd.toPlainString(); // 输出 20240115123456789但getNumericCellValue()返回的是 double超过 15 位有效数字仍然会丢精度。如果订单号超过 15 位建议在 Excel 里把该列设为文本格式或者用cell.getRichStringCellValue()读原始字符串。3.3 生成SQL文件与直接入库的切换资源里把“生成文件”和“直接入库”做成了两个独立方法通过命令行参数切换# 生成 .sql 文件不连数据库 java -jar excel2sql.jar --modefile --inputdata.xlsx --outputinsert.sql --tableusers --mappingmapping.properties # 直连数据库执行 java -jar excel2sql.jar --modejdbc --inputdata.xlsx --urljdbc:mysql://localhost:3306/test --userroot --password123456 --tableusers --mappingmapping.properties--modefile模式下程序只做解析和拼接把结果写到insert.sql每行一条 INSERT末尾加分号。DBA 可以先用EXPLAIN或者事务回滚的方式验证。--modejdbc模式下程序会先打印前 10 条 SQL 供确认然后询问是否继续执行——这个交互设计很实用避免手滑直接写生产库。如果你要集成到自动化流程里加一个--yes参数跳过确认。但生产环境我建议还是保留确认步骤或者至少把生成的 SQL 同时落一份文件方便事后审计。4. 避坑与排查那些让我加班到凌晨的细节4.1 中文乱码从文件编码到数据库字符集现象生成的 SQL 文件用记事本打开正常但用source命令导入 MySQL 后中文变成问号。原因Java 写文件时默认用系统编码Windows 中文版是 GBK而 MySQL 的character_set_server可能是 latin1 或 utf8mb4。编码链路不一致导致乱码。解决写文件时强制指定 UTF-8try (BufferedWriter writer new BufferedWriter( new OutputStreamWriter(new FileOutputStream(insert.sql), StandardCharsets.UTF_8))) { for (String sql : sqlList) { writer.write(sql); writer.newLine(); } }同时确认数据库连接串里有characterEncodingutf8建表时用DEFAULT CHARSETutf8mb4。导入时用mysql --default-character-setutf8mb4 -u root -p db insert.sql。4.2 单引号和换行符导致的语法错误现象某条 INSERT 执行报You have an error in your SQL syntax定位到某个字段值里有一个单引号比如OBrien。原因字符串值没有转义SQL 解析器把OBrien里的单引号当成了字符串结束符。解决前面代码里的replace(, )就是干这个的。但要注意如果字段值里同时有反斜杠和单引号替换顺序不能反——必须先替换反斜杠再替换单引号。否则\会被先变成再变成结果还是错的。换行符同理\n和\r在 SQL 字符串里是合法的但如果你用source命令导入换行符可能被解析成语句分隔符。稳妥做法是把换行符替换成\\n字面量或者用LOAD DATA代替 INSERT。4.3 批量插入时的主键冲突现象执行到第 500 条时报Duplicate entry xxx for key PRIMARY整个批次回滚。原因Excel 里有重复数据或者数据库里已经存在相同主键的记录。解决两种策略。一是用INSERT IGNORE冲突时跳过不报错。二是用ON DUPLICATE KEY UPDATE冲突时更新指定字段。资源里默认用普通 INSERT但提供了--on-conflictignore|update参数切换。如果选 update需要额外指定更新哪些列否则会更新所有非主键列可能覆盖掉你不希望改的数据。4.4 日期格式与数据库时区不一致现象Excel 里是2024-01-15 10:00:00入库后变成2024-01-15 02:00:00差了 8 小时。原因JDBC 连接串里没指定时区驱动用了 JVM 默认时区而数据库服务器用的是 UTC。解决在 JDBC URL 里加serverTimezoneAsia/Shanghai或者在代码里用TimeZone.setDefault(TimeZone.getTimeZone(Asia/Shanghai))。更彻底的做法是数据库字段用TIMESTAMP类型并在连接串里加useLegacyDatetimeCodefalse让驱动按标准协议处理时区转换。4.5 大文件读取时的内存溢出现象处理一个 20 万行的 xlsx 时程序跑了几分钟就抛OutOfMemoryError: Java heap space。原因XSSFWorkbook会把整个文件加载到内存20 万行大约需要 2 到 4 GB 堆内存。解决换用SXSSFWorkbook做流式读取或者用 EasyExcel 的ReadListener。如果不想改代码临时方案是加大 JVM 堆java -Xmx4g -jar excel2sql.jar ...。但长期来看流式解析才是正解。另外读的时候用sheet.getRow(i)逐行获取不要用sheet.iterator()一次性遍历后者在某些 POI 版本里会缓存所有行对象。5. 进阶技巧用元数据自动生成映射与校验5.1 从 information_schema 读取表结构手工维护 mapping.properties 很烦尤其是表有几十个字段的时候。更聪明的做法是连上数据库查information_schema.columns拿到字段名和类型然后和 Excel 表头做自动匹配。SELECT column_name, data_type, is_nullable, column_default FROM information_schema.columns WHERE table_schema your_db AND table_name users ORDER BY ordinal_position;拿到结果后用列名做模糊匹配Excel 表头是“用户名”数据库字段是user_name可以用拼音首字母或者编辑距离算法匹配。匹配不上的列在控制台高亮提示让你手动指定。这样既减少了配置工作量又不会因为自动匹配错误导致数据写错列。5.2 生成建表语句作为校验有时候 Excel 里的列比数据库表多或者类型不匹配。可以在生成 INSERT 之前先根据 Excel 的列和推断出的类型生成一份CREATE TABLE语句和数据库里实际的表结构做对比。如果发现字段缺失或类型冲突提前报错而不是等到 INSERT 执行失败。推断类型时扫描前 100 行数据如果全是数字用DECIMAL或BIGINT如果包含日期格式用DATETIME否则用VARCHAR(255)。这只是一个粗略推断最终建表语句还是建议人工确认。5.3 用事务和回滚做安全网直连数据库执行时把整个批量插入包在一个事务里。如果中途出错全部回滚数据库不会留下半截数据。但要注意事务太大也会有问题——回滚段膨胀、锁等待超时。折中方案是每 1000 条一个事务出错时只回滚当前批次前面已提交的保留。然后在日志里记录失败的行号和原因修完数据后从失败处继续。我自己的习惯是不管多急先跑--modefile生成 SQL用BEGIN; ... ROLLBACK;在测试库验证一遍确认无误再切--modejdbc写正式库。这个习惯帮我挡掉了至少三次因为 Excel 里隐藏字符导致的批量报错。从那以后我每次处理表格数据都强制走一遍“生成文件→测试库验证→正式库执行”的流程再也没在凌晨被叫起来修数据。希望这份资源和你自己的流程结合起来能让你少加几次班。本文还有配套的精品资源点击获取