Why h2 doesn't see table that was just migrated? - java

I have a Spring Boot web service backed by PostgreSQL 10. Since there were a few rounds of development Flyway is used to apply all necessary changes in DB.
Now I need to cover one of our modules with test and thus I need to mock PostgreSQL and I decided to use H2. The weird part is that when I run test I can see that I have no problems with DB migration, but when I try to use repository I get
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "MY_TABLE" not found; SQL statement:
I've tried to switch H2 to PostgreSQL mode with MODE=PostgreSQL and DATABASE_TO_LOWER=TRUE in connection url, but it didn't help. Also I keep connection open with DB_CLOSE_DELAY=-1. SQL queries in Flyway scripts seems fine, because on any attempt to modify them I can easily get syntax error.
Config properties
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:test;MODE=PostgreSQL;DATABASE_TO_UPPER=FALSE;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1
username: sa
password: sa
Test class
#SpringBootTest(classes = MyConfig.class)
#RunWith(SpringRunner.class)
public class MailServiceTest {
#Autowired
private MyRepo repo;
#Test
public void x() throws Exception {
repo.findAll(); // exception is thrown here
}
}
Config class
#SpringBootConfiguration
#EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
#PropertySource("classpath:custom-application.properties")
#ComponentScan(basePackages = {"com.example.myproj"})
#EnableJpaRepositories(basePackages = "com.example.myproj", entityManagerFactoryRef = "customEntityManagerFactory", transactionManagerRef = "customTransactionManager")
#EntityScan(basePackages = "com.example.myproj")
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableJpaAuditing(auditorAwareRef = "customAuditorAware")
#EnableScheduling
public class MyConfig {
...
}
Any clue how to fix this or at least where to look for solution? Thank you

Just stuck on the same issue this morning.
Solution was in a configuration inherited from AbstractJdbcConfiguration to change quote handling:
#Bean
#Override
public JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStrategy, JdbcCustomConversions customConversions) {
JdbcMappingContext mappingContext = super.jdbcMappingContext(namingStrategy, customConversions);
mappingContext.setForceQuote(false);
return mappingContext;
}
TL;DR
Using JdbcTemplate to debug the test I found out, that select * from test is working, but select * from "test" doesn't.
It was the key finding to actually solve this issue.
In my case it was clear it is some kind of regression, because it worked before I upgraded Spring version.
So I started to search "quotes jdbc spring template" in google. Third in list was this:
https://spring.io/blog/2020/05/20/migrating-to-spring-data-jdbc-2-0

Related

Spring Boot Test not rolling back transactions (MS SQL Server, Spring Boot 2.7.5)

I am writing integration tests for a project with multiple datasources and want to test the repository and service layer. Problem is that the #Transactional annotation is not working and data inserted during tests is persisted.
This is a simple IT. I need to use #SpringBootTest instead of #DataJpaTest because multiple datasources are configured in a bean which handles assigning the right datasource to the correct repository.
#SpringBootTest(classes = { MyApp.class, TestSecurityConfiguration.class })
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#ActiveProfiles("database-test")
#Transactional
#Rollback
public class MyIT {
#Autowired
ChannelRepository channelRepository;
#Test
public void testChannels() {
Channel testChannel1 = new Channel();
testChannel1.setDescription("Test Channel 1");
channelRepository.save(testChannel1);
Channel testChannel2 = new Channel();
testChannel2.setDescription("Test Channel 2");
channelRepository.save(testChannel2);
Channel channel = channelRepository.findById(1).get();
assertThat(channel).isNotNull();
}
}
The dialect configured in the application.yml is "org.hibernate.dialect.SQLServer2012Dialect" and the database is our test database that runs on a dedicated server, not an embedded database.
The data is inserted without rollback.
After looking at the JpaTransactionManager logs using:
logging:
level:
sql: DEBUG
ROOT: DEBUG
org.springframework.orm.jpa: DEBUG
org.springframework.transaction: DEBUG
I saw that for each .save() call there was a new inner transaction. I tried to specify the transaction manager manually by passing the value to the #Transactional annotation and now it works. Looks like the DataSource beans are not picked up entirely correct. I got the idea based on this article where a ChainedTransactionManager is used for multiple datasources https://medium.com/preplaced/distributed-transaction-management-for-multiple-databases-with-springboot-jpa-and-hibernate-cde4e1b298e4

Junit Spring avoid to load twice application context datasource

I have this configuration classes:
#ComponentScan(
basePackages = {
"mypackage.controller",
"mypackage.service",
"mypackage.repository"
}
)
#TestPropertySource(locations="classpath:configuration.properties")
#Import({
H2Configuration.class
})
public class TestConfiguration {
}
#Configuration
public class H2Configuration {
#Bean
public DataSource dataSource() throws SQLException {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase db = builder
.setType(EmbeddedDatabaseType.H2)
.addScript("h2/create.sql")
.addScript("h2/insert.sql")
.build();
db.getConnection().setAutoCommit(false);
return db;
}
}
And I have this two class tests:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes = { TestConfiguration.class })
public class FirstRepositoryTest {
#Autowired
MyFirstRepositoryImpl repository;
#Before
public void initTest() {
}
#Test(expected = NullPointerException.class)
public void testNullRecords() {
repository.foo(null, null);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes = { TestConfiguration.class })
public class SecondRepositoryTest {
#Autowired
MySecondRepositoryImpl repository;
#Before
public void initTest() {
}
#Test(expected = NullPointerException.class)
public void testSomethingNullRecords() {
repository.something(null, null);
}
}
If I run junit test once for each class, all goes well.
In clean install phase tests fails because the application context is initialized twice.
For example it try to create the h2 tables twice and do the insert.sql script twice.
What I have to do for initialize the h2 database and so application context only once?
Thanks
I think you could start looking at the Spring documentation about Integration Testing.
It can also be a good practice to use transactional tests for integration tests (#Transactional), which rollback at the end of each test : see Transaction Management.
To avoid the cost of recreating the ApplicationContext for each test class, the cache may be used as explained here : Context Caching.
For integration testing with Embedded Database, you can also find documentation : Testing Data Access Logic with an Embedded Database.
A note from the previous link, matching your use case :
However, if you wish to create an embedded database that is shared
within a test suite, consider using the Spring TestContext Framework
and configuring the embedded database as a bean in the Spring
ApplicationContext as described in Creating an Embedded Database by
Using Spring XML and Creating an Embedded Database Programmatically.
I hope you will find some useful references.
Another good tip I found from Spring Boot documentation from Embedded Database Support :
They say :
If you are using this feature in your tests, you may notice that the
same database is reused by your whole test suite regardless of the
number of application contexts that you use. If you want to make sure
that each context has a separate embedded database, you should set
spring.datasource.generate-unique-name to true.
So to make each EmbeddedDatabase unique, you may try to create them with :
EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
...
.build();
In unit testing you must garantee that every test is repeatible hance context independent. Due to this is not good idea to load the context only once. Is better to reset after the execution. For this you can use #DirtiesContext(classMode = ClassMode.AFTER_CLASS) in your test classes
So you will force your context to restart when the next junit class is launched
So the reason that this is failing is that the database (H2) is resident in memory when you run the tests as part of clean/install. The create/insert scripts have already executed after the first test is run. Any subsequent test execution after this point will result in a re-execution of the same script(s) and the error will occur.
Update your create script with a DROP TABLE IF EXISTS <table name>;. This will ensure that the table is dropped then recreated.
NOTE: I'm not sure why you've specified AnnotationConfigContextLoader explicitly. I think, without that, the runner SpringJUnit4ClassRunner will cache contexts that have not been changed. I don't know specifically if that is the case here though.

Spring Boot integration test using multiple database types

In my test I need test with different databases (mysql, oracle, etc.) and I would like to know if it's possible with SpringRunner.
I'm using #SqlGroup and #Sql annotations, but I didn't discover how to indicate script files (sql) corresponding database.
Example:
#Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD, scripts = "classpath:tenantBeforeTestRun.sql")
This annotation configures my test to execute the script to all database types, but this file didn't work on Oracle.
#Sql annotation lets you define a SqlConfig which contains a datasource bean name.
Then you can define many datasource beans, with possibly different drivers and refer them from different #Sql. This might be helpful: Spring Boot Multiple Datasource
#Sql(..., config = #SqlConfig(datasource = "db1", ...)
application.properties:
#first db
spring.db1.url = [url]
spring.db1.username = [username]
spring.db1.password = [password]
spring.db1.driverClassName = oracle.jdbc.OracleDriver
#second db ...
spring.secondDatasource.url = [url]
spring.secondDatasource.username = [username]
spring.secondDatasource.password = [password]
spring.secondDatasource.driverClassName = oracle.jdbc.OracleDriver
Then, somewhere in #Configuration class:
#Bean(name = "db1")
#ConfigurationProperties(prefix="spring.db1")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}

spring boot autoconfiguration include

Is there a way to include autoconfigurations based on profiles? (It would be nice if there was a spring.autonfigure.include)
I would like to connect to an h2 database for testing and for local development. For ease of development, I would like DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class, and DataSourceTransactionManagerAutoConfiguration.class autoconfigured. However, I would like to be able to easily switch to an oracle database which is defined in the application server and configured in a Configuration class. When switching to the oracle database, I need to exclude the autoconfiguration classes above:
// This only works for the oracle database - need to include autoconfig
// classes for h2 database
#SpringBootApplication(
exclude = {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class },
scanBasePackages = {
"foo.bar"
})
I have an "h2" profile that configures the h2 database and several other profiles in which I want the live database (local, dev, test, qual, prod). I could use the spring.autoconfigure.exclude property on each of the live database profiles, but sometimes I want to switch between "live" and h2 databases locally. I could also figure out exactly what the excluded autoconfigure classes are doing and manually configure in an "h2" profile but I'd rather not duplicate effort.
Anyone have ideas as to how to accomplish this?
I was able to get this to work by splitting up the #SpringBootApplication annotation and providing specific #EnableAutoConfiguration annotations.
#Configuration
#ComponentScan(basePackages = {"foo.bar"})
#EnableTransactionManagement
#EnableConfigurationProperties
public class App extends SpringBootServletInitializer {
public static void main(String... args) throws Exception {
SpringApplication.run(App.class, args);
}
}
For the h2 database, I enable the "h2" profile and use this class:
#Profile("h2")
#Configuration
#EnableAutoConfiguration
public class H2Config {
#Bean
public ServletRegistrationBean h2servletRegistration() {
ServletRegistrationBean registration = new ServletRegistrationBean(new WebServlet());
registration.addUrlMappings("/console/*");
return registration;
}
}
And for the "live" oracle database, I disable the "h2" profile and use this class:
#Profile("!h2")
#Configuration
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class })
public class NonH2Config {
}

Spring Boot: How to setup test data with liquibase in unit test

I'm trying to setup the database schema and some test data with liquibase for some tests. Each test has a separate changelog which setup the schema and some specific data for the test.
In order to make my tests working, I need to drop the schema before each test and fill it with new test data. However, it seems that this is not working because some tests are failing because the old test data is still available. I think something with my configuration is not correct. How can I force liquibase to drop the schema before each test?
My tests look as following:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = MyTestConfig.class)
#TestPropertySource(properties = "liquibase.change-log=classpath:changelog/schema-with-testdata.xml")
public class MyRepositoryTest {
The config for the tests looks as follows:
#SpringApplicationConfiguration
#Configuration
#EnableAutoConfiguration
#ComponentScan("com.mypackage")
#EntityScan(basePackages = { "com.mypackage.domain" })
#EnableJpaRepositories(basePackages = { "com.mypackage.domain", "com.mypackage.infra.persistence" })
public class MyTestConfig {
And the application.properties under src/main/test/resources is
liquibase.drop-first=true
spring.jpa.hibernate.ddl-auto=none
There is a spring.liquibase.dropFirst config property. Maybe this is what you're looking for?
Not sure if this completely answers your question. There is another property liquibase.default-schema=schemaNameToCreate
But even with that I was never able to get it to create the schema from scratch.

Categories

Resources