1. Einführung
In diesem Tutorial lernen wir, wie man JSON-Daten aus Dateien liest und sie mit Spring Boot in MongoDB importiert. Dies kann aus vielen Gründen nützlich sein:Wiederherstellen von Daten, Masseneinfügen neuer Daten oder Einfügen von Standardwerten. MongoDB verwendet intern JSON, um seine Dokumente zu strukturieren, also verwenden wir das natürlich, um importierbare Dateien zu speichern. Da es sich um reinen Text handelt, hat diese Strategie auch den Vorteil, dass sie leicht komprimierbar ist.
Darüber hinaus lernen wir, wie wir unsere Eingabedateien bei Bedarf anhand unserer benutzerdefinierten Typen validieren. Schließlich stellen wir eine API bereit, damit wir sie während der Laufzeit in unserer Webanwendung verwenden können.
2. Abhängigkeiten
Lassen Sie uns diese Spring Boot-Abhängigkeiten zu unserer pom.xml hinzufügen :
<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>
Außerdem benötigen wir eine laufende Instanz von MongoDB, was eine ordnungsgemäß konfigurierte application.properties erfordert Datei.
3. Importieren von JSON-Strings
Der einfachste Weg, JSON in MongoDB zu importieren, besteht darin, es in ein „org.bson.Document zu konvertieren ” Objekt zuerst. Diese Klasse stellt ein generisches MongoDB-Dokument ohne bestimmten Typ dar. Daher müssen wir uns keine Gedanken über die Erstellung von Repositories für alle Arten von Objekten machen, die wir möglicherweise importieren.
Unsere Strategie nimmt JSON (aus einer Datei, Ressource oder Zeichenfolge) und konvertiert es in Dokument s und speichert sie mit MongoTemplate . Batch-Operationen funktionieren im Allgemeinen besser, da die Anzahl der Roundtrips im Vergleich zum einzelnen Einfügen jedes Objekts reduziert wird.
Am wichtigsten ist, dass wir davon ausgehen, dass unsere Eingabe nur ein JSON-Objekt pro Zeilenumbruch enthält. Auf diese Weise können wir unsere Objekte leicht abgrenzen. Wir kapseln diese Funktionalitäten in zwei Klassen, die wir erstellen werden:ImportUtils und ImportJsonService . Beginnen wir mit unserer Serviceklasse:
@Service
public class ImportJsonService {
@Autowired
private MongoTemplate mongo;
}
Als Nächstes fügen wir eine Methode hinzu, die JSON-Zeilen in Dokumente parst:
private List<Document> generateMongoDocs(List<String> lines) {
List<Document> docs = new ArrayList<>();
for (String json : lines) {
docs.add(Document.parse(json));
}
return docs;
}
Dann fügen wir eine Methode hinzu, die eine Liste von Dokumenten einfügt Objekte in die gewünschte Sammlung . Außerdem ist es möglich, dass der Stapelvorgang teilweise fehlschlägt. In diesem Fall können wir die Anzahl der eingefügten Dokumente zurückgeben, indem wir die Ursache überprüfen der Ausnahme :
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;
}
}
Lassen Sie uns diese Methoden schließlich kombinieren. Dieser nimmt die Eingabe und gibt einen String zurück, der anzeigt, wie viele Zeilen gelesen bzw. erfolgreich eingefügt wurden:
public String importTo(String collection, List<String> jsonLines) {
List<Document> mongoDocs = generateMongoDocs(jsonLines);
int inserts = insertInto(collection, mongoDocs);
return inserts + "/" + jsonLines.size();
}
4. Anwendungsfälle
Jetzt, da wir bereit sind, Eingaben zu verarbeiten, können wir einige Anwendungsfälle erstellen. Lassen Sie uns die ImportUtils erstellen Klasse uns dabei zu helfen. Diese Klasse ist für die Konvertierung von Eingaben in JSON-Zeilen verantwortlich. Es enthält nur statische Methoden. Beginnen wir mit dem zum Lesen eines einfachen String :
public static List<String> lines(String json) {
String[] split = json.split("[\\r\\n]+");
return Arrays.asList(split);
}
Da wir Zeilenumbrüche als Trennzeichen verwenden, funktioniert Regex hervorragend, um Zeichenfolgen in mehrere Zeilen aufzuteilen. Diese Regex behandelt sowohl Unix- als auch Windows-Zeilenenden. Als nächstes eine Methode zum Konvertieren einer Datei in eine Liste von Zeichenfolgen:
public static List<String> lines(File file) {
return Files.readAllLines(file.toPath());
}
Auf ähnliche Weise schließen wir mit einer Methode ab, um eine Klassenpfad-Ressource in eine Liste umzuwandeln:
public static List<String> linesFromResource(String resource) {
Resource input = new ClassPathResource(resource);
Path path = input.getFile().toPath();
return Files.readAllLines(path);
}
4.1. Datei während des Starts mit einer CLI importieren
In unserem ersten Anwendungsfall implementieren wir Funktionen zum Importieren einer Datei über Anwendungsargumente. Wir nutzen Spring Boot ApplicationRunner Schnittstelle, um dies beim Booten zu tun. Zum Beispiel können wir Befehlszeilenparameter lesen, um die zu importierende Datei zu definieren:
@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);
}
}
}
}
Verwenden von getOptionValues() wir können eine oder mehrere Dateien verarbeiten. Diese Dateien können entweder aus unserem Klassenpfad oder aus unserem Dateisystem stammen. Wir unterscheiden sie mit dem RESOURCE_PREFIX . Jedes Argument, das mit „classpath: “ wird aus unserem Ressourcenordner statt aus dem Dateisystem gelesen. Danach werden sie alle in die gewünschte Sammlung importiert .
Beginnen wir mit der Verwendung unserer Anwendung, indem wir eine Datei unter src/main/resources/data.json.log erstellen :
{"name":"Book A", "genre": "Comedy"}
{"name":"Book B", "genre": "Thriller"}
{"name":"Book C", "genre": "Drama"}
Nach dem Erstellen können wir das folgende Beispiel verwenden, um es auszuführen (Zeilenumbrüche zur besseren Lesbarkeit hinzugefügt). In unserem Beispiel werden zwei Dateien importiert, eine aus dem Klassenpfad und eine aus dem Dateisystem:
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-Datei vom HTTP-POST-Upload
Wenn wir einen REST-Controller erstellen, verfügen wir außerdem über einen Endpunkt zum Hochladen und Importieren von JSON-Dateien. Dafür benötigen wir eine MultipartFile Parameter:
@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);
}
}
Jetzt können wir Dateien mit einem POST wie diesem importieren, wobei „/tmp/data.json ” bezieht sich auf eine vorhandene Datei:
curl -X POST http://localhost:8082/import-json/file/books -F "[email protected]/tmp/books.json"
4.3. Zuordnen von JSON zu einem bestimmten Java-Typ
Wir haben nur JSON verwendet, das an keinen Typ gebunden ist, was einer der Vorteile der Arbeit mit MongoDB ist. Nun wollen wir unsere Eingabe validieren. Fügen wir in diesem Fall einen ObjectMapper hinzu indem Sie diese Änderung an unserem Service vornehmen:
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;
}
Auf diese Weise, wenn der Typ Parameter angegeben ist, unser mapper versucht, unsere JSON-Zeichenfolge als diesen Typ zu analysieren. Und löst mit der Standardkonfiguration eine Ausnahme aus, wenn unbekannte Eigenschaften vorhanden sind. Hier ist unsere einfache Bean-Definition für die Arbeit mit einem MongoDB-Repository:
@Document("books")
public class Book {
@Id
private String id;
private String name;
private String genre;
// getters and setters
}
Um nun die verbesserte Version unseres Dokumentengenerators zu verwenden, ändern wir auch diese Methode:
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();
}
Anstatt den Namen einer Sammlung zu übergeben, übergeben wir jetzt eine Klasse . Wir gehen davon aus, dass es das Dokument enthält Anmerkung, wie wir sie in unserem Buch verwendet haben , damit der Sammlungsname abgerufen werden kann. Da jedoch sowohl die Anmerkung als auch das Dokument Klassen den gleichen Namen haben, müssen wir das ganze Paket spezifizieren.