量数据导出到JSON文件中

有时,您需要将大量数据导出到JSON文件中。可能是“将所有数据导出到JSON”,或者是GDPR“可移植性的权利”,您实际上需要这样做。

与任何大型数据集一样,您不能将其全部放入内存中并将其写入文件。这需要一段时间,它从数据库中读取了很多条目,您需要小心,不要使这种导出重载整个系统,或者耗尽内存。

幸运的是,在杰克逊的帮助下,这样做很简单SequenceWriter和可选的管道流。下面是它的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
private ObjectMapper jsonMapper = new ObjectMapper();
private ExecutorService executorService = Executors.newFixedThreadPool(5);
 
@Async
public ListenableFuture<Boolean> export(UUID customerId) {
    try (PipedInputStream in = new PipedInputStream();
            PipedOutputStream pipedOut = new PipedOutputStream(in);
            GZIPOutputStream out = new GZIPOutputStream(pipedOut)) {
     
        Stopwatch stopwatch = Stopwatch.createStarted();
 
        ObjectWriter writer = jsonMapper.writer().withDefaultPrettyPrinter();
 
        try(SequenceWriter sequenceWriter = writer.writeValues(out)) {
            sequenceWriter.init(true);
         
            Future<?> storageFuture = executorService.submit(() ->
                   storageProvider.storeFile(getFilePath(customerId), in));
 
            int batchCounter = 0;
            while (true) {
                List<Record> batch = readDatabaseBatch(batchCounter++);
                for (Record record : batch) {
                    sequenceWriter.write(entry);
                }
                if (batch.isEmpty()) {
                    // if there are no more batches, stop.
                    break;
                }
            }
 
            // wait for storing to complete
            storageFuture.get();
 
            // send the customer a notification and a download link
            notifyCustomer(customerId);
        
 
        logger.info("Exporting took {} seconds", stopwatch.stop().elapsed(TimeUnit.SECONDS));
 
        return AsyncResult.forValue(true);
    } catch (Exception ex) {
        logger.error("Failed to export data", ex);
        return AsyncResult.forValue(false);
    }
}

代码做了几件事:

http://www.itangyuan.com/book/16245130.html

https://www.wenjuan.com/s/UZBZJvTYoO/

  • 使用SequenceWriter连续写入记录。它是用OutputStream初始化的,所有内容都写入到OutputStream中。这可能是一个简单的FileOutputStream,或者如下所述的管道流。注意这里的命名有点误导-writeValues(out)听起来,您现在正在指示作者编写一些东西;相反,它将其配置为稍后使用特定的流。
  • 这个SequenceWriter初始化为true,意思是“在数组中包装”。您正在编写许多相同的记录,因此它们应该在最终的JSON中表示一个数组。
  • 使用PipedOutputStreamPipedInputStream链接SequenceWriter转到InputStream然后传递给存储服务。如果我们显式地处理文件,就不需要这样做--只需传递一个FileOutputStream就行了。但是,您可能希望以不同的方式存储文件,例如在AmazonS 3中,并且在那里,putObject调用需要一个InputStream来读取数据并将其存储在S3中。因此,实际上,您正在写入一个OutputStream,它直接写到InputStream,当被攻击时,InputStream将所有的内容都写入另一个OutputStream
  • 存储文件是在一个单独的线程中调用的,这样对文件的写入不会阻塞当前线程,该线程的目的是从数据库中读取。同样,如果使用了简单的FileOutputStream,则不需要这样做。
  • 整个方法被标记为@异步(Spring),这样它就不会阻止执行--它将被调用并在准备就绪时结束(使用内部SpringExecutor服务和一个有限的线程池)
  • 此处不显示数据库批处理读取代码,因为它根据数据库的不同而有所不同。关键是,您应该分批获取数据,而不是从X中选择*。
  • OutputStream封装在GZIPOutputStream中,因为具有重复元素的文本文件(如JSON)从压缩中明显受益

主要的工作是由杰克逊的SequenceWriter完成的,(有点明显的)要点是--不要假设您的数据会被存储在内存中。它几乎从来没有这样做过,所以每件事都是分批和增量写的

posted @ 2021-09-02 20:41  javd9w  阅读(266)  评论(0)    收藏  举报