sql >> Database teknologi >  >> NoSQL >> MongoDB

Importer data til MongoDB fra JSON-fil ved hjælp af Java

1. Introduktion

I denne vejledning lærer vi, hvordan du læser JSON-data fra filer og importerer dem til MongoDB ved hjælp af Spring Boot. Dette kan være nyttigt af mange årsager:gendannelse af data, masseindsættelse af nye data eller indsættelse af standardværdier. MongoDB bruger JSON internt til at strukturere sine dokumenter, så det er naturligvis det, vi vil bruge til at gemme importerbare filer. Da denne strategi er almindelig tekst, har den også den fordel, at den er let komprimerbar.

Desuden lærer vi, hvordan vi validerer vores inputfiler mod vores tilpassede typer, når det er nødvendigt. Til sidst vil vi afsløre en API, så vi kan bruge den under kørsel i vores webapp.

2. Afhængigheder

Lad os tilføje disse Spring Boot-afhængigheder til vores 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>

Vi får også brug for en kørende forekomst af MongoDB, som kræver en korrekt konfigureret application.properties fil.

3. Importerer JSON-strenge

Den enkleste måde at importere JSON til MongoDB på er at konvertere den til et "org.bson.Document ” objekt først. Denne klasse repræsenterer et generisk MongoDB-dokument af ingen specifik type. Derfor behøver vi ikke bekymre os om at oprette depoter for alle de slags objekter, vi kan importere.

Vores strategi tager JSON (fra en fil, ressource eller streng), konverterer den til Dokument s, og gemmer dem ved hjælp af MongoTemplate . Batchoperationer fungerer generelt bedre, da mængden af ​​rundturer er reduceret sammenlignet med at indsætte hvert objekt individuelt.

Vigtigst af alt, vil vi betragte vores input som kun at have ét JSON-objekt pr. linjeskift. På den måde kan vi nemt afgrænse vores objekter. Vi indkapsler disse funktionaliteter i to klasser, som vi opretter:ImportUtils og ImportJsonService . Lad os starte med vores serviceklasse:

@Service
public class ImportJsonService {

    @Autowired
    private MongoTemplate mongo;
}

Lad os derefter tilføje en metode, der analyserer JSON-linjer til dokumenter:

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

Derefter tilføjer vi en metode, der indsætter en liste over Dokument objekter i den ønskede samling . Det er også muligt, at batchoperationen delvist mislykkes. I så fald kan vi returnere antallet af indsatte dokumenter ved at kontrollere årsagen af undtagelsen :

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;
    }
}

Lad os endelig kombinere disse metoder. Denne tager inputtet og returnerer en streng, der viser, hvor mange linjer der blev læst vs. indsat:

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

4. Use Cases

Nu hvor vi er klar til at behandle input, kan vi bygge nogle use cases. Lad os oprette ImportUtils klasse for at hjælpe os med det. Denne klasse vil være ansvarlig for at konvertere input til JSON-linjer. Det vil kun indeholde statiske metoder. Lad os starte med den til at læse en simpel streng :

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

Da vi bruger linjeskift som et afgrænsningstegn, fungerer regex godt til at opdele strenge i flere linjer. Dette regex håndterer både Unix- og Windows-linjeslutninger. Dernæst en metode til at konvertere en fil til en liste med strenge:

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

På samme måde afslutter vi med en metode til at konvertere en klassesti-ressource til en liste:

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

4.1. Importer fil under opstart med en CLI

I vores første use case implementerer vi funktionalitet til import af en fil via applikationsargumenter. Vi vil drage fordel af Spring Boot ApplicationRunner interface til at gøre dette ved opstart. Vi kan f.eks. læse kommandolinjeparametre for at definere filen, der skal importeres:

@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);
            }
        }
    }
}

Brug af getOptionValues() vi kan behandle en eller flere filer. Disse filer kan enten være fra vores klassesti eller fra vores filsystem. Vi adskiller dem ved hjælp af RESOURCE_PREFIX . Hvert argument, der starter med "classpath: ” vil blive læst fra vores ressourcemappe i stedet for fra filsystemet. Derefter vil de alle blive importeret til den ønskede samling .

Lad os begynde at bruge vores applikation ved at oprette en fil under src/main/resources/data.json.log :

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

Efter at have bygget, kan vi bruge følgende eksempel til at køre det (linjeskift tilføjet for læsbarheden). I vores eksempel vil to filer blive importeret, en fra klassestien og en fra filsystemet:

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-fil fra HTTP POST Upload

Derudover, hvis vi opretter en REST-controller, har vi et slutpunkt til at uploade og importere JSON-filer. Til det har vi brug for en 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);
    }
}

Nu kan vi importere filer med et POST som dette, hvor "/tmp/data.json ” refererer til en eksisterende fil:

curl -X POST http://localhost:8082/import-json/file/books -F "[email protected]/tmp/books.json"

4.3. Tilknytning af JSON til en specifik Java-type

Vi har kun brugt JSON, ikke bundet til nogen type, hvilket er en af ​​fordelene ved at arbejde med MongoDB. Nu vil vi validere vores input. Lad os i dette tilfælde tilføje en ObjectMapper ved at foretage denne ændring af vores service:

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;
}

På den måde, hvis typen parameter er angivet, vores mapper vil forsøge at parse vores JSON-streng som den type. Og vil med standardkonfigurationen give en undtagelse, hvis der er ukendte egenskaber til stede. Her er vores enkle bønnedefinition for at arbejde med et MongoDB-lager:

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

Og nu, for at bruge den forbedrede version af vores dokumentgenerator, lad os også ændre denne metode:

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();
}

Nu, i stedet for at videregive navnet på en samling, passerer vi en klasse . Vi antager, at den har Dokumentet annotation, som vi brugte i vores bog , så den kan hente samlingens navn. Men da både annotationen og Dokumentet klasser har samme navn, vi skal angive hele pakken.


  1. MongoDB $dayOfWeek

  2. Hvordan virker redis for at hjælpe i session persistens i azurblå vindue

  3. MongoDB Document Re-shaping

  4. MongoDB $prøve