使用 Java 從 JSON 文件將數據導入 MongoDB
- java
- mongodb
一、簡介
在本教程中,我們將學習如何從文件中讀取 JSON 數據並使用 Spring Boot 將它們導入 MongoDB。出於多種原因,這可能很有用:恢復數據、批量插入新數據或插入默認值。 MongoDB 在內部使用 JSON 來構造其文檔,因此很自然,我們將使用 JSON 來存儲可導入文件。作為純文本,這種策略還具有易於壓縮的優點。
此外,我們將學習如何在必要時根據我們的自定義類型驗證我們的輸入文件。最後,我們將公開一個 API,以便我們可以在 Web 應用程序的運行時使用它。
2. 依賴
讓我們將這些 Spring Boot 依賴項添加到我們的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>
我們還需要一個正在運行的 MongoDB 實例,這需要正確配置的application.properties
文件。
3. 導入 JSON 字符串
將 JSON 導入 MongoDB 的最簡單方法是首先將其轉換為“ org.bson.Document
”對象。此類表示沒有特定類型的通用 MongoDB 文檔。因此,我們不必擔心為我們可能導入的所有類型的對象創建存儲庫。
我們的策略採用 JSON(來自文件、資源或字符串),將其轉換為Document
,並使用MongoTemplate
保存它們。批處理操作通常執行得更好,因為與單獨插入每個對象相比,往返次數減少了。
最重要的是,我們會認為我們的輸入每個換行符只有一個 JSON 對象。這樣,我們可以輕鬆地分隔我們的對象。我們將這些功能封裝到我們將創建的兩個類中: ImportUtils
和ImportJsonService
。讓我們從我們的服務類開始:
@Service
public class ImportJsonService {
@Autowired
private MongoTemplate mongo;
}
接下來,讓我們添加一個將 JSON 行解析為文檔的方法:
private List<Document> generateMongoDocs(List<String> lines) {
List<Document> docs = new ArrayList<>();
for (String json : lines) {
docs.add(Document.parse(json));
}
return docs;
}
然後我們添加一個將Document
對象列表插入所需collection
的方法。此外,批處理操作可能會部分失敗。在這種情況下,我們可以通過檢查exception
cause
來返回插入文檔的數量:
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;
}
}
最後,讓我們結合這些方法。這個接受輸入並返回一個字符串,顯示讀取了多少行與成功插入了多少行:
public String importTo(String collection, List<String> jsonLines) {
List<Document> mongoDocs = generateMongoDocs(jsonLines);
int inserts = insertInto(collection, mongoDocs);
return inserts + "/" + jsonLines.size();
}
4. 用例
現在我們已經準備好處理輸入,我們可以構建一些用例。讓我們創建ImportUtils
類來幫助我們。此類將負責將輸入轉換為 JSON 行。它只包含靜態方法。讓我們從閱讀一個簡單的String
開始:
public static List<String> lines(String json) {
String[] split = json.split("[\\r\\n]+");
return Arrays.asList(split);
}
由於我們使用換行符作為分隔符,因此正則表達式可以很好地將字符串分成多行。此正則表達式處理 Unix 和 Windows 行尾。接下來,將 File 轉換為字符串列表的方法:
public static List<String> lines(File file) {
return Files.readAllLines(file.toPath());
}
同樣,我們完成了一個將類路徑資源轉換為列表的方法:
public static List<String> linesFromResource(String resource) {
Resource input = new ClassPathResource(resource);
Path path = input.getFile().toPath();
return Files.readAllLines(path);
}
4.1。使用 CLI 在啟動期間導入文件
在我們的第一個用例中,我們將實現通過應用程序參數導入文件的功能。我們將利用 Spring Boot ApplicationRunner
接口在啟動時執行此操作。例如,我們可以讀取命令行參數來定義要導入的文件:
@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()
我們可以處理一個或多個文件。這些文件可以來自我們的類路徑或來自我們的文件系統。我們使用RESOURCE_PREFIX
來區分它們。每個以“ classpath:
”開頭的參數都將從我們的資源文件夾中讀取,而不是從文件系統中讀取。之後,它們都將被導入所需的collection
中。
讓我們通過在src/main/resources/data.json.log
下創建一個文件來開始使用我們的應用程序:
{"name":"Book A", "genre": "Comedy"}
{"name":"Book B", "genre": "Thriller"}
{"name":"Book C", "genre": "Drama"}
構建完成後,我們可以使用以下示例運行它(添加換行符以提高可讀性)。在我們的示例中,將導入兩個文件,一個來自類路徑,一個來自文件系統:
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.來自 HTTP POST 上傳的 JSON 文件
此外,如果我們創建一個 REST 控制器,我們將有一個端點來上傳和導入 JSON 文件。為此,我們需要一個MultipartFile
參數:
@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);
}
}
現在我們可以像這樣使用 POST 導入文件,其中“ /tmp/data.json
”指的是現有文件:
curl -X POST http://localhost:8082/import-json/file/books -F "[email protected]/tmp/books.json"
4.3.將 JSON 映射到特定的 Java 類型
我們一直只使用 JSON,沒有綁定到任何類型,這是使用 MongoDB 的優勢之一。現在我們要驗證我們的輸入。在這種情況下,讓我們通過對我們的服務進行以下更改來添加一個ObjectMapper
:
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;
}
這樣,如果指定了type
參數,我們的mapper
將嘗試將我們的 JSON 字符串解析為該類型。並且,使用默認配置,如果存在任何未知屬性,將引發異常。這是我們使用 MongoDB 存儲庫的簡單 bean 定義:
@Document("books")
public class Book {
@Id
private String id;
private String name;
private String genre;
// getters and setters
}
現在,為了使用我們的文檔生成器的改進版本,讓我們也改變這個方法:
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();
}
現在,我們不傳遞集合的名稱,而是傳遞一個Class
。我們假設它具有我們在Book
中使用的Document
註釋,因此它可以檢索集合名稱。但是,由於註解和Document
類具有相同的名稱,我們必須指定整個包。
5. 結論
在本文中,我們從文件、資源或簡單字符串中分解了 JSON 輸入,並將它們導入 MongoDB。我們將此功能集中在一個服務類和一個實用程序類中,以便我們可以在任何地方重用它。我們的用例包括一個 CLI 和一個 REST 選項,以及有關如何使用它的示例命令。