I migrated some projects from Springboot 1.5.10 to 2.0.
Springboot 2.0.3Release, JDK10, mysql, hikari-cp
After this work, in JUnit test, all data in test cases remains at database. I think it doesn't works #Tranactional - org.springframework.transaction.annotation.Transactional
Here is part of application.yml and test class.
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
jpa:
show-sql: true
hibernate:
ddl-auto: none
database: mysql
database-platform: org.hibernate.dialect.MySQL5Dialect
Here is datasource.
#Configuration
#EnableTransactionManagement
public class DatasourceJPAConfig {
#Bean
#Primary
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
Here is part of JUnit test class.
#Transactional
#Rollback(true)
#RunWith(SpringRunner.class)
#ActiveProfiles("local")
#SpringBootTest
public class RepoTests {
#Autowired
private TestRepository testRepository;
#Test
public void saveTest() {
var name = "test";
var description = "test description"
var item = TestDomain.builder()
.name(name)
.description(description)
.build();
testRepository.save(item);
var optional = testRepository.findById(item.getId());
assertTrue(optional.isPresent());
assertEquals(optional.get().getDescription(), description);
assertEquals(optional.get().getName(), name);
}
}
after to run saveTest method, increase 1 row at database.
Add datasource to test and set auto commit to false
#Autowired
private DataSource dataSource;
And inside test
((HikariDataSource)dataSource).setAutoCommit(false);
Related
As the title suggests, I am looking for any means that could help me run Flyway migrations before Springs application context (persistence context to be precise) is loaded. The reason for that is I have few queries that run at the startup of the application. This leads to my tests failing as queries are being executed on database tables that do not yet exist. I am using H2 as my test database. Right now I am using only flyway core dependency:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>6.5.0</version>
<scope>test</scope>
</dependency>
and I have a single Flyway configuration class as follows:
#Configuration
class FlywayConfig {
private static final String resourcePath = "classpath:flyway/migrations";
private static final String sampleDataPath = "classpath:flyway/sample_data";
#Bean
Flyway flyway(#Value("${spring.datasource.url}") String dataSource,
#Value("${spring.datasource.username}") String username,
#Value("${spring.datasource.password}") String password) {
FluentConfiguration fluentConfiguration = Flyway.configure().dataSource(dataSource, username, password);
fluentConfiguration.locations(resourcePath, sampleDataPath);
Flyway flyway = fluentConfiguration.load();
return flyway;
}
}
and the properties are defined in application.yml
spring:
datasource:
username: sa
password: sa
url: 'jdbc:h2:mem:testdb;Mode=Oracle;IGNORE_CATALOGS=TRUE;DB_CLOSE_DELAY=-1;'
platform: h2
h2:
console:
enabled: true
jpa:
show-sql: true
What I would like to achieve is that: 1. flyway does migration 2. Spring context loads up (in that particular order)
I managed to be able to acomplish what I wanted by creating DataSource object manually in configuration file (not by Spring automatically from application.yml) and using #DependsOn on the DataSource object. This way I made sure that any possible database connection from the application context would be established once I do the migration in the Flyway bean (which btw I also tweaked). I was cleaning and migrating Flyway right before tests and now I have to do this while initalizing application context beans. Here is the code which worked for me:
#Configuration
class DatabaseConfig {
private static final String resourcePath = "classpath:flyway/migrations";
private static final String sampleDataPath = "classpath:flyway/sample_data";
private static final String dataSourceUrl = "jdbc:h2:mem:testdb;Mode=Oracle;IGNORE_CATALOGS=TRUE;DB_CLOSE_DELAY=-1;";
private static final String username = "sa";
private static final String password = "sa";
#Bean("flyway")
public Flyway flyway() {
FluentConfiguration fluentConfiguration = Flyway.configure().dataSource(dataSourceUrl, username, password);
fluentConfiguration.locations(resourcePath, sampleDataPath);
Flyway flyway = fluentConfiguration.load();
flyway.clean();
flyway.migrate();
return flyway;
}
#DependsOn("flyway")
#Bean
public DataSource dataSource() {
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("org.h2.Driver");
dataSourceBuilder.url(dataSourceUrl);
dataSourceBuilder.username(username);
dataSourceBuilder.password(password);
return dataSourceBuilder.build();
}
}
and here is the application.yml file (I got rid of datasource related records):
spring:
h2:
console:
enabled: true
jpa:
show-sql: true
flyway:
enabled: false
In my application.yml, I have the following configuration (to be able to customize variable on different environment with docker/docker-compose) :
spring:
datasource:
url: ${SPRING_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
The trouble is that Spring tries to autoconfigure this datasource while I am in #DataJpaTest, so with an embedded H2 database, and obviously it does not like placeholders....
I tried to exclude some autoconfiguration :
#DataJpaTest(excludeAutoConfiguration =
{DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class})
But then, nothing works, entityManagerFactory is missing, ...
I could probably use profiles but if possible I preferred another solution.
Did you try defining your own Datasource bean?
#Import(JpaTestConfiguration.class)
#DataJpaTest
#Configuration
public class JpaTestConfiguration{
//...
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
//...
}
I am using Spring Boot 1.5.8.Release and writing test cases using H2 in memory database. Currently in each test case class, we have #Before annotation, where we insert data using Spring Data classes.
I want to know, can I have a single place in project where we can define data for our all test cases. The database tables are created by Hybernate using entity classes. The only desired thing is about inserting data from single place instead of from #Before in each test case class.
I tried to use data.sql containing Insert statements but with it, Spring does not generate schema objects (tables) due to which I get table not found errors. I do not want to specify Create Table statement for each table in schema.sql
application-test.yml
spring:
datasource:
url: jdbc:h2:mem:test;
driverClassName: org.h2.Driver
username: sa
password:
jpa:
database: h2
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create
naming.physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
properties:
hibernate:
show_sql: true
format_sql: false
Schema.sql
CREATE SCHEMA AB AUTHORIZATION SA;
AbcControllerTest.java
#RunWith(SpringRunner.class)
#ContextConfiguration(initializers = ConfigFileApplicationContextInitializer.class)
#SpringBootTest(classes = WebApp.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
#ActiveProfiles("test")
public class AbcControllerTest {
#Autowired
private MockMvc mockMvc;
#Autowired
private LeDataService leDataService;
#Before
public void setup() {
MyInfo myInfo = new MyInfo();
..............
..............
leDataService.save(myInfo);
}
#Test
public void getAbcTest() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/Abcs/1234567/12345678")
.with(SecurityMockMvcRequestPostProcessors.user("test").password("test123"))
.with(SecurityMockMvcRequestPostProcessors.csrf()))
.andExpect(status().isOk())
}
}
create new class annotated with #Component #Profile({ "dev", "test" }) and that implements CommandLineRunner then inject dependencies
Override run() method with your initial data that came with CommandLineRunner
for example
#Component
#Profile({ "dev", "test" })
setupInitialData implements CommandLineRunner {
UserService userService;
//bla bla
#Override
#Transactional
public void run(String... args) {
User user = new User;
user.setName("test");
userService.save(user);
//bla bla
}
}
I need to set up liquibase for two datasources in Spring, at the moment it seems that only one liquibase set up is possible and you can choose for which data source.
If you are using spring boot, here is the setup which can help you:
Configuration class:
#Configuration
public class DatasourceConfig {
#Primary
#Bean
#ConfigurationProperties(prefix = "datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "datasource.primary.liquibase")
public LiquibaseProperties primaryLiquibaseProperties() {
return new LiquibaseProperties();
}
#Bean
public SpringLiquibase primaryLiquibase() {
return springLiquibase(primaryDataSource(), primaryLiquibaseProperties());
}
#Bean
#ConfigurationProperties(prefix = "datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "datasource.secondary.liquibase")
public LiquibaseProperties secondaryLiquibaseProperties() {
return new LiquibaseProperties();
}
#Bean
public SpringLiquibase secondaryLiquibase() {
return springLiquibase(secondaryDataSource(), secondaryLiquibaseProperties());
}
private static SpringLiquibase springLiquibase(DataSource dataSource, LiquibaseProperties properties) {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource(dataSource);
liquibase.setChangeLog(properties.getChangeLog());
liquibase.setContexts(properties.getContexts());
liquibase.setDefaultSchema(properties.getDefaultSchema());
liquibase.setDropFirst(properties.isDropFirst());
liquibase.setShouldRun(properties.isEnabled());
liquibase.setLabels(properties.getLabels());
liquibase.setChangeLogParameters(properties.getParameters());
liquibase.setRollbackFile(properties.getRollbackFile());
return liquibase;
}
...
}
properties.yml
datasource:
primary:
url: jdbc:mysql://localhost/primary
username: username
password: password
liquibase:
change-log: classpath:/db/changelog/db.primary.changelog-master.xml
secondary:
url: jdbc:mysql://localhost/secondary
username: username
password: password
liquibase:
change-log: classpath:/db/changelog/db.secondary.changelog-master.xml
I've done a project that I can create multiple dataSources with your specific changeSets, so if you need to add another dataSource, it would just change your application.yml, no longer needing to change the code.
Configuration class
#Configuration
#ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled", matchIfMissing = true)
#EnableConfigurationProperties(LiquibaseProperties.class)
#AllArgsConstructor
public class LiquibaseConfiguration {
private LiquibaseProperties properties;
private DataSourceProperties dataSourceProperties;
#Bean
#DependsOn("tenantRoutingDataSource")
public MultiTenantDataSourceSpringLiquibase liquibaseMultiTenancy(Map<Object, Object> dataSources,
#Qualifier("taskExecutor") TaskExecutor taskExecutor) {
// to run changeSets of the liquibase asynchronous
MultiTenantDataSourceSpringLiquibase liquibase = new MultiTenantDataSourceSpringLiquibase(taskExecutor);
dataSources.forEach((tenant, dataSource) -> liquibase.addDataSource((String) tenant, (DataSource) dataSource));
dataSourceProperties.getDataSources().forEach(dbProperty -> {
if (dbProperty.getLiquibase() != null) {
liquibase.addLiquibaseProperties(dbProperty.getTenantId(), dbProperty.getLiquibase());
}
});
liquibase.setContexts(properties.getContexts());
liquibase.setChangeLog(properties.getChangeLog());
liquibase.setDefaultSchema(properties.getDefaultSchema());
liquibase.setDropFirst(properties.isDropFirst());
liquibase.setShouldRun(properties.isEnabled());
return liquibase;
}
}
application.yml
spring:
dataSources:
- tenantId: db1
url: jdbc:postgresql://localhost:5432/db1
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
liquibase:
enabled: true
default-schema: public
change-log: classpath:db/master/changelog/db.changelog-master.yaml
- tenantId: db2
url: jdbc:postgresql://localhost:5432/db2
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
- tenantId: db3
url: jdbc:postgresql://localhost:5432/db3
username: postgres
password: 123456
driver-class-name: org.postgresql.Driver
Link of repository: https://github.com/dijalmasilva/spring-boot-multitenancy-datasource-liquibase
I was in the need of supporting a dynamic amount of DataSources, not a fixed number of them. I found that you can use the same SpringLiquibase bean for multiple DataSources by making a service like this:
#Service
#DependsOn("liquibase")
public class LiquibaseService {
#Autowired
#Qualifier("liquibase")
private SpringLiquibase liquibase;
#PostConstruct
public void initialize() {
/* Obtain datasources from wherever. I obtain them from a master DB. It's up to you. */
List<DataSource> dataSources = obtainDataSources();
for (DataSource dataSource : dataSources) {
try {
liquibase.setDataSource(dataSource);
liquibase.setChangeLog("classpath:liquibase/emp.changelog.xml");
liquibase.setShouldRun(true);
// This runs Liquibase
liquibase.afterPropertiesSet();
} catch (LiquibaseException ex) {
throw new RuntimeException(ex);
}
}
}
}
For this to work, you should have a SpringLiquibase bean declared somewhere. In this example, I got this in one of my configuration files:
#Bean
public SpringLiquibase liquibase(LiquibaseProperties properties) {
SpringLiquibase liquibase = new SpringLiquibase();
liquibase.setDataSource(systemDataSource);
liquibase.setChangeLog("classpath:liquibase/sis.changelog.xml");
liquibase.setContexts(properties.getContexts());
liquibase.setDefaultSchema(properties.getDefaultSchema());
liquibase.setDropFirst(properties.isDropFirst());
liquibase.setLabels(properties.getLabels());
liquibase.setChangeLogParameters(properties.getParameters());
liquibase.setRollbackFile(properties.getRollbackFile());
// This is because we are running the process manually. Don't let SpringLiquibase do it.
liquibase.setShouldRun(false);
return liquibase;
}
The above highly depends on your DataSource configuration requirements. You could also need to put this on your main Application class so the Spring-Liquibase auto-configuration doesn't kick in:
#SpringBootApplication(exclude = {
LiquibaseAutoConfiguration.class
})
public class Application {
// Stuff...
}
Just have 2 datasources and 2 beans
<bean id="liquibase1" class="liquibase.integration.spring.SpringLiquibase">
<property name="dataSource" ref="dataSource1" />
<property name="changeLog" value="classpath:db1-changelog.xml" />
</bean>
<bean id="liquibase2" class="liquibase.integration.spring.SpringLiquibase">
<property name="dataSource" ref="dataSource2" />
<property name="changeLog" value="classpath:db2-changelog.xml" />
</bean>
You can also run multiple liquibase instance (i.e. not only limit to a primary and a secondary).
e.g. Your configuration java can have:
#Bean
#ConfigurationProperties(prefix = "liquibase1")
...
#Bean
#ConfigurationProperties(prefix = "liquibase2")
...
#Bean
#ConfigurationProperties(prefix = "liquibase3")
Your application.property can have:
liquibase1.default-schema=schemaA
...
liquibase2.default-schema=schemaB
...
liquibase3.default-schema=schemaC
...
And (excitingly), these springLiquibase instances can use the same Datasource, or different DataSource... however you like it.
Running order? I haven't found any official document, from my observation in debug, all liquibase migration runs according to the order you write in application.properties. Those who wants to run migration in one datasource, then go to another datasource, then come back to this datasource and run something else,,, you may want to try this multiple liquibase instance approach.
I tried to write integration test using annotation #DataJpaTest .
I have two datasource: Primary and secondary (class config)
in result i have an error:
expected single matching bean but found 2: primaryDataSource,secondary
then i tried to add a annotation
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.AUTO_CONFIGURED)
and With AUTO_CONFIGURED only DataSources configured by properties will be replaced but instead embedded h2 i saw Dialect : HHH000400: Using dialect: org.hibernate.dialect.Oracle10gDialect
how using #DataJpaTest with multiple datasources ?
public class DataSourcesConfig {
#Bean
#Primary
#ConfigurationProperties(prefix="spring.datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "secondary")
#ConfigurationProperties(prefix="datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
I have a #Primary configuration class
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
basePackages = {"com.something"}
)
public class APrimaryDBDBConfiguration {
#Primary
#Bean
#ConfigurationProperties("spring.datasource")
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties("spring.datasource.hikari")
public HikariDataSource dataSource() {
return dataSourceProperties()
.initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
#Primary
#Bean(name = "entityManagerFactory")
#Profile("!test")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com")
.persistenceUnit("some_persistence_unit")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Later, in my repository test classes:
#RunWith(SpringRunner.class)
#DataJpaTest
#ActiveProfiles("test")
#Import(APrimaryDBDBConfiguration.class)
Finally, my test properties have:
spring.jpa.hibernate.ddl-auto=create-drop
Check if you have h2 database added as dependency in test scope. If not, add and try:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
Found a possible solution here.
Basically you manually configure the H2 database appropriately instead of letting Spring do it automatically.
Create an application.properties file in “src/test/resources” with the following content
# Let Spring autodetect the different SQL Dialects of each datasource
spring.jpa.database=default
# Generate the DB schema in the In-Memory H2 databases based on the JPA Entities
spring.jpa.generate-ddl=true
# H2 In-Memory Database "foo" (used in tests instead of a real PostgreSQL DB)
spring.datasource.url=jdbc:h2:mem:foo;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
# H2 In-Memory Database "bar" (used in tests instead of a real PostgreSQL DB)
bar.datasource.url=jdbc:h2:mem:bar;DB_CLOSE_ON_EXIT=FALSE
bar.datasource.username=sa
bar.datasource.password=
bar.datasource.driver-class-name=org.h2.Driver