sql >> Database >  >> NoSQL >> MongoDB

Gegevens importeren naar MongoDB vanuit JSON-bestand met Java

1. Inleiding

In deze zelfstudie leren we hoe u JSON-gegevens uit bestanden kunt lezen en deze in MongoDB kunt importeren met Spring Boot. Dit kan om vele redenen handig zijn:gegevens herstellen, bulksgewijs nieuwe gegevens invoegen of standaardwaarden invoegen. MongoDB gebruikt JSON intern om zijn documenten te structureren, dus dat is natuurlijk wat we zullen gebruiken om importeerbare bestanden op te slaan. Omdat het platte tekst is, heeft deze strategie ook het voordeel dat ze gemakkelijk samen te drukken is.

Bovendien leren we hoe we onze invoerbestanden kunnen valideren tegen onze aangepaste typen wanneer dat nodig is. Ten slotte zullen we een API beschikbaar stellen zodat we deze tijdens runtime in onze web-app kunnen gebruiken.

2. Afhankelijkheden

Laten we deze Spring Boot-afhankelijkheden toevoegen aan onze 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>

We hebben ook een actieve instantie van MongoDB nodig, waarvoor een correct geconfigureerde application.properties vereist is. bestand.

3. JSON-strings importeren

De eenvoudigste manier om JSON in MongoDB te importeren, is door het te converteren naar een "org.bson.Document ” object eerst. Deze klasse vertegenwoordigt een generiek MongoDB-document van geen specifiek type. Daarom hoeven we ons geen zorgen te maken over het maken van opslagplaatsen voor alle soorten objecten die we zouden kunnen importeren.

Onze strategie neemt JSON (van een bestand, bron of string), converteert het naar Document s, en slaat ze op met MongoTemplate . Batchbewerkingen presteren over het algemeen beter omdat het aantal retourvluchten is verminderd in vergelijking met het afzonderlijk invoegen van elk object.

Het belangrijkste is dat we onze invoer beschouwen als slechts één JSON-object per regeleinde. Op die manier kunnen we onze objecten gemakkelijk afbakenen. We zullen deze functionaliteiten inkapselen in twee klassen die we zullen maken:ImportUtils en ImportJsonService . Laten we beginnen met onze serviceklasse:

@Service
public class ImportJsonService {

    @Autowired
    private MongoTemplate mongo;
}

Laten we vervolgens een methode toevoegen die JSON-regels in documenten parseert:

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

Vervolgens voegen we een methode toe die een lijst met Documenten invoegt objecten in de gewenste verzameling . Het is ook mogelijk dat de batchbewerking gedeeltelijk mislukt. In dat geval kunnen we het aantal ingevoegde documenten teruggeven door de oorzaak . aan te vinken van de uitzondering :

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

Laten we ten slotte die methoden combineren. Deze neemt de invoer en retourneert een tekenreeks die aangeeft hoeveel regels zijn gelezen vs. met succes zijn ingevoegd:

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

4. Gebruiksscenario's

Nu we klaar zijn om invoer te verwerken, kunnen we enkele gebruiksscenario's bouwen. Laten we de ImportUtils . maken klas om ons daarbij te helpen. Deze klasse is verantwoordelijk voor het converteren van invoer naar JSON-regels. Het bevat alleen statische methoden. Laten we beginnen met die voor het lezen van een eenvoudige String :

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

Omdat we regeleinden als scheidingsteken gebruiken, werkt regex uitstekend om strings in meerdere regels te splitsen. Deze regex verwerkt zowel Unix- als Windows-regeleindes. Vervolgens een methode om een ​​bestand om te zetten in een lijst met strings:

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

Op dezelfde manier eindigen we met een methode om een ​​classpath-bron om te zetten in een lijst:

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

4.1. Bestand importeren tijdens opstarten met een CLI

In onze eerste use case zullen we functionaliteit implementeren voor het importeren van een bestand via toepassingsargumenten. We maken gebruik van de Spring Boot ApplicationRunner interface om dit tijdens het opstarten te doen. We kunnen bijvoorbeeld opdrachtregelparameters lezen om het te importeren bestand te definiëren:

@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() gebruiken wij kunnen één of meerdere bestanden verwerken. Deze bestanden kunnen afkomstig zijn van ons klassenpad of van ons bestandssysteem. We onderscheiden ze met behulp van de RESOURCE_PREFIX . Elk argument dat begint met “classpath: ” worden gelezen uit onze bronnenmap in plaats van uit het bestandssysteem. Daarna worden ze allemaal geïmporteerd in de gewenste verzameling .

Laten we onze applicatie gaan gebruiken door een bestand te maken onder src/main/resources/data.json.log :

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

Na het bouwen kunnen we het volgende voorbeeld gebruiken om het uit te voeren (regeleinden toegevoegd voor de leesbaarheid). In ons voorbeeld worden twee bestanden geïmporteerd, één uit het klassenpad en één uit het bestandssysteem:

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-bestand van HTTP POST-upload

Als we bovendien een REST-controller maken, hebben we een eindpunt om JSON-bestanden te uploaden en te importeren. Daarvoor hebben we een MultipartFile . nodig 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 kunnen we bestanden importeren met een POST zoals deze, waarbij "/tmp/data.json ” verwijst naar een bestaand bestand:

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

4.3. JSON toewijzen aan een specifiek Java-type

We hebben alleen JSON gebruikt, niet gebonden aan welk type dan ook, wat een van de voordelen is van het werken met MongoDB. Nu willen we onze input valideren. Laten we in dit geval een ObjectMapper . toevoegen door deze wijziging in onze service aan te brengen:

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

Op die manier, als het type parameter is opgegeven, onze mapper zal proberen onze JSON-tekenreeks als dat type te ontleden. En, met standaardconfiguratie, zal een uitzondering worden gegenereerd als er onbekende eigenschappen aanwezig zijn. Hier is onze eenvoudige bean-definitie voor het werken met een MongoDB-repository:

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

En nu, om de verbeterde versie van onze Documentgenerator te gebruiken, laten we deze methode ook veranderen:

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

In plaats van de naam van een verzameling door te geven, geven we nu een Klasse . We gaan ervan uit dat het het Document . heeft annotatie zoals we gebruikten in ons Boek , zodat het de collectienaam kan ophalen. Echter, aangezien zowel de annotatie als het Document klassen dezelfde naam hebben, moeten we het hele pakket specificeren.


  1. Voorkom dubbele aanmelding met FOSUserBundle

  2. Preventieve beveiliging met auditlogging voor MongoDB

  3. PHP Mongo Fout bij lezen van socket

  4. Vragen over Redis en Node.js en Socket.io