MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Importar dados para o MongoDB do arquivo JSON usando Java

1. Introdução


Neste tutorial, aprenderemos a ler dados JSON de arquivos e importá-los para o MongoDB usando Spring Boot. Isso pode ser útil por vários motivos:restauração de dados, inserção de novos dados em massa ou inserção de valores padrão. O MongoDB usa JSON internamente para estruturar seus documentos, então, naturalmente, é isso que usaremos para armazenar arquivos importáveis. Sendo texto simples, essa estratégia também tem a vantagem de ser facilmente compactável.

Além disso, aprenderemos como validar nossos arquivos de entrada em relação aos nossos tipos personalizados quando necessário. Por fim, vamos expor uma API para que possamos usá-la durante o tempo de execução em nosso aplicativo da web.

2. Dependências


Vamos adicionar essas dependências do Spring Boot ao nosso 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>

Também precisaremos de uma instância em execução do MongoDB, que requer um application.properties configurado corretamente Arquivo.

3. Importando strings JSON


A maneira mais simples de importar JSON para o MongoDB é convertê-lo em um “org.bson.Document ” primeiro. Esta classe representa um documento genérico do MongoDB sem tipo específico. Portanto, não precisamos nos preocupar em criar repositórios para todos os tipos de objetos que podemos importar.

Nossa estratégia pega JSON (de um arquivo, recurso ou string), converte em Documento s, e os salva usando MongoTemplate . As operações em lote geralmente têm melhor desempenho, pois a quantidade de viagens de ida e volta é reduzida em comparação com a inserção de cada objeto individualmente.

Mais importante, consideraremos nossa entrada como tendo apenas um objeto JSON por quebra de linha. Dessa forma, podemos facilmente delimitar nossos objetos. Vamos encapsular essas funcionalidades em duas classes que criaremos:ImportUtils e ImportJsonService . Vamos começar com nossa classe de serviço:
@Service
public class ImportJsonService {

    @Autowired
    private MongoTemplate mongo;
}

Em seguida, vamos adicionar um método que analisa linhas de JSON em documentos:
private List<Document> generateMongoDocs(List<String> lines) {
    List<Document> docs = new ArrayList<>();
    for (String json : lines) {
        docs.add(Document.parse(json));
    }
    return docs;
}

Em seguida, adicionamos um método que insere uma lista de Documento objetos na coleção desejada . Além disso, é possível que a operação em lote falhe parcialmente. Nesse caso, podemos retornar o número de documentos inseridos verificando a causa da exceção :
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;
    }
}

Finalmente, vamos combinar esses métodos. Este pega a entrada e retorna uma string mostrando quantas linhas foram lidas versus inseridas com sucesso:
public String importTo(String collection, List<String> jsonLines) {
    List<Document> mongoDocs = generateMongoDocs(jsonLines);
    int inserts = insertInto(collection, mongoDocs);
    return inserts + "/" + jsonLines.size();
}

4. Casos de uso


Agora que estamos prontos para processar a entrada, podemos criar alguns casos de uso. Vamos criar o ImportUtils classe para nos ajudar com isso. Esta classe será responsável por converter a entrada em linhas de JSON. Ele conterá apenas métodos estáticos. Vamos começar com a leitura de uma simples String :
public static List<String> lines(String json) {
    String[] split = json.split("[\\r\\n]+");
    return Arrays.asList(split);
}

Como estamos usando quebras de linha como delimitador, regex funciona muito bem para quebrar strings em várias linhas. Essa regex lida com terminações de linha do Unix e do Windows. Em seguida, um método para converter um arquivo em uma lista de strings:
public static List<String> lines(File file) {
    return Files.readAllLines(file.toPath());
}

Da mesma forma, terminamos com um método para converter um recurso de caminho de classe em uma lista:
public static List<String> linesFromResource(String resource) {
    Resource input = new ClassPathResource(resource);
    Path path = input.getFile().toPath();
    return Files.readAllLines(path);
}

4.1. Importar arquivo durante a inicialização com uma CLI


Em nosso primeiro caso de uso, implementaremos a funcionalidade para importar um arquivo por meio de argumentos do aplicativo. Aproveitaremos o Spring Boot ApplicationRunner interface para fazer isso no momento da inicialização. Por exemplo, podemos ler os parâmetros da linha de comando para definir o arquivo a ser importado:
@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);
            }
        }
    }
}

Usando getOptionValues() podemos processar um ou mais arquivos. Esses arquivos podem ser do nosso classpath ou do nosso sistema de arquivos. Nós os diferenciamos usando o RESOURCE_PREFIX . Todos os argumentos que começam com “caminho de classe: ” será lido da nossa pasta de recursos em vez do sistema de arquivos. Depois disso, todos eles serão importados para a coleção desejada .

Vamos começar a usar nosso aplicativo criando um arquivo em src/main/resources/data.json.log :
{"name":"Book A", "genre": "Comedy"}
{"name":"Book B", "genre": "Thriller"}
{"name":"Book C", "genre": "Drama"}

Após a construção, podemos usar o exemplo a seguir para executá-lo (quebras de linha adicionadas para facilitar a leitura). Em nosso exemplo, dois arquivos serão importados, um do classpath e outro do sistema de arquivos:
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. Arquivo JSON do upload HTTP POST


Além disso, se criarmos um REST Controller, teremos um endpoint para carregar e importar arquivos JSON. Para isso, precisaremos de um MultipartFile parâmetro:
@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);
    }
}

Agora podemos importar arquivos com um POST como este, onde “/tmp/data.json ” refere-se a um arquivo existente:
curl -X POST http://localhost:8082/import-json/file/books -F "[email protected]/tmp/books.json"

4.3. Mapeando JSON para um tipo específico de Java


Estamos usando apenas JSON, não vinculado a nenhum tipo, o que é uma das vantagens de trabalhar com o MongoDB. Agora queremos validar nossa entrada. Neste caso, vamos adicionar um ObjectMapper fazendo esta alteração em nosso serviço:
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;
}

Dessa forma, se o tipo for especificado, nosso mapeador tentará analisar nossa string JSON como esse tipo. E, com a configuração padrão, lançará uma exceção se alguma propriedade desconhecida estiver presente. Aqui está nossa definição de bean simples para trabalhar com um repositório MongoDB:
@Document("books")
public class Book {
    @Id
    private String id;
    private String name;
    private String genre;
    // getters and setters
}

E agora, para usar a versão aprimorada do nosso gerador de documentos, vamos alterar este método também:
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();
}

Agora, em vez de passar o nome de uma coleção, passamos uma Class . Presumimos que ele tem o Documento anotação como usamos em nosso Livro , para que ele possa recuperar o nome da coleção. No entanto, como a anotação e o Documento classes têm o mesmo nome, temos que especificar o pacote inteiro.