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 (от файл, ресурс или низ), преобразува го в Документ s и ги запазва с помощта на 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;
}
След това добавяме метод, който вмъква списък с Документ обекти в желаната колекция . Също така е възможно пакетната операция частично да се провали. В този случай можем да върнем броя на вмъкнатите документи, като проверим причината на изключението :
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. Той ще съдържа само статични методи. Нека започнем с този за четене на обикновен String :
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.baeldung.SpringBootPersistenceApplication \
--import=classpath:data.json.log \
--import=/tmp/data.json \
--collection=books
4.2. JSON файл от HTTP POST качване
Освен това, ако създадем REST контролер, ще имаме крайна точка за качване и импортиране на 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 "[email protected]/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;
}
По този начин, ако тип параметърът е посочен, нашият mapper ще се опита да анализира нашия JSON низ като този тип. И с конфигурация по подразбиране, ще изведе изключение, ако има някакви неизвестни свойства. Ето нашата проста дефиниция на bean за работа с хранилище 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 . Предполагаме, че има Документа анотация, както използвахме в нашата Книга , така че може да извлече името на колекцията. Тъй като обаче и анотацията, и Документът класовете имат едно и също име, трябва да посочим целия пакет.