How do I reset my database state after each unit test without making the whole test a transaction? - spring

I'm using Spring 3.1.1.RELEASE, Hibernate 4.1.0.Final, JPA 2, JUnit 4.8.1, and HSQL 2.2.7. I want to run some JUnit tests on my service methods, and after each test, I would like any data written to the in-memory database to be rolled back. However, I do NOT want the entire test to be treated as a transaction. For example in this test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "classpath:test-context.xml" })
public class ContractServiceTest
{
…
#Autowired
private ContractService m_contractService;
#Test
public void testUpdateContract()
{
// Add the contract
m_contractService.save(m_contract);
Assert.assertNotNull(m_contract.getId());
// Update the activation date by 6 months.
final Calendar activationDate = Calendar.getInstance();
activationDate.setTime(activationDate.getTime());
activationDate.add(Calendar.MONTH, 6);
m_contract.setActivationDate(activationDate.getTime());
m_contractService.save(m_contract);
final List<Contract> foundContracts = m_contractService.findContractByOppId(m_contract.getOpportunityId());
Assert.assertEquals(foundContracts.get(0), m_contract);
} // testUpdateContract
there are three calls to the service, ("m_contractService.save", "m_contractService.save", and "m_contractService.findContractByOppId") and each is treated as a transaction, which I want. But I don't know how to reset my in-memory database to its original state after each unit test.
Let me know if I need to provide additional information.

Since you are using Hibernate, you could use the property hibernate.hbm2ddl.auto to create the database on startup every time. You would also need to force the spring context to be reloaded after each test. You can do this with the #DirtiesContext annotation.
This might add a bit extra overhead to your tests, so the other solution is to just manually delete the data from each table.

#DirtiesContext was no solution for me because the whole application context gets destroyed an must be created after each test -> Took very long.
#Before was also not a good solution for me as I have to create #Before in each integration test.
So I decided to create an TestExecutionListener which recreates the database after each test. (With Liquibase, but it also works with Flyway and normal SQL)
public class CleanupDatabaseTestExecutionListener
extends AbstractTestExecutionListener {
public final int getOrder() {
return 2001;
}
private boolean alreadyCleared = false;
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (!alreadyCleared) {
cleanupDatabase(testContext);
alreadyCleared = true;
} else {
alreadyCleared = true;
}
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
cleanupDatabase(testContext);
}
private void cleanupDatabase(TestContext testContext) throws LiquibaseException {
ApplicationContext app = testContext.getApplicationContext();
SpringLiquibase springLiquibase = app.getBean(SpringLiquibase.class);
springLiquibase.setDropFirst(true);
springLiquibase.afterPropertiesSet(); //The database get recreated here
}
}
To use the TestExecutionListenere I created a custom test annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#RunWith(SpringRunner.class)
#SpringBootTest(classes = OurderApp.class)
#TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = {CleanupDatabaseTestExecutionListener.class}
)
public #interface OurderTest {
}
Last but not least, I can now create tests and I can be sure that the database is in a clean mode.
#RunWith(SpringRunner.class)
#OurderTest
public class ProductSaveServiceIntTest {
}
EDIT: I improved my solution a bit. I had the problem that sometime one test method destroyed my database for all upcoming tests within the test class. So I created the annotation
package com.ourder.e2e.utils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface ClearContext {
}
and added this to the CleanupDatabaseTestExectionListener.
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
if(testContext.getTestMethod().getAnnotation(ClearContext.class)!=null){
cleanupDatabase(testContext);
}
super.afterTestMethod(testContext);
}
with help of these two snippets I am now able to create tests like this:
#Test
#ClearContext
public void testWhichDirtiesDatabase() {}

If you use flyway for migrations, I use the following pattern:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class JUnit5Class {
#Autowired
Flyway flyway;
#BeforeAll
public void cleanUp(){
flyway.clean();
flyway.migrate();
}
}
#TestInstance allows you to make #BeforeAll non static and thus you can migrate only once per test class. If you want to reset it for each test remove the class anotation and make change #BeforeAll to #BeforeEach.

As of JUnit 5 you could also create a custom extension and access the data source from the Spring context, like so (using Kotlin):
class DatabaseCleanerExtension : AfterEachCallback {
override fun afterEach(context: ExtensionContext) {
val ds = SpringExtension.getApplicationContext(context).getBean(DataSource::class.java)
ds.connection.use { connection ->
connection.prepareStatement("DELETE FROM my_table").execute()
}
}
}
You can then register the extension as follows:
#SpringBootTest
#ExtendWith(DatabaseCleanerExtension::class)
class SpringJunitExtensionApplicationTests { .. }
Now after each test the callback is executed and you can easily annotate any test classes this applies to.
Here is also a video on settings this up.

Make a #Before method in which you delete all data from database. You are using Hibernate so you can use HQL: delete from Contract.

You can use #Transactional annotation at Junit class level from org.springframework.transaction.annotation.Transactional.
For example:
package org.test
import org.springframework.transaction.annotation.Transactional;
#Transactional
public class ArmyTest{
}

I solve the same problem using a random memory database for each test:
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
#TestPropertySource(properties = {
"spring.datasource.url=jdbc:hsqldb:mem:${random.uuid}"
})

I was having same issue and solved following this
http://www.javafixing.com/2021/10/fixed-how-to-cleanup-h2-db-after-each.html?m=1
basically you can clean the DB after every test method with:
#Sql(scripts = "clean_file.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
on clean_file.sql you can add all the SQL statements to reset the db

Related

How to execute code in a SpringBootTest before the Application is run?

I have a SpringBoot based command line application. The application creates or deletes some records in a database. It does so not directly via JDBC but rather through a special API (instance variable dbService).
The application class looks like this:
#SpringBootApplication
public class Application implements CommandLineRunner {
#Autowired
private DbService dbService;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) {
// Do something via dbService based on the spring properties
}
}
Now I'd like to create a SpringBoot test that would run the whole application with a configuration specially prepared for the test.
I run the test with an in-memory DB (H2) which is empty at the test start. Hence I'd like to insert some records into the DB -- as the setup for the test. The code for inserting the records must be executed
After the Spring context has been loaded -- so that I can use the bean dbService.
Before the Application is run -- so that the application runs with the prepared DB.
Somehow I fail to implement the above two points.
What I have so far is this:
#SpringBootTest
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
#ActiveProfiles("specialtest")
public class MyAppTest {
#Autowired
private DbService dbService;
private static final Logger logger = LoggerFactory.getLogger(MyAppTest.class);
// The expectation is that this method is executed after the spring context
// has been loaded and all beans created, but before the Application class
// is executed.
#EventListener(ApplicationStartedEvent.class)
public void preparedDbForTheTest() {
// Create some records via dbService
logger.info("Created records for the test");
}
// This test is executed after the application has run. Here we check
// whether the DB contains the expected records.
#Test
public void testApplication() {
// Check the DB contents
}
}
My problem is that the the method preparedDbForTheTest does not seem to get executed at all.
According to the SpringBoot docs, the event ApplicationReadyEvent is sent exactly when I want to execute the setup code. But somehow the code is not executed.
If I annotate the method with #Before... (I tried several variants of it) then it gets executed, but after the Application class has run.
What am I doing wrong?
Test classes aren't Spring-managed beans so things like #EventListener methods will be ignored.
The most conventional solution to your problem would be to add some #TestConfiguration that declares the #EventListener:
#SpringBootTest
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class MyAppTest {
private static final Logger logger = LoggerFactory.getLogger(MyAppTest.class);
#Test
public void testApplication() {
}
#TestConfiguration
static class DatabasePreparation {
#EventListener(ApplicationStartedEvent.class)
public void preparedDbForTheTest() {
logger.info("Created records for the test");
}
}
}
A #TestConfiguration is additive so it'll be used alongside your application's main configuration. The preparedDbForTheTest method will now be called as part of refreshing the application context for the tests.
Note that, due to application context caching, this method won't be called for every test. It will only be called as part of refreshing the context which may then be shared among several tests.

Spring 2 + JUnit 5, share #MockBean for entire test suite

I create a Spring 2.3 application using Spring Data REST, Hibernate, Mysql.
I created my tests, I've around 450 tests splitted in about 70 files. Because the persistence layer leans on a multi tenant approach (single db per tenant) using a Hikari connection pool, I've the need to avoid the pool is initializated for each test file but at the same time I need to use #MockBean because I need to mock up some repositories in the entire Spring test contest.
I create a custom annotation for all test in my suite:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#SpringBootTest
#TestExecutionListeners(value = TestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#Transactional
#ActiveProfiles("test")
public #interface TestConfig {
}
Reading many posts and the doc, I know if I use #MockBean inside a test, the Spring context is reloaded and therefore a new pool connection is created in my case.
My idea is to create a #MockBean and share it with all tests in my suite so the context is not reloaded every time.
I tried several approaches:
#Log4j2
public class TestExecutionListener extends AbstractTestExecutionListener implements Ordered {
#Override
public void beforeTestMethod(TestContext testContext) throws Exception {
try {
TestDbUtils testDbUtils = (TestDbUtils) testContext.getApplicationContext().getBean(TestDbUtils.class);
testDbUtils.truncateDB();
TenantRepository tenantRepository = mock(TenantRepository.class);
testContext.setAttribute("tenantRepository", tenantRepository);
TenantContext.setCurrentTenantId("test");
when(tenantRepository.findByTenantId("test")).thenReturn(testDbUtils.fakeTenant());
} catch (Exception e) {
}
}
#Override
public int getOrder() {
return Integer.MAX_VALUE;
}
}
All my tests are annotated like this:
#TestConfig
#Log4j2
public class InvoiceTests {
#Test
public void test1(){
}
}
Unfortunately my tenantRepository.findByTenantId() is not mocked up. I also tried to create an abstract superclass:
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
#TestExecutionListeners(value = TestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#Transactional
#ActiveProfiles("test")
public abstract class AbstractIntegrationTest {
#MockBean
protected TenantRepository tenantRepository;
#MockBean
protected SubscriptionRepository subscriptionRepository;
#Autowired
protected TestDbUtils testDbUtils;
#BeforeAll
public void beforeAll() {
when(tenantRepository.findByTenantId("test")).thenReturn(testDbUtils.fakeTenant());
}
#BeforeEach
public void setup() {
testDbUtils.truncateDB();
TenantContext.setCurrentTenantId("test");
}
}
Even if my tests extended this superclass, during the run all of them were skipped (not sure why).
Is there any way to accomplish what I described?

Testing ApplicationListener with #SQL

We have a spring bean doing some initialization stuff at bootup of a spring-boot application. To do so, the application registers a ApplicationListener<ApplicationReadyEvent>, which queries the database and acts upon the data.
To test this process, we have to inject testdata in the database. Our first try was to init the database with a test-specific ApplicationListener<ApplicationStartedEvent>. This works, however, the code looks rather nasty. The idea was to use the #Sql annotation instead to load the initial data.
This works not as expected, as the data is injected after the ApplicationReadyEvent has been published. I was unable to find means to change the phase during which the #Sql data is written to the database.
Is there a way to ensure, the data of #Sql is written prior to publishing the ApplicationReadyEvent? The test is currently otherwise annotated to run with SpringRunner, #DataJpaTest and with #DirtiesContext.
Edit: Provide Code
The ApplicationListener ist provided as this:
#Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
#Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
// Do someting with SQL-Data
}
}
While the test looks like this:
#RunWith(SpringRunner.class)
#ComponentScan
#Import(SomeTestConfig.class)
#DataJpaTest
//#SQL("/somedata.sql")
public class SomeTest {
#Test
public void test() {
// Assert ApplicationListener has run
}
}
With the test-config as follows:
#TestConfiguration
#Profile(ReplayTest.PROFILE_SOME_TEST)
class SomeTestConfig {
#Bean
public ApplicationListener<ApplicationStartedEvent> testSetupBean() {
return new ApplicationListener<ApplicationStartedEvent>() {
// Insert data within onApplicationEvent-Method
};
}
}
If I uncomment #SQL and comment-out the #Import, the testdata is visible from within the test itself but not from within the ApplicationListener.
You can make use of the #Order annotation. You can annotate your application listeners and your test with it. By default, this has lowest precedence. So you should provide higher precedence to your application listener and lowest to your test.
Listener:
#Override
#Order(Ordered.HIGHEST_PRECEDENCE)
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("...publishing application event");
}
Test:
#Test
#Sql(scripts = "test.sql")
#Order
public void test() {
System.out.println("...loading data");
}

how to conditionally not create beans in spring boot?

In my application, I have a component that reads data from other system when the application is started.
However, during testing, I don't want this component to be created
#Component
#Slf4j
public class DeviceStatisticsSyncHandler {
#EventListener
public void handle(ApplicationReadyEvent event) {
syncDeviceStatisticsDataSync();
}
#Value("${test.mode:false}")
public boolean serviceEnabled;
}
I can use condition to solve this, but other code readers need to understand, so I don't think this is a very good method:
#EventListener(condition = "#deviceStatisticsSyncHandler .isServiceEnabled()")
public void handle(ApplicationReadyEvent event) {
syncDeviceStatisticsDataSync();
}
public boolean isServiceEnabled() {
return !serviceEnabled;
}
#Value("${test.mode:false}")
public boolean serviceEnabled;
My application doesn't use Profiles, is there any other method to solve this problem.
Spring Boot version:2.1.3
One possible option is not to load the DeviceStaticsticsSyncHandler at all if you're in a test mode.
The "test.mode" is not a good name here, because the production code contains something tightly bound to the tests.
How about the following approach:
#Component
#ConditionalOnProperty(name ="device.stats.handler.enabled", havingValue = "true", matchIfMissing=true)
public class DeviceStatisticsSyncHandler {
// do whatever you need here, but there is no need for "test.mode" enabled related code here
}
Now in Tests you can define a test property "device.stats.handler.enabled=false" on the test itself or even place that definition in src/test/reources/application.properties so it will be false for all tests in the module.
An obvious advantage is that this definition is pretty much self explanatory and can be easy understood by other project maintainers.
for me, it's not the case of the condition rather environment-related. I will solve this problem using spring profile.
Step 1: Create an Interface first
public interface DeviceStatisticsSyncHandler {
public void handle(ApplicationReadyEvent event);
}
Step 2: Create an Implementation for production
#Component
#Profile("!test")
public class DeviceStatisticsSyncHandlerImpl implements DeviceStatisticsSyncHandler {
#EventListener
#Override
public void handle(ApplicationReadyEvent event) {
syncDeviceStatisticsDataSync();
}
}
step 3: create an implementation of test
#Component
#Profile("test")
public class DeviceStatisticsSyncHandlerTestImpl implements DeviceStatisticsSyncHandler {
#EventListener
#Override
public void handle(ApplicationReadyEvent event) {
//do Nothing
}
}
final step
All you need to do is set/toggle the property
-Dspring.profiles.active=test
or
-Dspring.profiles.active=prod
I found a way to achieve this without any further external configuration required.
The idea is to create a general configuration that applies to all integration tests and use #MockBean there to replace the real bean. So one should create a class like this under the test classpath (i.e. that is not scanned during normal application launch):
#Configuration
public class IntegrationTestConfiguration
{
#MockBean
public DeviceStatisticsSyncHandler deviceStatisticsSyncHandler;
}
I was actually surprised that #MockBean can be used here, but the Javadoc explicitly points that out: Can be used as a class level annotation or on fields in either #Configuration classes, or test classes that are #RunWith the SpringRunner..

Spring Boot - Loading Initial Data

I'm wondering what the best way to load initial database data before the application starts? What I'm looking for is something that will fill my H2 database with data.
For example, I have a domain model "User" I can access users by going to /users but initially there won't be any users in the database so I have to create them. Is there anyway to fill the database with data automatically?
At the moment I have a Bean that gets instantiated by the container and creates users for me.
Example:
#Component
public class DataLoader {
private UserRepository userRepository;
#Autowired
public DataLoader(UserRepository userRepository) {
this.userRepository = userRepository;
LoadUsers();
}
private void LoadUsers() {
userRepository.save(new User("lala", "lala", "lala"));
}
}
But I very much doubt that is the best way of doing it. Or is it?
You can create a data.sql file in your src/main/resources folder and it will be automatically executed on startup. In this file you can add some insert statements, eg.:
INSERT INTO users (username, firstname, lastname) VALUES
('lala', 'lala', 'lala'),
('lolo', 'lolo', 'lolo');
Similarly, you can create a schema.sql file (or schema-h2.sql) as well to create your schema:
CREATE TABLE task (
id INTEGER PRIMARY KEY,
description VARCHAR(64) NOT NULL,
completed BIT NOT NULL);
Though normally you shouldn't have to do this since Spring boot already configures Hibernate to create your schema based on your entities for an in memory database. If you really want to use schema.sql you'll have to disable this feature by adding this to your application.properties:
spring.jpa.hibernate.ddl-auto=none
More information can be found at the documentation about Database initialization.
If you're using Spring Boot 2, database initialization only works for embedded databases (H2, HSQLDB, ...). If you want to use it for other databases as well, you need to change the initialization mode property:
spring.sql.init.mode=always # Spring Boot >=v2.5.0
spring.datasource.initialization-mode=always # Spring Boot <v2.5.0
If you're using multiple database vendors, you can name your file data-h2.sql or data-mysql.sql depending on which database platform you want to use.
To make that work, you'll have to configure the datasource platform property:
spring.sql.init.platform=h2 # Spring Boot >=v2.5.0
spring.datasource.platform=h2 # Spring Boot <v2.5.0
If I just want to insert simple test data I often implement a ApplicationRunner. Implementations of this interface are run at application startup and can use e.g. a autowired repository to insert some test data.
I think such an implementation would be slightly more explicit than yours because the interface implies that your implementation contains something you would like to do directly after your application is ready.
Your implementation would look sth. like this:
#Component
public class DataLoader implements ApplicationRunner {
private UserRepository userRepository;
#Autowired
public DataLoader(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void run(ApplicationArguments args) {
userRepository.save(new User("lala", "lala", "lala"));
}
}
You can add a spring.datasource.data property to application.properties listing the sql files you want to run. Like this:
spring.datasource.data=classpath:accounts.sql, classpath:books.sql, classpath:reviews.sql
//or (depending on SB version)
spring.sql.init.data-locations=classpath:accounts.sql, classpath:books.sql, file:reviews.sql
The sql insert statements in each of these files will then be run, allowing you to keep things tidy.
If you put the files in the classpath, for example in src/main/resources they will be applied. Or replace classpath: with file: and use an absolute path to the file
If you want to run DDL type SQL then use:
spring.datasource.schema=classpath:create_account_table.sql
// depending on spring version
spring.sql.init.schema-locations=classpath:create_account_table.sql
Edit: these solutions are great to get you up and running quickly, however for a more production ready solution it would be worth looking at a framework such as flyway, or liquibase. These frameworks integrate well with spring, and provide a quick, consistent, version-controlled means of initialising schema, and standing-data.
There are multiple ways how to achieve this. I prefer to use one of following options:
Option 1: Initializing with CommandLineRunner bean:
#Bean
public CommandLineRunner loadData(CustomerRepository repository) {
return (args) -> {
// save a couple of customers
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));
// fetch all customers
log.info("Customers found with findAll():");
log.info("-------------------------------");
for (Customer customer : repository.findAll()) {
log.info(customer.toString());
}
log.info("");
// fetch an individual customer by ID
Customer customer = repository.findOne(1L);
log.info("Customer found with findOne(1L):");
log.info("--------------------------------");
log.info(customer.toString());
log.info("");
// fetch customers by last name
log.info("Customer found with findByLastNameStartsWithIgnoreCase('Bauer'):");
log.info("--------------------------------------------");
for (Customer bauer : repository
.findByLastNameStartsWithIgnoreCase("Bauer")) {
log.info(bauer.toString());
}
log.info("");
}
}
Option 2: Initializing with schema and data SQL scripts
Prerequisites:
application.properties
spring.jpa.hibernate.ddl-auto=none
Explanation:
Without ddl-auto SQL scripts will be ignored by
Hibernate and trigger default behavior - scanning project for
#Entity and/or #Table annotated classes.
Then, in your MyApplication class paste this:
#Bean(name = "dataSource")
public DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:~/myDB;MV_STORE=false");
dataSource.setUsername("sa");
dataSource.setPassword("");
// schema init
Resource initSchema = new ClassPathResource("scripts/schema-h2.sql");
Resource initData = new ClassPathResource("scripts/data-h2.sql");
DatabasePopulator databasePopulator = new ResourceDatabasePopulator(initSchema, initData);
DatabasePopulatorUtils.execute(databasePopulator, dataSource);
return dataSource;
}
Where scripts folder is located under resources folder (IntelliJ Idea)
Hope it helps someone
Update 04-2021: Both options are great to combine with Spring Profiles as this will help you to avoid creating additional config files making your life as the developer easy.
You can use something like this:
#SpringBootApplication
public class Application {
#Autowired
private UserRepository userRepository;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
InitializingBean sendDatabase() {
return () -> {
userRepository.save(new User("John"));
userRepository.save(new User("Rambo"));
};
}
}
In Spring Boot 2 data.sql was not working with me as in spring boot 1.5
import.sql
In addition, a file named import.sql in the root of the classpath is executed on startup if Hibernate creates the schema from scratch (that is, if the ddl-auto property is set to create or create-drop).
Note very important if you insert Keys cannot be duplicated do not use ddl-auto property is set to update because with each restart will insert same data again
For more information you vist spring websit
https://docs.spring.io/spring-boot/docs/current/reference/html/howto-database-initialization.html
Spring Boot allows you to use a simple script to initialize your database, using Spring Batch.
Still, if you want to use something a bit more elaborated to manage DB versions and so on, Spring Boot integrates well with Flyway.
See also:
Spring Boot Database initialization
You can simply create a import.sql file in src/main/resources and Hibernate will execute it when the schema is created.
If you came here and nothing seems to work for you, then it might be the case that you are affected from some changes that were introduced with Spring Boot 2.5 and onwards.
Here is the total set of properties which I use for postgresql.
spring:
sql.init.mode: always <-----------------
datasource:
url: jdbc:postgresql://localhost:5432/products
username:
password:
jpa:
defer-datasource-initialization: true <------------------
hibernate:
ddl-auto: create-drop <----------------
database-platform: org.hibernate.dialect.PostgreSQLDialect
I have also marked with <--- the relevant properties for the current topic in order to achieve the following.
ORM vendor will create database schema for you from Java Entities model.
After database schema is created, initial data will be loaded to database from the file data.sql
Ps: Don't forget to add the file with initial data, data.sql under src/main/resources
Also as reference: Spring Boot 2.5 release notes
Here is the way I got that:
#Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
/**
* This event is executed as late as conceivably possible to indicate that
* the application is ready to service requests.
*/
#Autowired
private MovieRepositoryImpl movieRepository;
#Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
seedData();
}
private void seedData() {
movieRepository.save(new Movie("Example"));
// ... add more code
}
}
Thanks to the author of this article:
http://blog.netgloo.com/2014/11/13/run-code-at-spring-boot-startup/
I solved similar problem this way:
#Component
public class DataLoader {
#Autowired
private UserRepository userRepository;
//method invoked during the startup
#PostConstruct
public void loadData() {
userRepository.save(new User("user"));
}
//method invoked during the shutdown
#PreDestroy
public void removeData() {
userRepository.deleteAll();
}
}
You're almost there!
#Component
public class DataLoader implements CommandLineRunner {
private UserRepository userRepository;
public DataLoader(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public void run(String... args) throws Exception {
LoadUsers()
}
private void LoadUsers() {
userRepository.save(new User("lala", "lala", "lala"));
}
}
you can register and event listener to achieve that like below:
#EventListener
public void seed(ContextRefreshedEvent event) {
userRepository.save(new User("lala", "lala", "lala"));
}
When the ContextRefreshEvent is fired, we get access to all autowired beans in the application — including models and repositories.
If someone are struggling in make this to work even following the accepted answer, for me only work adding in my src/test/resources/application.yml the H2 datasource details:
spring:
datasource:
platform: h2
url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
driver-class-name: org.h2.Driver
username: sa
password:
If you want to insert only few rows and u have JPA Setup. You can use below
#SpringBootApplication
#Slf4j
public class HospitalManagementApplication {
public static void main(String[] args) {
SpringApplication.run(HospitalManagementApplication.class, args);
}
#Bean
ApplicationRunner init(PatientRepository repository) {
return (ApplicationArguments args) -> dataSetup(repository);
}
public void dataSetup(PatientRepository repository){
//inserts
}
You can use the below code. In the following code a database insertion occurs during the startup of the spring boot application.
#SpringBootApplication
public class Application implements CommandLineRunner {
#Autowired
private IService<Car> service;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) throws Exception {
for(int i=1; i<=1000; i++) {
Car car = new Car();
car.setName("Car Name "+i);
book.setPrice(50 + i);
service.saveOrUpdate(car);
}
}
}
This will also work.
#Bean
CommandLineRunner init (StudentRepo studentRepo){
return args -> {
// Adding two students objects
List<String> names = Arrays.asList("udara", "sampath");
names.forEach(name -> studentRepo.save(new Student(name)));
};
}
The most compact (for dynamic data) put #mathias-dpunkt solution into MainApp (with Lombok #AllArgsConstructor):
#SpringBootApplication
#AllArgsConstructor
public class RestaurantVotingApplication implements ApplicationRunner {
private final VoteRepository voteRepository;
private final UserRepository userRepository;
public static void main(String[] args) {
SpringApplication.run(RestaurantVotingApplication.class, args);
}
#Override
public void run(ApplicationArguments args) {
voteRepository.save(new Vote(userRepository.getOne(1), LocalDate.now(), LocalTime.now()));
}
}
One possibility is using incorrect JDBC URL. make sure it is jdbc:h2:mem:testdb
I created a library that facilitates initial/demo data loading in a Spring Boot application. You can find it at https://github.com/piotrpolak/spring-boot-data-fixtures
Once the data fixtures starter is on the classpath, it will automatically try to load DICTIONARY data upon application startup (this behavior can be controlled by properties) - all you need to do is to register a bean implementing DataFixture.
I find loading initial data by code superior to loading it using SQL scripts:
the logic of your fixtures lives close to your application logic/domain model and it is subject to refactoring as your domain evolves
you benefit from incremental demo data updates - imagine a QA environment with some user data (that needs not to be lost after application deploy) but at the same time you want to add data for the new features you developed
Example data fixture:
/**
* You can have as many fixture classes as you want.
* #Order annotation is respected for the fixtures belonging to the same set.
* You can make your demo database to be incrementally updated with fresh data
* each time the application is redeployed - all you need to do is to write
* a good condition in `canBeLoaded()` method.
*/
#Component
public class InitialDataFixture implements DataFixture {
private final LanguageRepository languageRepository;
// ...
#Override
public DataFixtureSet getSet() {
return DataFixtureSet.DICTIONARY;
}
/**
* We want to make sure the fixture is applied once and once only.
* A more sophisticated condition can be used to create incremental demo data
* over time without the need to reset the QA database (for example).
*/
#Override
public boolean canBeLoaded() {
return languageRepository.size() == 0;
}
/**
* The actual application of the fixture.
* Assuming that data fixtures are registered as beans, this method can call
* other services and/or repositories.
*/
#Override
public void load() {
languageRepository.saveAll(Arrays.asList(
new Language("en-US"), new Language("pl-PL")));
}
}
The concept is inspired by the Symfony Doctrine Data Fixtures bundle.
For those using MysqlDriver, I tried using Init attribute of #bean annotation and it works.
After created the Schema and Data sql file in the path of resources\Scripts
Add the line in application.properties
spring.jpa.hibernate.ddl-auto=none
Edit the Application content:
package com.spring_mvaen.demo;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
#SpringBootApplication
public class DemoApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Override
public void run(String... arg0) throws Exception {
System.out.println("Hello world from Command Line Runner");
}
#Bean(name = "dataSource")
public DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/db_spring_rest?useUnicode=true&useLegacyDatetimeCode=fa lse&serverTimezone=UTC&createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true&useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
// schema init
Resource initSchema = new ClassPathResource("scripts/schema.sql");
Resource initData = new ClassPathResource("scripts/data.sql");
DatabasePopulator databasePopulator = new ResourceDatabasePopulator(initSchema, initData);
DatabasePopulatorUtils.execute(databasePopulator, dataSource);
return dataSource;
}
}
If you want to do insert quick some queries, you can do with h2 data.sql queries as well
application.properties include:
spring.jpa.show-sql=true
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
#This directs the data.sql file and help it to run
spring.sql.init.data-locations=classpath:data.sql
spring.jpa.defer-datasource-initialization=true
data.sql file include:
INSERT INTO todo (id, username, description, target_date, is_done) VALUES (10001, 'lighteducation', 'Learn dance', CURRENT_DATE ,false);
INSERT INTO todo (id, username, description, target_date, is_done) VALUES (10002, 'lighteducation', 'Learn Angular14', CURRENT_DATE, false);
INSERT INTO todo (id, username, description, target_date, is_done) VALUES (10003, 'lighteducation', 'Learn Microservices', CURRENT_DATE,false);
P.S.: data.sql file should be inside src/main/resources
Your #Entity include
#Getter
#Setter
#AllArgsConstructor
#ToString
#Entity
public class Todo {
#Id
#GeneratedValue
private Long id;
private String username;
private String description;
private Date targetDate;
private boolean isDone;
protected Todo() {
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Todo todo = (Todo) o;
return id == todo.id;
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
That is it basically. it will be in memory, it means when you restart application data will arese and will be again same as queries show.
But it is easy for quick check
also you can access the path with http://localhost:8080/h2-console/ or you can edit the path from .properties file

Resources