Перейти к основному содержимому

Импорт данных в MongoDB из файла JSON с использованием Java

· 7 мин. чтения

1. Введение

В этом руководстве мы узнаем, как читать данные JSON из файлов и импортировать их в MongoDB с помощью Spring Boot . Это может быть полезно по многим причинам: восстановление данных, массовая вставка новых данных или вставка значений по умолчанию. MongoDB использует JSON для внутреннего структурирования своих документов, поэтому, естественно, мы будем использовать его для хранения импортируемых файлов. Будучи простым текстом, эта стратегия также имеет то преимущество, что ее легко сжимать .

Кроме того, мы узнаем, как проверять наши входные файлы на соответствие нашим пользовательским типам, когда это необходимо. Наконец, мы предоставим API, чтобы мы могли использовать его во время выполнения в нашем веб-приложении.

2. Зависимости

Давайте добавим эти зависимости Spring Boot в наш pom.xml :

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

Нам также понадобится работающий экземпляр MongoDB, для которого требуется правильно настроенный файл application.properties .

3. Импорт строк JSON

Самый простой способ импортировать JSON в MongoDB — сначала преобразовать его в объект « org.bson.Document ». Этот класс представляет общий документ MongoDB без определенного типа. Поэтому нам не нужно беспокоиться о создании репозиториев для всех типов объектов, которые мы можем импортировать.

Наша стратегия берет JSON (из файла, ресурса или строки), преобразует его в Document и сохраняет их с помощью MongoTemplate . Пакетные операции, как правило, работают лучше, так как количество обращений туда и обратно уменьшается по сравнению с вставкой каждого объекта по отдельности.

Самое главное, мы будем считать, что наш ввод имеет только один объект JSON на разрыв строки. Таким образом, мы можем легко разграничить наши объекты. Мы инкапсулируем эти функции в два класса, которые мы создадим: ImportUtils и ImportJsonService . Начнем с нашего класса обслуживания:

@Service
public class ImportJsonService {

@Autowired
private MongoTemplate mongo;
}

Далее добавим метод, который анализирует строки JSON в документы:

private List<Document> generateMongoDocs(List<String> lines) {
List<Document> docs = new ArrayList<>();
for (String json : lines) {
docs.add(Document.parse(json));
}
return docs;
}

Затем мы добавляем метод, который вставляет список объектов Document в нужную коллекцию . Также возможен частичный сбой пакетной операции. В этом случае мы можем вернуть количество вставленных документов, проверив причину исключения : ``

private int insertInto(String collection, List<Document> mongoDocs) {
try {
Collection<Document> inserts = mongo.insert(mongoDocs, collection);
return inserts.size();
} catch (DataIntegrityViolationException e) {
if (e.getCause() instanceof MongoBulkWriteException) {
return ((MongoBulkWriteException) e.getCause())
.getWriteResult()
.getInsertedCount();
}
return 0;
}
}

Наконец, давайте объединим эти методы. Этот принимает ввод и возвращает строку, показывающую, сколько строк было прочитано и успешно вставлено:

public String importTo(String collection, List<String> jsonLines) {
List<Document> mongoDocs = generateMongoDocs(jsonLines);
int inserts = insertInto(collection, mongoDocs);
return inserts + "/" + jsonLines.size();
}

4. Варианты использования

Теперь, когда мы готовы обрабатывать входные данные, мы можем создать несколько вариантов использования. Давайте создадим класс ImportUtils , который поможет нам в этом. Этот класс будет отвечать за преобразование ввода в строки JSON. Он будет содержать только статические методы. Начнем с чтения простой строки :

public static List<String> lines(String json) {
String[] split = json.split("[\\r\\n]+");
return Arrays.asList(split);
}

Поскольку мы используем разрывы строк в качестве разделителя, регулярное выражение отлично подходит для разбиения строк на несколько строк. Это регулярное выражение обрабатывает окончания строк как в Unix, так и в Windows. Далее метод преобразования файла в список строк:

public static List<String> lines(File file) {
return Files.readAllLines(file.toPath());
}

Точно так же мы заканчиваем с методом для преобразования ресурса пути к классам в список:

public static List<String> linesFromResource(String resource) {
Resource input = new ClassPathResource(resource);
Path path = input.getFile().toPath();
return Files.readAllLines(path);
}

4.1. Импорт файла во время запуска с помощью CLI

В нашем первом случае мы реализуем функциональность для импорта файла через аргументы приложения. Мы воспользуемся преимуществами интерфейса Spring Boot ApplicationRunner , чтобы сделать это во время загрузки. Например, мы можем прочитать параметры командной строки, чтобы определить файл для импорта:

@SpringBootApplication
public class SpringBootJsonConvertFileApplication implements ApplicationRunner {
private static final String RESOURCE_PREFIX = "classpath:";

@Autowired
private ImportJsonService importService;

public static void main(String ... args) {
SpringApplication.run(SpringBootPersistenceApplication.class, args);
}

@Override
public void run(ApplicationArguments args) {
if (args.containsOption("import")) {
String collection = args.getOptionValues("collection")
.get(0);

List<String> sources = args.getOptionValues("import");
for (String source : sources) {
List<String> jsonLines = new ArrayList<>();
if (source.startsWith(RESOURCE_PREFIX)) {
String resource = source.substring(RESOURCE_PREFIX.length());
jsonLines = ImportUtils.linesFromResource(resource);
} else {
jsonLines = ImportUtils.lines(new File(source));
}

String result = importService.importTo(collection, jsonLines);
log.info(source + " - result: " + result);
}
}
}
}

Используя getOptionValues() , мы можем обработать один или несколько файлов. Эти файлы могут быть либо из нашего пути к классам, либо из нашей файловой системы. Мы различаем их с помощью RESOURCE_PREFIX . Каждый аргумент, начинающийся с « classpath: », будет прочитан из нашей папки ресурсов, а не из файловой системы. После этого все они будут импортированы в нужную коллекцию .

Давайте начнем использовать наше приложение, создав файл в src/main/resources/data.json.log :

{"name":"Book A", "genre": "Comedy"}
{"name":"Book B", "genre": "Thriller"}
{"name":"Book C", "genre": "Drama"}

После построения мы можем использовать следующий пример для его запуска (разрывы строк добавлены для удобочитаемости). В нашем примере будут импортированы два файла: один из пути к классам и один из файловой системы:

java -cp target/spring-boot-persistence-mongodb/WEB-INF/lib/*:target/spring-boot-persistence-mongodb/WEB-INF/classes \
-Djdk.tls.client.protocols=TLSv1.2 \
com.foreach.SpringBootPersistenceApplication \
--import=classpath:data.json.log \
--import=/tmp/data.json \
--collection=books

4.2. Файл JSON из загрузки HTTP POST

Кроме того, если мы создадим REST Controller , у нас будет конечная точка для загрузки и импорта файлов JSON. Для этого нам понадобится параметр MultipartFile :

@RestController
@RequestMapping("/import-json")
public class ImportJsonController {
@Autowired
private ImportJsonService service;

@PostMapping("/file/{collection}")
public String postJsonFile(@RequestPart("parts") MultipartFile jsonStringsFile, @PathVariable String collection) {
List<String> jsonLines = ImportUtils.lines(jsonStringsFile);
return service.importTo(collection, jsonLines);
}
}

Теперь мы можем импортировать файлы с помощью POST , как здесь, где « /tmp/data.json » относится к существующему файлу:

curl -X POST http://localhost:8082/import-json/file/books -F "parts=@/tmp/books.json"

4.3. Сопоставление JSON с определенным типом Java

Мы использовали только JSON, без привязки к какому-либо типу, что является одним из преимуществ работы с MongoDB. Теперь мы хотим проверить наш ввод. В этом случае давайте добавим ObjectMapper , внеся это изменение в нашу службу:

private <T> List<Document> generateMongoDocs(List<String> lines, Class<T> type) {
ObjectMapper mapper = new ObjectMapper();

List<Document> docs = new ArrayList<>();
for (String json : lines) {
if (type != null) {
mapper.readValue(json, type);
}
docs.add(Document.parse(json));
}
return docs;
}

Таким образом, если указан параметр типа , наш преобразователь попытается проанализировать нашу строку JSON как этот тип. И, с конфигурацией по умолчанию, вызовет исключение, если присутствуют какие-либо неизвестные свойства. Вот наше простое определение компонента для работы с репозиторием MongoDB :

@Document("books")
public class Book {
@Id
private String id;
private String name;
private String genre;
// getters and setters
}

А теперь, чтобы использовать улучшенную версию нашего генератора документов, давайте изменим и этот метод:

public String importTo(Class<?> type, List<String> jsonLines) {
List<Document> mongoDocs = generateMongoDocs(jsonLines, type);
String collection = type.getAnnotation(org.springframework.data.mongodb.core.mapping.Document.class)
.value();
int inserts = insertInto(collection, mongoDocs);
return inserts + "/" + jsonLines.size();
}

Теперь вместо передачи имени коллекции мы передаем Class . Мы предполагаем, что у него есть аннотация Document , которую мы использовали в нашей книге , поэтому он может получить имя коллекции. Однако, поскольку и аннотация, и классы Document имеют одно и то же имя, мы должны указать весь пакет.

5. Вывод

В этой статье мы разобрали ввод JSON из файлов, ресурсов или простых строк и импортировали их в MongoDB. Мы централизовали эту функциональность в сервисном классе и служебном классе, чтобы мы могли повторно использовать ее где угодно. Наши варианты использования включали CLI и опцию REST, а также примеры команд о том, как их использовать.

И, как всегда, исходный код доступен на GitHub .