【问题标题】:What is the most efficient way to persist thousands of entities?持久化数千个实体的最有效方法是什么?
【发布时间】:2021-09-28 16:27:19
【问题描述】:

我有相当大的 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();
        }
    }

models 包含很多记录。解析成记录是使用并行流完成的,所以速度非常快。我不敢调用 SimpleJpaRepository.saveAll,因为我不确定它在后台会做什么。

问题是:持久化如此庞大的实体列表最有效的方法是什么?

P.S.:非常感谢任何其他改进。

【问题讨论】:

  • 目标数据库的架构设计有不同的列对应于 csv 列?
  • 使用CopyManager API,它是copy ... from stdin的Java包装器
  • 看起来像是操作动作。如果你正在开发一个运营服务,这没问题,否则你最好不要为这个管理任务编写代码。
  • 放弃流,使用经典的 for 循环。保存每个单独的项目和每 x 个项目(比如 100 个)对实体管理器进行刷新和清除。

标签: java postgresql spring-boot hibernate spring-data-jpa


【解决方案1】:

您必须使用批量插入。

  1. 为自定义存储库创建接口SomeRepositoryCustom
public interface SomeRepositoryCustom {

    void batchSave(List<Record> records);

}
  1. 创建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;
        });
    }

}
  1. SomeRepositoryCustom 扩展您的JpaRepository
@Repository
public interface SomeRepository extends JpaRepository, SomeRepositoryCustom {

}

保存

someRepository.batchSave(records);

备注

请记住,即使您使用批量插入,数据库驱动程序也不会使用它们。例如,对于 MySQL,需要在数据库 URL 中添加参数rewriteBatchedStatements=true。 所以最好启用驱动程序 SQL 日志记录(不是 Hibernate)来验证一切。也可用于调试驱动程序代码。

您需要决定是否在循环中按数据包拆分记录

    for (Record record : records) { 

    }

司机可以为你做这件事,所以你不需要它。但最好也调试一下这个东西。

P。 S. 不要到处使用var

【讨论】:

  • 您可以使用 JPA 更轻松地做到这一点,您不需要 SQL。
  • @M.Deinum 怎么样?可以分享一个链接吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-04
  • 2010-09-28
  • 1970-01-01
  • 2021-04-15
  • 2010-12-29
  • 1970-01-01
相关资源
最近更新 更多