3.2.4 Nạp trước dữ liệu với CommandLineRunner
Khi làm việc với JdbcTemplate, chúng ta đã nạp sẵn dữ liệu Ingredient khi ứng dụng khởi động bằng cách sử dụng tệp data.sql, tệp này được thực thi với cơ sở dữ liệu khi bean data source được tạo ra. Cách tiếp cận đó cũng sẽ hoạt động với Spring Data JDBC. Thực tế, nó sẽ hoạt động với bất kỳ cơ chế lưu trữ nào mà cơ sở dữ liệu nền là cơ sở dữ liệu quan hệ. Nhưng hãy cùng xem một cách khác để điền dữ liệu vào cơ sở dữ liệu khi khởi động ứng dụng, cách này linh hoạt hơn một chút.
Spring Boot cung cấp hai interface hữu ích để thực thi logic khi ứng dụng khởi động: CommandLineRunner và ApplicationRunner. Hai interface này khá giống nhau. Cả hai đều là functional interface yêu cầu triển khai một phương thức run() duy nhất. Khi ứng dụng khởi động, bất kỳ bean nào trong application context triển khai CommandLineRunner hoặc ApplicationRunner sẽ có phương thức run() của chúng được gọi sau khi application context và tất cả các bean được thiết lập, nhưng trước khi bất kỳ điều gì khác xảy ra. Điều này cung cấp một vị trí thuận tiện để tải dữ liệu vào cơ sở dữ liệu.
Bởi vì cả CommandLineRunner và ApplicationRunner đều là functional interface, chúng có thể dễ dàng được khai báo dưới dạng bean trong một lớp cấu hình bằng cách sử dụng phương thức được chú thích với @Bean trả về một hàm lambda. Ví dụ, đây là cách bạn có thể tạo một bean CommandLineRunner để tải dữ liệu:
@Bean
public CommandLineRunner dataLoader(IngredientRepository repo) {
return args -> {
repo.save(new Ingredient("FLTO", "Flour Tortilla", Type.WRAP));
repo.save(new Ingredient("COTO", "Corn Tortilla", Type.WRAP));
repo.save(new Ingredient("GRBF", "Ground Beef", Type.PROTEIN));
repo.save(new Ingredient("CARN", "Carnitas", Type.PROTEIN));
repo.save(new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES));
repo.save(new Ingredient("LETC", "Lettuce", Type.VEGGIES));
repo.save(new Ingredient("CHED", "Cheddar", Type.CHEESE));
repo.save(new Ingredient("JACK", "Monterrey Jack", Type.CHEESE));
repo.save(new Ingredient("SLSA", "Salsa", Type.SAUCE));
repo.save(new Ingredient("SRCR", "Sour Cream", Type.SAUCE));
};
}Ở đây, IngredientRepository được inject vào phương thức bean và được sử dụng trong lambda để tạo các đối tượng Ingredient. Phương thức run() của CommandLineRunner nhận một tham số duy nhất là một vararg String chứa tất cả các đối số dòng lệnh cho ứng dụng đang chạy. Chúng ta không cần các đối số đó để tải nguyên liệu vào cơ sở dữ liệu, vì vậy tham số args được bỏ qua.
Ngoài ra, chúng ta cũng có thể định nghĩa bean tải dữ liệu dưới dạng lambda triển khai ApplicationRunner như sau:
@Bean
public ApplicationRunner dataLoader(IngredientRepository repo) {
return args -> {
repo.save(new Ingredient("FLTO", "Flour Tortilla", Type.WRAP));
repo.save(new Ingredient("COTO", "Corn Tortilla", Type.WRAP));
repo.save(new Ingredient("GRBF", "Ground Beef", Type.PROTEIN));
repo.save(new Ingredient("CARN", "Carnitas", Type.PROTEIN));
repo.save(new Ingredient("TMTO", "Diced Tomatoes", Type.VEGGIES));
repo.save(new Ingredient("LETC", "Lettuce", Type.VEGGIES));
repo.save(new Ingredient("CHED", "Cheddar", Type.CHEESE));
repo.save(new Ingredient("JACK", "Monterrey Jack", Type.CHEESE));
repo.save(new Ingredient("SLSA", "Salsa", Type.SAUCE));
repo.save(new Ingredient("SRCR", "Sour Cream", Type.SAUCE));
};
}Sự khác biệt chính giữa CommandLineRunner và ApplicationRunner nằm ở tham số được truyền vào phương thức run() tương ứng. CommandLineRunner nhận một vararg String, là dạng thô của các đối số dòng lệnh. Trong khi đó, ApplicationRunner nhận một tham số ApplicationArguments cung cấp các phương thức để truy cập các đối số như những thành phần đã được phân tích cú pháp của dòng lệnh.
Ví dụ, giả sử bạn muốn ứng dụng của mình chấp nhận một dòng lệnh với đối số như "--version 1.2.3" và cần xử lý đối số đó trong bean tải dữ liệu. Nếu sử dụng CommandLineRunner, bạn sẽ phải duyệt qua mảng để tìm "--version" và sau đó lấy giá trị kế tiếp từ mảng. Nhưng với ApplicationRunner, bạn có thể truy vấn ApplicationArguments được cung cấp để lấy đối số "--version" như sau:
public ApplicationRunner dataLoader(IngredientRepository repo) {
return args -> {
List<String> version = args.getOptionValues("version");
...
};
}Phương thức getOptionValues() trả về một List<String> cho phép đối số tùy chọn đó được chỉ định nhiều lần.
Tuy nhiên, trong cả hai trường hợp CommandLineRunner hay ApplicationRunner, chúng ta không cần các đối số dòng lệnh để tải dữ liệu. Vì vậy, tham số args được bỏ qua trong bean tải dữ liệu của chúng ta.
Điều tuyệt vời khi sử dụng CommandLineRunner hoặc ApplicationRunner để tải dữ liệu ban đầu là chúng sử dụng các repository để tạo các đối tượng được lưu trữ thay vì dùng script SQL. Điều này có nghĩa là chúng sẽ hoạt động tốt với cả cơ sở dữ liệu quan hệ và phi quan hệ. Điều này sẽ rất hữu ích trong chương tiếp theo khi chúng ta tìm hiểu cách sử dụng Spring Data để lưu trữ vào cơ sở dữ liệu phi quan hệ.
Nhưng trước khi làm điều đó, hãy cùng xem một dự án Spring Data khác để lưu trữ dữ liệu trong cơ sở dữ liệu quan hệ: Spring Data JPA.
