1. Giới thiệu
Trong hướng dẫn này, chúng ta sẽ học cách đọc dữ liệu JSON từ các tệp và nhập chúng vào MongoDB bằng Spring Boot. Điều này có thể hữu ích vì nhiều lý do:khôi phục dữ liệu, chèn hàng loạt dữ liệu mới hoặc chèn giá trị mặc định. MongoDB sử dụng JSON bên trong để cấu trúc tài liệu của nó, vì vậy, tự nhiên, đó là những gì chúng tôi sẽ sử dụng để lưu trữ các tệp có thể nhập. Là văn bản thuần túy, chiến lược này cũng có ưu điểm là dễ nén.
Hơn nữa, chúng tôi sẽ tìm hiểu cách xác thực các tệp đầu vào của chúng tôi dựa trên các loại tùy chỉnh của chúng tôi khi cần thiết. Cuối cùng, chúng tôi sẽ đưa ra một API để chúng tôi có thể sử dụng nó trong thời gian chạy trong ứng dụng web của chúng tôi.
2. Sự phụ thuộc
Hãy thêm các phụ thuộc Spring Boot này vào pom.xml của chúng tôi :
<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>
Chúng tôi cũng sẽ cần một phiên bản MongoDB đang chạy, phiên bản này yêu cầu một application.properties được định cấu hình đúng cách tệp.
3. Nhập chuỗi JSON
Cách đơn giản nhất để nhập JSON vào MongoDB là chuyển đổi nó thành “ org.bson.Document ”Đối tượng đầu tiên. Lớp này đại diện cho một tài liệu MongoDB chung không có kiểu cụ thể. Do đó, chúng tôi không phải lo lắng về việc tạo kho lưu trữ cho tất cả các loại đối tượng mà chúng tôi có thể nhập.
Chiến lược của chúng tôi sử dụng JSON (từ tệp, tài nguyên hoặc chuỗi), chuyển đổi nó thành Tài liệu s và lưu chúng bằng MongoTemplate . Hoạt động hàng loạt thường hoạt động tốt hơn vì số lượng chuyến đi khứ hồi giảm so với việc chèn từng đối tượng riêng lẻ.
Quan trọng nhất, chúng tôi sẽ coi đầu vào của chúng tôi chỉ có một đối tượng JSON trên mỗi ngắt dòng. Bằng cách đó, chúng ta có thể dễ dàng phân định các đối tượng của mình. Chúng tôi sẽ đóng gói các chức năng này thành hai lớp mà chúng tôi sẽ tạo: ImportUtils và ImportJsonService . Hãy bắt đầu với lớp dịch vụ của chúng tôi:
@Service
public class ImportJsonService {
@Autowired
private MongoTemplate mongo;
}
Tiếp theo, hãy thêm một phương thức phân tích cú pháp các dòng JSON vào tài liệu:
private List<Document> generateMongoDocs(List<String> lines) {
List<Document> docs = new ArrayList<>();
for (String json : lines) {
docs.add(Document.parse(json));
}
return docs;
}
Sau đó, chúng tôi thêm một phương thức chèn danh sách Tài liệu các đối tượng vào bộ sưu tập mong muốn . Ngoài ra, có thể hoạt động hàng loạt bị lỗi một phần. Trong trường hợp đó, chúng tôi có thể trả lại số tài liệu đã chèn bằng cách kiểm tra nguyên nhân của ngoại lệ :
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;
}
}
Cuối cùng, hãy kết hợp các phương pháp đó. Công cụ này nhận đầu vào và trả về một chuỗi hiển thị số dòng đã được đọc so với đã được chèn thành công:
public String importTo(String collection, List<String> jsonLines) {
List<Document> mongoDocs = generateMongoDocs(jsonLines);
int inserts = insertInto(collection, mongoDocs);
return inserts + "/" + jsonLines.size();
}
4. Các trường hợp sử dụng
Bây giờ chúng tôi đã sẵn sàng xử lý đầu vào, chúng tôi có thể xây dựng một số trường hợp sử dụng. Hãy tạo ImportUtils lớp để giúp chúng tôi với điều đó. Lớp này sẽ chịu trách nhiệm chuyển đổi đầu vào thành các dòng JSON. Nó sẽ chỉ chứa các phương thức tĩnh. Hãy bắt đầu với một cái để đọc một Chuỗi đơn giản :
public static List<String> lines(String json) {
String[] split = json.split("[\\r\\n]+");
return Arrays.asList(split);
}
Vì chúng tôi đang sử dụng ngắt dòng làm dấu phân cách, nên regex hoạt động tuyệt vời để ngắt các chuỗi thành nhiều dòng. Regex này xử lý cả phần cuối dòng Unix và Windows. Tiếp theo, một phương pháp để chuyển đổi một Tệp thành một danh sách các chuỗi:
public static List<String> lines(File file) {
return Files.readAllLines(file.toPath());
}
Tương tự, chúng ta kết thúc với một phương thức để chuyển đổi tài nguyên classpath thành một danh sách:
public static List<String> linesFromResource(String resource) {
Resource input = new ClassPathResource(resource);
Path path = input.getFile().toPath();
return Files.readAllLines(path);
}
4.1. Nhập tệp trong khi khởi động bằng CLI
Trong trường hợp sử dụng đầu tiên của chúng tôi, chúng tôi sẽ triển khai chức năng nhập tệp thông qua các đối số ứng dụng. Chúng tôi sẽ tận dụng Spring Boot ApplicationRunner giao diện để thực hiện việc này tại thời điểm khởi động. Ví dụ:chúng tôi có thể đọc các tham số dòng lệnh để xác định tệp cần nhập:
@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);
}
}
}
}
Sử dụng getOptionValues () chúng tôi có thể xử lý một hoặc nhiều tệp. Các tệp này có thể từ classpath của chúng tôi hoặc từ hệ thống tệp của chúng tôi. Chúng tôi phân biệt chúng bằng cách sử dụng RESOURCE_PREFIX . Mọi đối số bắt đầu bằng “ classpath: ”Sẽ được đọc từ thư mục tài nguyên của chúng tôi thay vì từ hệ thống tệp. Sau đó, tất cả chúng sẽ được nhập vào bộ sưu tập mong muốn .
Hãy bắt đầu sử dụng ứng dụng của chúng tôi bằng cách tạo tệp trong src / main / resources / data.json.log :
{"name":"Book A", "genre": "Comedy"}
{"name":"Book B", "genre": "Thriller"}
{"name":"Book C", "genre": "Drama"}
Sau khi xây dựng, chúng ta có thể sử dụng ví dụ sau để chạy nó (thêm dấu ngắt dòng để dễ đọc). Trong ví dụ của chúng tôi, hai tệp sẽ được nhập, một từ classpath và một từ hệ thống tệp:
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. Tệp JSON từ tải lên HTTP BÀI ĐĂNG
Ngoài ra, nếu chúng tôi tạo Bộ điều khiển REST, chúng tôi sẽ có một điểm cuối để tải lên và nhập tệp JSON. Đối với điều đó, chúng tôi sẽ cần một MultipartFile tham số:
@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);
}
}
Giờ đây, chúng tôi có thể nhập tệp bằng ĐĂNG như thế này, trong đó “ /tmp/data.json ”Đề cập đến một tệp hiện có:
curl -X POST http://localhost:8082/import-json/file/books -F "[email protected]/tmp/books.json"
4.3. Ánh xạ JSON sang một loại Java cụ thể
Chúng tôi chỉ sử dụng JSON, không bị ràng buộc với bất kỳ loại nào, đó là một trong những lợi thế khi làm việc với MongoDB. Bây giờ chúng tôi muốn xác thực thông tin đầu vào của mình. Trong trường hợp này, hãy thêm một ObjectMapper bằng cách thực hiện thay đổi này đối với dịch vụ của chúng tôi:
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;
}
Theo cách đó, nếu loại tham số được chỉ định, người lập bản đồ của chúng tôi sẽ cố gắng phân tích cú pháp chuỗi JSON của chúng tôi thành kiểu đó. Và, với cấu hình mặc định, sẽ đưa ra một ngoại lệ nếu có bất kỳ thuộc tính nào không xác định. Đây là định nghĩa bean đơn giản của chúng tôi để làm việc với kho lưu trữ MongoDB:
@Document("books")
public class Book {
@Id
private String id;
private String name;
private String genre;
// getters and setters
}
Và bây giờ, để sử dụng phiên bản cải tiến của Trình tạo tài liệu của chúng tôi, hãy thay đổi cả phương pháp này:
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();
}
Bây giờ, thay vì chuyển tên của một tập hợp, chúng tôi chuyển một Lớp . Chúng tôi giả sử nó có Tài liệu chú thích như chúng tôi đã sử dụng trong Sách của mình , vì vậy nó có thể truy xuất tên bộ sưu tập. Tuy nhiên, vì cả chú thích và Tài liệu các lớp có cùng tên, chúng ta phải chỉ định toàn bộ gói.