量数据导出到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);@Asyncpublic 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中表示一个数组。 - 使用
PipedOutputStream和PipedInputStream链接SequenceWriter转到InputStream然后传递给存储服务。如果我们显式地处理文件,就不需要这样做--只需传递一个FileOutputStream就行了。但是,您可能希望以不同的方式存储文件,例如在AmazonS 3中,并且在那里,putObject调用需要一个InputStream来读取数据并将其存储在S3中。因此,实际上,您正在写入一个OutputStream,它直接写到InputStream,当被攻击时,InputStream将所有的内容都写入另一个OutputStream - 存储文件是在一个单独的线程中调用的,这样对文件的写入不会阻塞当前线程,该线程的目的是从数据库中读取。同样,如果使用了简单的FileOutputStream,则不需要这样做。
- 整个方法被标记为@异步(Spring),这样它就不会阻止执行--它将被调用并在准备就绪时结束(使用内部SpringExecutor服务和一个有限的线程池)
- 此处不显示数据库批处理读取代码,因为它根据数据库的不同而有所不同。关键是,您应该分批获取数据,而不是从X中选择*。
- OutputStream封装在GZIPOutputStream中,因为具有重复元素的文本文件(如JSON)从压缩中明显受益
主要的工作是由杰克逊的SequenceWriter完成的,(有点明显的)要点是--不要假设您的数据会被存储在内存中。它几乎从来没有这样做过,所以每件事都是分批和增量写的

浙公网安备 33010602011771号