We have already initialized the database schema using Hibernate, Flyway or Liquibase. However, in addition to the schema, we often require certain data that is necessary for the application during runtime. This could be e.g. a table Country
, in which the ISO code and a currency is stored. What options are available for this in a Spring Boot application?
Using an ApplicationRunner
An ApplicationRunner
is executed immediately after the start of our Spring Boot application. If there are multiple ApplicationRunners
in the application, they can also be sorted in the desired order using @Order
.
@Component
public class CountryLoader implements ApplicationRunner {
private final CountryRepository countryRepository;
@Autowired
public CountryLoader(final CountryRepository countryRepository) {
this.countryRepository = countryRepository;
}
public void run(final ApplicationArguments args) {
if (countryRepository.count() != 0) {
return;
}
final Country germany = new Country();
germany.setCode("DE");
germany.setCurrency("EUR");
countryRepository.save(germany);
final Country states = new Country();
states.setCode("US");
states.setCurrency("USD");
countryRepository.save(states);
}
}
Using an ApplicationRunner to initialize data
This option provides the highest flexibility, as the process can be controlled directly in our application.
Using the data.sql
If we are working with a relational database, we could simply place a data.sql
in our resources
folder. This script will be automatically executed by Spring Boot against the configured DataSource
during startup.
INSERT INTO country (code, currency) VALUES ('DE', 'EUR') ON CONFLICT DO NOTHING;
INSERT INTO country (code, currency) VALUES ('US', 'USD') ON CONFLICT DO NOTHING;
Special insert script for PostgreSQL
We have to ensure that the values are not created multiple times. If our database schema is created by Hibernate, we should also add the following property to run our script only after Hibernate made its changes.
spring:
jpa:
defer-datasource-initialization: true
Running the data.sql after Hibernate
Using changelogs
If we have chosen Flyway or Liquibase for schema generation, we can also use them for loading our initial data. They implicitly ensure that the changes are executed exactly once against the connected database.
In case of Liquibase we simply add another changelog with a higher timestamp into our changelogs
folder.
databaseChangeLog:
- changeSet:
id: countries-initial
author: bootify.io
changes:
- insert:
tableName: country
columns:
- column:
name: code
value: DE
- column:
name: currency
value: EUR
- insert:
tableName: country
columns:
- column:
name: code
value: US
- column:
name: currency
stringValue: USD
Liquibase changelog in resources/changelogs/2023-01-18_11-00.yml
In case of Flyway we create our migration script directly in the dialect of the used database. We store it in resources/db/migration/V002__INITIAL_COUNTRIES.sql
so that it is executed immediately after the table structure has been created.
INSERT INTO country (code, currency) VALUES ('DE', 'EUR');
INSERT INTO country (code, currency) VALUES ('US', 'USD');
_ Flyway migration script _
All three ways are valid options to initialize our data - so choose according to your own preference. Only the parallel use of multiple ways should be avoided.
Top comments (2)
This works, but I compare it to solutions some other frameworks have it isn't soo good.
For example in Laravel you can define seed's and run them via a cli.
Would be cool to create something similar for spring
Interesting approach, thanks for link - yes a dedicated option would be useful. Here you have to choose between liquibase, taking care of execution, or using an ApplicationRunner, where you have to ensure yourself that every initialization is running only once.