java - 持久化数千个实体的最有效方法是什么?
问题描述
我有相当大的 CSV 文件,我需要对其进行解析然后保存到 PostgreSQL 中。例如,一个文件包含 2_070_000 条记录,我能够在大约 8 分钟内解析并保留这些记录(单线程)。是否可以使用多个线程来持久化它们?
public void importCsv(MultipartFile csvFile, Class<T> targetClass) {
final var headerMapping = getHeaderMapping(targetClass);
File tempFile = null;
try {
final var randomUuid = UUID.randomUUID().toString();
tempFile = File.createTempFile("data-" + randomUuid, "csv");
csvFile.transferTo(tempFile);
final var csvFileName = csvFile.getOriginalFilename();
final var csvReader = new BufferedReader(new FileReader(tempFile, StandardCharsets.UTF_8));
Stopwatch stopWatch = Stopwatch.createStarted();
log.info("Starting to import {}", csvFileName);
final var csvRecords = CSVFormat.DEFAULT
.withDelimiter(';')
.withHeader(headerMapping.keySet().toArray(String[]::new))
.withSkipHeaderRecord(true)
.parse(csvReader);
final var models = StreamSupport.stream(csvRecords.spliterator(), true)
.map(record -> parseRecord(record, headerMapping, targetClass))
.collect(Collectors.toUnmodifiableList());
// How to save such a large list?
log.info("Finished import of {} in {}", csvFileName, stopWatch);
} catch (IOException ex) {
ex.printStackTrace();
} finally {
tempFile.delete();
}
}
模型包含很多记录。解析成记录是使用并行流完成的,所以速度非常快。我害怕调用SimpleJpaRepository.saveAll,因为我不确定它会在幕后做什么。
问题是:持久化如此庞大的实体列表最有效的方法是什么?
PS:非常感谢任何其他改进。
解决方案
您必须使用批量插入。
- 为自定义存储库创建接口
SomeRepositoryCustom
public interface SomeRepositoryCustom {
void batchSave(List<Record> records);
}
- 创建一个实现
SomeRepositoryCustom
@Repository
class SomesRepositoryCustomImpl implements SomeRepositoryCustom {
private JdbcTemplate template;
@Autowired
public SomesRepositoryCustomImpl(JdbcTemplate template) {
this.template = template;
}
@Override
public void batchSave(List<Record> records) {
final String sql = "INSERT INTO RECORDS(column_a, column_b) VALUES (?, ?)";
template.execute(sql, (PreparedStatementCallback<Void>) ps -> {
for (Record record : records) {
ps.setString(1, record.getA());
ps.setString(2, record.getB());
ps.addBatch();
}
ps.executeBatch();
return null;
});
}
}
- 扩展你
JpaRepository
的SomeRepositoryCustom
@Repository
public interface SomeRepository extends JpaRepository, SomeRepositoryCustom {
}
保存
someRepository.batchSave(records);
笔记
请记住,即使您使用批量插入,数据库驱动程序也不会使用它们。例如,对于 MySQL,需要在rewriteBatchedStatements=true
数据库 URL 中添加一个参数。所以最好启用驱动程序 SQL 日志记录(不是 Hibernate)来验证一切。也可用于调试驱动程序代码。
您需要决定在循环中按数据包拆分记录
for (Record record : records) {
}
司机可以为您完成,因此您不需要它。但最好也调试这个东西。
PS 不要var
到处使用。
推荐阅读
- soap - 实用地在 SOAP 1.1 和 SOAP 1.2 之间动态切换的最佳库
- servicenow - 如何将示例项目 xml 文件导入 ServiceNow 开发者实例?
- python - 无法导入名称“imread”、“imresize”、“imsave”anaconda(Windows)
- c# - 通过正则表达式模式匹配使用字符串生成器替换多次出现的字符串
- flutter - 单击按钮时显示小部件的错误文本
- spring-cloud-feign - OAuth2认证的feign客户端不支持异步调用?
- django - 发送 post 请求后删除数据表
- java - Servlet 请求的 Java 并发问题
- node.js - 如何配置时刻不显示:弃用警告:提供的值不是可识别的 RFC2822 或 ISO 格式
- oracle - 如何在 NIFI 的 Oracle 的 DBCPConnectionPool 中设置 useUnicode=yes& characterEncoding=UTF-8?