1. Wprowadzenie
W tym samouczku nauczymy się odczytywać dane JSON z plików i importować je do MongoDB za pomocą Spring Boot. Może to być przydatne z wielu powodów:przywracanie danych, zbiorcze wstawianie nowych danych lub wstawianie wartości domyślnych. MongoDB używa wewnętrznie JSON do strukturyzowania swoich dokumentów, więc naturalnie tego użyjemy do przechowywania importowanych plików. Jako zwykły tekst, ta strategia ma również tę zaletę, że jest łatwo kompresowalna.
Ponadto dowiemy się, jak w razie potrzeby sprawdzać poprawność naszych plików wejściowych względem naszych niestandardowych typów. Na koniec udostępnimy interfejs API, abyśmy mogli z niego korzystać w czasie wykonywania w naszej aplikacji internetowej.
2. Zależności
Dodajmy te zależności Spring Boot do naszego pliku 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>
Będziemy również potrzebować działającej instancji MongoDB, która wymaga odpowiednio skonfigurowanego application.properties plik.
3. Importowanie ciągów JSON
Najprostszym sposobem na zaimportowanie JSON do MongoDB jest przekonwertowanie go na „org.bson.Document ” obiekt jako pierwszy. Ta klasa reprezentuje ogólny dokument MongoDB bez określonego typu. Dlatego nie musimy się martwić o tworzenie repozytoriów dla wszystkich rodzajów obiektów, które możemy importować.
Nasza strategia pobiera JSON (z pliku, zasobu lub ciągu), konwertuje go na Dokument i zapisuje je za pomocą MongoTemplate . Operacje wsadowe generalnie działają lepiej, ponieważ liczba podróży w obie strony jest zmniejszona w porównaniu z wstawianiem każdego obiektu osobno.
Co najważniejsze, rozważymy, że nasze dane wejściowe mają tylko jeden obiekt JSON na podział wiersza. W ten sposób możemy łatwo rozgraniczyć nasze obiekty. Zamkniemy te funkcje w dwóch klasach, które utworzymy:ImportUtils i ImportJsonService . Zacznijmy od naszej klasy usług:
@Service
public class ImportJsonService {
@Autowired
private MongoTemplate mongo;
}
Następnie dodajmy metodę, która analizuje wiersze JSON w dokumentach:
private List<Document> generateMongoDocs(List<String> lines) {
List<Document> docs = new ArrayList<>();
for (String json : lines) {
docs.add(Document.parse(json));
}
return docs;
}
Następnie dodajemy metodę, która wstawia listę Dokumentu obiekty do wybranej kolekcji . Możliwe jest również, że operacja wsadowa częściowo się nie powiedzie. W takim przypadku możemy zwrócić liczbę wstawionych dokumentów, sprawdzając przyczynę wyjątku :
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;
}
}
Na koniec połączmy te metody. Ten pobiera dane wejściowe i zwraca ciąg pokazujący, ile wierszy zostało przeczytanych, a ile pomyślnie wstawionych:
public String importTo(String collection, List<String> jsonLines) {
List<Document> mongoDocs = generateMongoDocs(jsonLines);
int inserts = insertInto(collection, mongoDocs);
return inserts + "/" + jsonLines.size();
}
4. Przypadki użycia
Teraz, gdy jesteśmy gotowi do przetwarzania danych wejściowych, możemy zbudować kilka przypadków użycia. Utwórzmy ImportUtils klasę, która nam w tym pomoże. Ta klasa będzie odpowiedzialna za konwersję danych wejściowych na wiersze JSON. Będzie zawierać tylko metody statyczne. Zacznijmy od czytania prostego Ciągu :
public static List<String> lines(String json) {
String[] split = json.split("[\\r\\n]+");
return Arrays.asList(split);
}
Ponieważ używamy łamania wierszy jako separatora, wyrażenie regularne świetnie sprawdza się w przypadku dzielenia ciągów na wiele wierszy. To wyrażenie regularne obsługuje zakończenia linii zarówno w systemie Unix, jak i Windows. Następnie metoda konwersji pliku na listę ciągów:
public static List<String> lines(File file) {
return Files.readAllLines(file.toPath());
}
Podobnie kończymy z metodą konwersji zasobu ścieżki klasy na listę:
public static List<String> linesFromResource(String resource) {
Resource input = new ClassPathResource(resource);
Path path = input.getFile().toPath();
return Files.readAllLines(path);
}
4.1. Importuj plik podczas uruchamiania za pomocą CLI
W naszym pierwszym przypadku użycia zaimplementujemy funkcjonalność importowania pliku za pomocą argumentów aplikacji. Skorzystamy z programu Spring Boot ApplicationRunner interfejs, aby to zrobić w czasie rozruchu. Możemy na przykład odczytać parametry wiersza poleceń, aby zdefiniować plik do zaimportowania:
@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);
}
}
}
}
Korzystanie z getOptionValues() możemy przetwarzać jeden lub więcej plików. Te pliki mogą pochodzić z naszej ścieżki klasy lub z naszego systemu plików. Rozróżniamy je za pomocą RESOURCE_PREFIX . Każdy argument zaczynający się od „classpath: ” zostanie odczytany z naszego folderu zasobów zamiast z systemu plików. Następnie wszystkie zostaną zaimportowane do wybranej kolekcji .
Zacznijmy korzystać z naszej aplikacji, tworząc plik pod src/main/resources/data.json.log :
{"name":"Book A", "genre": "Comedy"}
{"name":"Book B", "genre": "Thriller"}
{"name":"Book C", "genre": "Drama"}
Po skompilowaniu możemy skorzystać z poniższego przykładu, aby go uruchomić (dodane podziały wierszy dla czytelności). W naszym przykładzie zostaną zaimportowane dwa pliki, jeden ze ścieżki klasy, a drugi z systemu plików:
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. Plik JSON z przesłania HTTP POST
Dodatkowo, jeśli utworzymy kontroler REST, będziemy mieli punkt końcowy do przesyłania i importowania plików JSON. W tym celu potrzebujemy MultipartFile parametr:
@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);
}
}
Teraz możemy importować pliki z takim POST, gdzie „/tmp/data.json ” odnosi się do istniejącego pliku:
curl -X POST http://localhost:8082/import-json/file/books -F "[email protected]/tmp/books.json"
4.3. Mapowanie JSON do określonego typu Java
Używaliśmy tylko JSON, niezwiązanego z żadnym typem, co jest jedną z zalet pracy z MongoDB. Teraz chcemy zweryfikować nasz wkład. W takim przypadku dodajmy ObjectMapper wprowadzając tę zmianę w naszej usłudze:
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;
}
W ten sposób, jeśli typ określono parametr, nasz mapper spróbuje przeanalizować nasz ciąg JSON jako tego typu. I przy domyślnej konfiguracji zgłosi wyjątek, jeśli obecne są jakiekolwiek nieznane właściwości. Oto nasza prosta definicja fasoli do pracy z repozytorium MongoDB:
@Document("books")
public class Book {
@Id
private String id;
private String name;
private String genre;
// getters and setters
}
A teraz, aby skorzystać z ulepszonej wersji naszego Generatora dokumentów, zmieńmy również tę metodę:
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();
}
Teraz zamiast przekazywać nazwę kolekcji, przekazujemy Klasę . Zakładamy, że zawiera Dokument adnotacja, jak użyliśmy w naszej książce , dzięki czemu może pobrać nazwę kolekcji. Jednak ponieważ zarówno adnotacja, jak i Dokument klasy mają tę samą nazwę, musimy określić cały pakiet.