3.2.4 Preloading data with CommandLineRunner
When working with JdbcTemplate, we preloaded the Ingredient data at application startup using data.sql, which was executed against the database when the data source bean was created. That same approach will work with Spring Data JDBC. In fact, it will work with any persistence mechanism for which the backing database is a relational database. But let’s see another way of populating a database at startup that offers a bit more flexibility.
Spring Boot offers two useful interfaces for executing logic when an application starts up: CommandLineRunner and ApplicationRunner. These two interfaces are quite similar. Both are functional interfaces that require that a single run() method be implemented. When the application starts up, any beans in the application context that implement CommandLineRunner or ApplicationRunner will have their run() methods invoked after the application context and all beans are wired up, but before anything else happens. This provides a convenient place for data to be loaded into the database.
Because both CommandLineRunner and ApplicationRunner are functional interfaces, they can easily be declared as beans in a configuration class using a @Bean-annotated method that returns a lambda function. For example, here’s how you might create a data-loading CommandLineRunner bean:
@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));
};
}Here, the IngredientRepository is injected into the bean method and used within the lambda to create Ingredient objects. The run() method of CommandLineRunner accepts a single parameter that is a String vararg containing all of the command-line arguments for the running application. We don’t need those to load ingredients into the database, so the args parameter is ignored.
Alternatively, we could have defined the data-loader bean as a lambda implementation of ApplicationRunner like this:
@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));
};
}The key difference between CommandLineRunner and ApplicationRunner is in the parameter passed to the respective run() methods. CommandLineRunner accepts a String vararg, which is a raw representation of arguments passed on the command line. But ApplicationRunner accepts an ApplicationArguments parameter that offers methods for accessing the arguments as parsed components of the command line.
For example, suppose that we want our application to accept a command line with arguments such as "--version 1.2.3" and need to consider that argument in our loader bean. If using a CommandLineRunner, we’d need to search the array for “--version” and then take the very next value from the array. But with ApplicationRunner, we can query the given ApplicationArguments for the “--version” argument like this:
public ApplicationRunner dataLoader(IngredientRepository repo) {
return args -> {
List<String> version = args.getOptionValues("version");
...
};
}The getOptionValues() method returns a List<String> to allow for the option argument to be specified multiple times.
In the case of either CommandLineRunner or ApplicationRunner, however, we don’t need command-line arguments to load data. So the args parameter is ignored in our data-loader bean.
What’s nice about using CommandLineRunner or ApplicationRunner to do an initial data load is that they are using the repositories to create the persisted objects instead of a SQL script. This means that they’ll work equally well for relational databases and nonrelational databases. This will come in handy in the next chapter when we see how to use Spring Data to persist to nonrelational databases.
But before we do that, let’s have a look at another Spring Data project for persisting data in relational databases: Spring Data JPA.
