文章目录
- 一、简要概述
- 二、处理流程
-
- [1, 准备文件](#1, 准备文件)
- [2, 核心代码](#2, 核心代码)
- [3, 运行结果](#3, 运行结果)
- [4, 切换Mysql](#4, 切换Mysql)
- 三、小结
一、简要概述
不同数据源之间周期性地执行数据同步是一个很常见的需求。为了保险起见,一般情况下处理流程是先将原始数据导出为中间文件(如 Excel、CSV 等),数据库管理工具如Navicat等几乎都支持。
问题就转换成了:如何将 CSV 文件数据导入数据库表?
二、处理流程
下面我们以 CSV 文件导入 H2 数据库为例进行说明。
1, 准备文件
CSV 文件本质上是普通的文本文件,默认以逗号分割。Spring Boot 工程中,文件路径为 src/test/resources/csv/data.csv,内容如下:
bash
mid,name,slug,type,description,sort,parent
7,程序,,category,,0,0
8,诗词,,category,,0,0
9,散文,,category,,0,0
10,kidylee,https://kidylee.com,link,"",6,0
12,我的码云,https://gitee.com/00fly,link,"",5,0
13,日常,,category,,0,0
14,对联,对联,tag,,0,0
15,恭贺新年,恭贺新年,tag,,0,0
16,吉祥如意,吉祥如意,tag,,0,0
17,故乡,故乡,tag,,0,0
18,祝福,祝福,tag,,0,0
19,祈愿,祈愿,tag,,0,0
20,默认分类,默认分类,category,,0,0
21,鸡汤,,category,,0,0
22,哲理,哲理,tag,,0,0
23,凯利公式,凯利公式,tag,,0,0
25,成长,,category,,0,0
26,教育,教育,tag,,0,0
27,成长,成长,tag,,0,0
28,百科,,category,,0,0
29,国士无双,国士无双,tag,,0,0
30,脊梁,,category,,0,0
31,幽默,,category,,0,0
32,诗词,诗词,tag,,0,0
33,哈哈哈,哈哈哈,tag,,0,0
34,天才,天才,tag,,0,0
35,叶企孙,叶企孙,tag,,0,0
36,科普,科普,tag,,0,0
37,王勃,王勃,tag,,0,0
38,视角,视角,tag,,0,0
39,梅,梅,tag,,0,0
40,束星北,束星北,tag,,0,0
41,李白,李白,tag,,0,0
44,数据库管理,https://soft.00fly.online/treesoft/index,link,"",3,0
45,电脑,电脑,tag,,0,0
46,趣闻,,category,,0,0
47,历史,历史,tag,,0,0
48,联想,联想,tag,,0,0
49,吐槽,,category,,0,0
50,持续集成,https://jks.00fly.online/,link,"",2,0
52,My CSDN,https://blog.csdn.net/qq_16127313,link,"",0,0
53,新版博客,http://1.117.164.5,link,"",0,0
54,春节,春节,tag,,0,0
55,鼠年,鼠年,tag,,0,0
2, 核心代码
java
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.util.StopWatch;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CsvToH2Test
{
long batch = 10;
Path path = Paths.get("src/test/resources/csv/data.csv");
JdbcTemplate jdbcTemplate = new JdbcTemplate(new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).setName("test").setScriptEncoding("UTF-8").build());
@Test
public void test1()
throws IOException, InterruptedException
{
File file = path.toFile();
log.info("{}", FileUtils.byteCountToDisplaySize(FileUtils.sizeOf(file)));
StopWatch clock = new StopWatch();
clock.start();
String title;
try (Stream<String> lines = Files.lines(path))
{
title = lines.findFirst().orElse("");
log.info("标题行:{}", title);
createTable(title);
}
int length = StringUtils.split(title, ",").length;
List<Object[]> params = new ArrayList<>();
String batchInsert = createBatchInsert(title);
try (Stream<String> dataLines = Files.lines(path))
{
long total = dataLines.count(); // 总行数
log.info(">>>>> total: {}", total);
for (long i = 0; i < total; i += batch)
{
params.clear();
try (Stream<String> lines = Files.lines(path))
{
// 跳过表头行(注意:不能用StringUtils.split,此方法会忽略空值)
lines.skip(i + 1).limit(batch).forEach(line -> params.add(ArrayUtils.subarray(line.split(","), 0, length)));
jdbcTemplate.batchUpdate(batchInsert, params);
}
log.info(">>>>> params size: {}", params.size());
}
}
clock.stop();
log.info("{}", clock.prettyPrint());
// 打印数据表数据(注意大数据量OOM风险)
AtomicLong count = new AtomicLong(0);
long total = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM `data_csv`", Long.class);
for (long i = 0; i < total; i += batch)
{
log.info("##### start: {}", i);
jdbcTemplate.queryForList("SELECT * FROM `data_csv` LIMIT ?, ?", i, batch).forEach(map -> log.info("{}. {}", count.incrementAndGet(), map));
}
}
/**
* 执行建表
*
* @param title
* @see [类、类#方法、类#成员]
*/
private void createTable(String title)
{
StringBuffer sql = new StringBuffer("CREATE TABLE IF NOT EXISTS `data_csv` (\n");
String script = Stream.of(StringUtils.split(title, ",")).map(column -> StringUtils.join("`", column, "` varchar(50)")).collect(Collectors.joining(", \n"));
sql.append(script).append(");");
jdbcTemplate.execute(sql.toString());
log.info("####\n{}", sql);
}
private String createBatchInsert(String title)
{
String[] columns = StringUtils.split(title, ",");
StringBuffer sql = new StringBuffer("INSERT INTO `data_csv` (");
String script = Stream.of(columns).map(column -> StringUtils.join("`", column, "`")).collect(Collectors.joining(", "));
sql.append(script).append(") VALUES (").append(Stream.of(columns).map(column -> "?").collect(Collectors.joining(", "))).append(")");
log.info("#### {}", sql);
return sql.toString();
}
}
3, 运行结果
bash
2026-11-12 12:42:12.556 [main] INFO .j.d.e.EmbeddedDatabaseFactory - Starting embedded database: url='jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'
2026-11-12 12:42:13.233 [main] INFO c.f.c.CsvToH2Test - 1 KB
2026-11-12 12:42:13.236 [main] INFO c.f.c.CsvToH2Test - 标题行:mid,name,slug,type,description,sort,parent
2026-11-12 12:42:13.257 [main] INFO c.f.c.CsvToH2Test - ####
CREATE TABLE IF NOT EXISTS `data_csv` (
`mid` varchar(50),
`name` varchar(50),
`slug` varchar(50),
`type` varchar(50),
`description` varchar(50),
`sort` varchar(50),
`parent` varchar(50));
2026-11-12 12:42:13.259 [main] INFO c.f.c.CsvToH2Test - #### INSERT INTO `data_csv` (`mid`, `name`, `slug`, `type`, `description`, `sort`, `parent`) VALUES (?, ?, ?, ?, ?, ?, ?)
2026-11-12 12:42:13.260 [main] INFO c.f.c.CsvToH2Test - >>>>> total: 45
2026-11-12 12:42:13.297 [main] INFO c.f.c.CsvToH2Test - >>>>> params size: 10
2026-11-12 12:42:13.302 [main] INFO c.f.c.CsvToH2Test - >>>>> params size: 10
2026-11-12 12:42:13.316 [main] INFO c.f.c.CsvToH2Test - >>>>> params size: 10
2026-11-12 12:42:13.320 [main] INFO c.f.c.CsvToH2Test - >>>>> params size: 10
2026-11-12 12:42:13.323 [main] INFO c.f.c.CsvToH2Test - >>>>> params size: 4
2026-11-12 12:42:13.325 [main] INFO c.f.c.CsvToH2Test - StopWatch '': running time = 88300798 ns
---------------------------------------------
ns % Task name
---------------------------------------------
088300798 100%
2026-11-12 12:42:13.445 [main] INFO c.f.c.CsvToH2Test - ##### start: 0
2026-11-12 12:42:13.454 [main] INFO c.f.c.CsvToH2Test - 1. {MID=7, NAME=程序, SLUG=, TYPE=category, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.457 [main] INFO c.f.c.CsvToH2Test - 2. {MID=8, NAME=诗词, SLUG=, TYPE=category, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.457 [main] INFO c.f.c.CsvToH2Test - 3. {MID=9, NAME=散文, SLUG=, TYPE=category, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.457 [main] INFO c.f.c.CsvToH2Test - 4. {MID=10, NAME=kidylee, SLUG=https://kidylee.com, TYPE=link, DESCRIPTION="", SORT=6, PARENT=0}
2026-11-12 12:42:13.458 [main] INFO c.f.c.CsvToH2Test - 5. {MID=12, NAME=我的码云, SLUG=https://gitee.com/00fly, TYPE=link, DESCRIPTION="", SORT=5, PARENT=0}
2026-11-12 12:42:13.458 [main] INFO c.f.c.CsvToH2Test - 6. {MID=13, NAME=日常, SLUG=, TYPE=category, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.458 [main] INFO c.f.c.CsvToH2Test - 7. {MID=14, NAME=对联, SLUG=对联, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.458 [main] INFO c.f.c.CsvToH2Test - 8. {MID=15, NAME=恭贺新年, SLUG=恭贺新年, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.458 [main] INFO c.f.c.CsvToH2Test - 9. {MID=16, NAME=吉祥如意, SLUG=吉祥如意, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.458 [main] INFO c.f.c.CsvToH2Test - 10. {MID=17, NAME=故乡, SLUG=故乡, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.459 [main] INFO c.f.c.CsvToH2Test - ##### start: 10
2026-11-12 12:42:13.462 [main] INFO c.f.c.CsvToH2Test - 11. {MID=18, NAME=祝福, SLUG=祝福, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.462 [main] INFO c.f.c.CsvToH2Test - 12. {MID=19, NAME=祈愿, SLUG=祈愿, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.462 [main] INFO c.f.c.CsvToH2Test - 13. {MID=20, NAME=默认分类, SLUG=默认分类, TYPE=category, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.463 [main] INFO c.f.c.CsvToH2Test - 14. {MID=21, NAME=鸡汤, SLUG=, TYPE=category, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.463 [main] INFO c.f.c.CsvToH2Test - 15. {MID=22, NAME=哲理, SLUG=哲理, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.464 [main] INFO c.f.c.CsvToH2Test - 16. {MID=23, NAME=凯利公式, SLUG=凯利公式, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.464 [main] INFO c.f.c.CsvToH2Test - 17. {MID=25, NAME=成长, SLUG=, TYPE=category, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.465 [main] INFO c.f.c.CsvToH2Test - 18. {MID=26, NAME=教育, SLUG=教育, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.465 [main] INFO c.f.c.CsvToH2Test - 19. {MID=27, NAME=成长, SLUG=成长, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.465 [main] INFO c.f.c.CsvToH2Test - 20. {MID=28, NAME=百科, SLUG=, TYPE=category, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.465 [main] INFO c.f.c.CsvToH2Test - ##### start: 20
2026-11-12 12:42:13.468 [main] INFO c.f.c.CsvToH2Test - 21. {MID=29, NAME=国士无双, SLUG=国士无双, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.468 [main] INFO c.f.c.CsvToH2Test - 22. {MID=30, NAME=脊梁, SLUG=, TYPE=category, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.468 [main] INFO c.f.c.CsvToH2Test - 23. {MID=31, NAME=幽默, SLUG=, TYPE=category, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.468 [main] INFO c.f.c.CsvToH2Test - 24. {MID=32, NAME=诗词, SLUG=诗词, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.469 [main] INFO c.f.c.CsvToH2Test - 25. {MID=33, NAME=哈哈哈, SLUG=哈哈哈, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.469 [main] INFO c.f.c.CsvToH2Test - 26. {MID=34, NAME=天才, SLUG=天才, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.469 [main] INFO c.f.c.CsvToH2Test - 27. {MID=35, NAME=叶企孙, SLUG=叶企孙, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.469 [main] INFO c.f.c.CsvToH2Test - 28. {MID=36, NAME=科普, SLUG=科普, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.469 [main] INFO c.f.c.CsvToH2Test - 29. {MID=37, NAME=王勃, SLUG=王勃, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.469 [main] INFO c.f.c.CsvToH2Test - 30. {MID=38, NAME=视角, SLUG=视角, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.470 [main] INFO c.f.c.CsvToH2Test - ##### start: 30
2026-11-12 12:42:13.473 [main] INFO c.f.c.CsvToH2Test - 31. {MID=39, NAME=梅, SLUG=梅, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.473 [main] INFO c.f.c.CsvToH2Test - 32. {MID=40, NAME=束星北, SLUG=束星北, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.473 [main] INFO c.f.c.CsvToH2Test - 33. {MID=41, NAME=李白, SLUG=李白, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.474 [main] INFO c.f.c.CsvToH2Test - 34. {MID=44, NAME=数据库管理, SLUG=https://soft.00fly.online/treesoft/index, TYPE=link, DESCRIPTION="", SORT=3, PARENT=0}
2026-11-12 12:42:13.474 [main] INFO c.f.c.CsvToH2Test - 35. {MID=45, NAME=电脑, SLUG=电脑, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.474 [main] INFO c.f.c.CsvToH2Test - 36. {MID=46, NAME=趣闻, SLUG=, TYPE=category, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.474 [main] INFO c.f.c.CsvToH2Test - 37. {MID=47, NAME=历史, SLUG=历史, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.474 [main] INFO c.f.c.CsvToH2Test - 38. {MID=48, NAME=联想, SLUG=联想, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.474 [main] INFO c.f.c.CsvToH2Test - 39. {MID=49, NAME=吐槽, SLUG=, TYPE=category, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.475 [main] INFO c.f.c.CsvToH2Test - 40. {MID=50, NAME=持续集成, SLUG=https://jks.00fly.online/, TYPE=link, DESCRIPTION="", SORT=2, PARENT=0}
2026-11-12 12:42:13.475 [main] INFO c.f.c.CsvToH2Test - ##### start: 40
2026-11-12 12:42:13.477 [main] INFO c.f.c.CsvToH2Test - 41. {MID=52, NAME=My CSDN, SLUG=https://blog.csdn.net/qq_16127313, TYPE=link, DESCRIPTION="", SORT=0, PARENT=0}
2026-11-12 12:42:13.477 [main] INFO c.f.c.CsvToH2Test - 42. {MID=53, NAME=新版博客, SLUG=http://1.117.164.5, TYPE=link, DESCRIPTION="", SORT=0, PARENT=0}
2026-11-12 12:42:13.478 [main] INFO c.f.c.CsvToH2Test - 43. {MID=54, NAME=春节, SLUG=春节, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
2026-11-12 12:42:13.479 [main] INFO c.f.c.CsvToH2Test - 44. {MID=55, NAME=鼠年, SLUG=鼠年, TYPE=tag, DESCRIPTION=, SORT=0, PARENT=0}
4, 切换Mysql
如果目标数据库为Mysql 可在springboot工程中配置系统数据源,上面的代码修改为
yaml
spring:
datasource:
url: ${jdbc.url}
username: ${jdbc.username}
password: ${jdbc.password}
driverClassName: ${jdbc.driverClassName}
type: com.zaxxer.hikari.HikariDataSource
hikari:
auto-commit: true
connection-test-query: SELECT 1
connection-timeout: 30000
idle-timeout: 30000
max-lifetime: 1800000
maximum-pool-size: 15
minimum-idle: 5
pool-name: DatebookHikariCP
jdbc-mysql.properties
yaml
jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true&createDatabaseIfNotExist=true
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.username=root
jdbc.password=root123
java
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class Csv2MysqlTest
{
@Resource
JdbcTemplate jdbcTemplate;
......
三、小结
核心步骤为三步:读取表头建表、按批构造 INSERT 批量插入、分批读取校验。
数据量增大时需注意:改用流式读取避免全量载入内存,控制批次大小并显式管理事务提交,同时根据样本推断字段类型,避免一律 varchar(50) 导致截断或精度丢失。
朋友们,你学会了吗?
--over--