Иногда вам нужно экспортировать много данных в файл JSON. Может быть, это «экспорт всех данных в JSON» или «Право на мобильность» GDPR, где вам нужно сделать то же самое.
И как с любым большим набором данных, вы не можете просто поместить все это в память и записать это в файл. Это занимает некоторое время, он читает много записей из базы данных, и вы должны быть осторожны, чтобы такие экспорты не перегружали всю систему или не исчерпывали память.
К счастью, это довольно просто сделать с помощью SequenceWriter
Джексона и, возможно, потоковых каналов. Вот как это будет выглядеть:
01
02
03
04
05
06
07
08
09
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
|
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); } } // wait for storing to complete storageFuture.get(); } 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 ); } } |
Код делает несколько вещей:
- Использует SequenceWriter для непрерывной записи записей. Он инициализируется с помощью OutputStream, в который все записано. Это может быть простой FileOutputStream или конвейерный поток, как описано ниже. Обратите внимание, что именование здесь немного вводит в заблуждение —
writeValues(out)
звучит так, как будто вы даете указание автору написать что-то сейчас; вместо этого он настраивает его для использования определенного потока позже. -
SequenceWriter
инициализируется значениемtrue
, что означает «перенос в массив». Вы пишете много одинаковых записей, поэтому они должны представлять массив в окончательном JSON. - Использует
PipedOutputStream
иPipedOutputStream
чтобы связатьSequenceWriter
сInputStream
который затем передается службе хранения. Если бы мы явно работали с файлами, в этом не было бы необходимости — достаточно просто передатьFileOutputStream
. Однако вы можете захотеть сохранить файл по-другому, например, в Amazon S3, и там для вызова putObject требуется InputStream, из которого можно прочитать данные и сохранить их в S3. Таким образом, в сущности, вы пишете в OutputStream, который напрямую записывается в InputStream, который, когда его запрашивают для чтения, получает все записанное в другой OutputStream - Сохранение файла вызывается в отдельном потоке, поэтому запись в файл не блокирует текущий поток, целью которого является чтение из базы данных. Опять же, это не будет необходимо, если используется простой FileOutputStream.
- Весь метод помечается как @Async (spring), чтобы он не блокировал выполнение — он вызывается и завершается, когда готов (используя внутренний сервис-исполнитель Spring с ограниченным пулом потоков)
- Код пакетного чтения базы данных здесь не показан, так как он варьируется в зависимости от базы данных. Суть в том, что вы должны получать данные в пакетном режиме, а не SELECT * FROM X.
- OutputStream заключен в GZIPOutputStream, поскольку текстовые файлы, такие как JSON с повторяющимися элементами, значительно выигрывают от сжатия
Основная работа выполняется SequenceWriter Джексона, и (вроде бы очевидный) момент, который стоит взять домой, — не предполагайте, что ваши данные поместятся в памяти. Это почти никогда не происходит, так что делайте все партиями и добавочными записями.
Опубликовано на Java Code Geeks с разрешения Божидара Божанова, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Написание больших файлов JSON с Джексоном
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |