I trying to integrate Flyway for migrations in a Spring Boot project with Hibernate and Spring JPA. I'm getting the following Exception:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flyway' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Invocation of init method failed; nested exception is org.flywaydb.core.api.FlywayException: Found non-empty schema "PUBLIC" without metadata table! Use init() or set initOnMigrate to true to initialize the metadata table.
My pom.xml is looking like this:
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>3.2</version>
</dependency>
I'm using Hibernate and a config java file for postgres (dev stage) and h2 (local). The signatures are looking like this:
#Bean(initMethod = "migrate")
public Flyway flyway() {
Flyway fly = new Flyway();
fly.clean();
fly.init();
//flyway.setInitOnMigrate(true);
fly.setSchemas("SBA_DIALOG");
//flyway.setLocations("filesystem:src/main/resources/db/migration");
fly.setDataSource(this.dataSource());
fly.migrate();
return fly;
}
#Bean(name = "sbaEntityManagerFactory") #DependsOn("flyway")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
...
I can't find anything about my problem described in this question.
Can anybody help?
Spring-Boot is capable on it's own to do this.
Just add flyway as dependency to your project and spring-boot will pick it up.
Flyway migration will start when the service starts up.
If you already have some tables in the database add:
spring.flyway.baselineOnMigrate = true
in your property file to keep flyway calm when it discovers that some tables already exist. ;-)
Flyway should pick up your datasource. If you need for example another user or something like that for flyway, you can set these properties:
spring.flyway.url: jdbc:postgresql://${db.host}/${db.name}
spring.flyway.user: MYUSER
spring.flyway.password: MYPWD
(Of course add your values! You can use SPEL to reference other properties)
Update
One word of caution: If you use a clustered database you may encounter problems that multiple instances that are started at the same time try to perform the updates at the same time. This is a problem when the table locks don't work, which happened to me using a clustered mariaDB.
For any one who want to solve it in java code you can use :
fly.setBaselineOnMigrate(true);
Edit(22-09-2020)
another solution also is :
spring:
flyway:
baselineOnMigrate: true
validateOnMigrate: false
Related
I am new at Hibernate.
Into my code, the connection to the DB is managed with the Hikari data source.
My code is right now multitenant, but it manages the same hibernate dialect for all tenants.
Is it possible to create a configuration where each tenant can use a different dialect?
The type of dialect can be provided as a tenant's property.
This is an example of the entityManagerFactory:
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
Map<String, Object> jpaProperties = new HashMap<>();
jpaProperties.put(..., ...);
jpaProperties.put(org.hibernate.cfg.Environment.DIALECT, "myDialect");
LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
emfBean.setPackagesToScan(new String[] {MyEntity.class.getPackage().getName()});
emfBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
emfBean.setJpaPropertyMap(jpaProperties);
return emfBean;
}
Edit
I was looking to this solution: it suggests to create a duplicated LocalContainerEntityManagerFactoryBean for each dialect.
What I do not understand is how can I tell when using one EntityManager (MySQL) and when the other one (Postgres or MsSQL): the solution discriminates the entities (each entity has its own DB) but in my case, all entities are on all DBs. Is the tenant that discriminates.
For example: if I create a second instance of LocalContainerEntityManagerFactoryBean (i.e. msSQLEntityManagerFactory()) with setted the dialect for SQL Server, the application fails to start with:
Application failed to start due to an exceptionorg.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'javax.persistence.EntityManagerFactory' available:
expected single matching bean but found 2:
msSQLEntityManagerFactory,entityManagerFactory
That's not really possible as the dialect affects certain quoting rules etc. which you can't just "swap out" at runtime based on a tenant identifier. Just create two persistence units, each pointing to a different data source for every database type. You will have to somehow lookup the appropriate EntityManager/EntityManagerFactory based on your tenant identifier which makes it a bit harder when you want to use Spring Data JPA as that requires a compilation static name reference for the entity manager factory. Maybe you can create a custom EntityManagerFactory that delegates all method calls to the appropriate instance based on the tenant identifier. Overall, this is not so easy and you will probably have to do a lot of trial and error.
IMO it would be better to have a separate application deployment with separate configuration if possible per database type.
I finally managed to find a solution for this problem.
I managed to get around the problem with the dialects by having an entity manager factory for each dialect (in this case MySQL, Postgres and MS SQL Server).
Create a bean for EntityManagerFactory and return a proxy of that interface and in the handler, based on your logic, you can switch which emf to use to suit the used data source.
I have created a video for this because it seems like there is no documentation online.
Session Scoped Connection
It is pretty similar to what you're trying to achieve but in my case the users are providing the credentials, so it's even more complicated.
I have a Spring Boot app that uses Hibernate and Spring Batch. I am using PostgreSQL for my backend database.
My project has 2 different data sources configured: one for Hibernate and one for Spring Batch. They are both in the same database but in different schemas.
My spring batch connection string is the following:
spring.batch.datasource.url=jdbc:postgresql://localhost:5432/mytestapp?currentSchema=springbatch
I have the Datasource configured in the following way:
#Bean("b_ds_prop")
#ConfigurationProperties("spring.batch.datasource")
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
#Bean("b_ds")
#DependsOn({"b_ds_prop"})
public DataSource batchFrameworkDatasource(DataSourceProperties dataSourceProperties) {
System.out.println("start print");
// dataSourceProperties.getSchema().stream().forEach(System.out::println);
// System.out.println("datasrcprop is null : " + (dataSourceProperties==null));
// System.out.println("datasrcprop.schema is null : " + (dataSourceProperties.getSchema()==null));
System.out.println("end print");
// dataSourceProperties.setSchema(Arrays.asList("spring_batch"));
return dataSourceProperties.initializeDataSourceBuilder().build();
}
#Bean
#DependsOn({"b_ds"})
public BatchConfigurer defaultBatchConfigurer(#Qualifier("b_ds") DataSource dataSource) {
return new DefaultBatchConfigurer(dataSource);
}
The currentSchema=springbatch setting is not respected, no matter what I do. All my Spring Batch tables keep ending up in public in Postgres.
The schema named springbatch is already present in my database : mytestapp.
I have tried currentSchema=springbatch, public, I have even tried spring.batch.table-prefix=springbatch..
I have tried everything, but still I cannot understand why my batch tables keep ending up in the public schema.
Note I have another connection string in my project like:
spring.hib.datasource.url=jdbc:postgresql://localhost:5432/mytestapp
I have also tried programmatically checking what is the value of schema in DataSourceProperties.
System.out.println("datasrcprop.schema is null : " + (dataSourceProperties.getSchema()==null));
This evaluates to true.
How can I set the schema to be different for Spring Batch ?
By default, Spring boot automatically creates the spring batch metadata tables at starting. It executes the default script which is located in org.springframework.batch.core package. In your situtation, this script is schema-postgresql.sql. This script creates tables in the postgre default schema so public if nothing is specified (and as we don't know which datasource spring boot uses to create spring batch metadata tables, we don't know what is the default schema).
When executing creation script, spring boot doesn't take into account spring.batch.table-prefix property which is only used to say spring batch where metadata tables are.
If you want to control this spring boot feature, you should :
modify the creation script (add the springbatch schema) and give spring boot the path for the modified script (spring.batch.schema property)
OR
create by yourself spring batch metadata tables before starting application and disable spring boot auto creation (spring.batch.initialize-schema=never property)
Good luck
Have you tried this configration:
spring.datasource.url=jdbc:postgresql://localhost:5432/mytestapp
#spring batch properties
spring.batch.job.enabled=false
spring.batch.initializer.enabled=false
spring.batch.initialize-schema=never
spring.batch.table-prefix=springbatch.
I'm having this problem with HikariCP and MySql in my maven project: a warning shows up saying:
Loading class 'com.mysql.jdbc.Driver'. This is deprecated. The new driver class is 'com.mysql.cj.jdbc.Driver'. The driver has automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
I want to get rid of this warning.
For that, I need to know why even having the maven MySql connector in its latest version and HikariCP configuration set to set the class com.mysql.cj.jdbc.Driver (which is the non-deprecated one) this warning still shows up.
Here it is the maven dependency in pom.xml:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
Then, I have this properties file called hikaricp-hibernate.properties
jdbcUrl=jdbc:mysql://localhost:3306/local_database
driverClassName=com.mysql.cj.jdbc.Driver
#user and password and other data omitted
Now, for the HibernateConfiguration I have:
package mypackage;
// ... imports
#Configuration
#EnableTransactionManagement
public class HibernateConfig {
private static final String HIBERNATE_PROPERTIES = "/hikaricp-hibernate.properties";
#Bean(name = "hikariDataSource")
public DataSource dataSource() {
HikariConfig config = new HikariConfig(HIBERNATE_PROPERTIES);
HikariDataSource dataSource = new HikariDataSource(config);
return dataSource;
}
}
In the same class, I register as beans the LocalSessionFactoryBean and the TransactionManager, but the code does not matter here.
I also already checked the configuration in debug mode to see if the driverClassName is the one I have put and the answer is yes.
Therefore, the logs are shown even if registered correctly.
Also, HikariCP docs tells me to use the jdbcUrl configuration:
The MySQL DataSource is known to be broken with respect to network timeout support. Use jdbcUrl configuration instead.
Why?
Does jdbcUrl configuration triggers automatically the old Class (if yes, how can I override and avoid this?)? And then it seems that it is deprecated and searches for another one? Does it ignore my driverClassName config?
There's no issue with the code that's here - the key points being:
You're listing a dependency on the 8.* branch of the mysql connector, which contains the new JDBC driver
You're listing the new driver under driverClassName in your properties file
You're instantiating the HikariConfig correctly, with the properties that you're defining.
If the old driver is being loaded, it therefore isn't in this code - it's either with a separate application, or somewhere else in this application.
I have standalone application. It’s on java, spring-boot, postgres and it has liquibase.
I need to deploy my app and liquibase should create all tables, etc. But it should do it into custom schema not in public. All service tables of liquibase (databasechangelog and databasechangeloglock) should be in custom schema too. How can I create my schema in DB before liquibase start to work? I must do it inside my app when it’s deploying, in config or some like. Without any manual intervention into the DB.
application.properties:
spring.datasource.jndi-name=java:/PostgresDS
spring.jpa.properties.hibernate.default_schema=my_schema
spring.jpa.show-sql = false
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.datasource.continue-on-error=true
spring.datasource.sql-script-encoding=UTF-8
liquibase.change-log = classpath:liquibase/changelog-master.yaml
liquibase.default-schema = my_schema
UPD:
When liquibase start, it's create two tables databasechangelogs and one more table. After that, liquibase start working. But I want liquibase in liquibase.default-schema = my_schema, but it's not exist when liquibase start to work and it an error: exception is liquibase.exception.LockException: liquibase.exception.DatabaseException: ERROR: schema "my_schema" does not exist
I want liquibase work in custom schema, not in public:
liquibase.default-schema = my_schema
but before liquibase can do it, the schema must be created. Liquibase can't do this because it not started yet and for start it needs schema.
Vicious circle.
I found a solution with my application.properties.
org.springframework.jdbc.datasource.init.ScriptUtils worked before liquibase.
Logs:
14:11:14,760 INFO [org.springframework.jdbc.datasource.init.ScriptUtils]
(ServerService Thread Pool -- 300) Executing SQL script from URL
[vfs:/content/app.war/WEB-INF/classes/schema.sql]
14:11:14,761 INFO [org.springframework.jdbc.datasource.init.ScriptUtils]
(ServerService Thread Pool -- 300) Executed SQL script from URL
[vfs:/content/app.war/WEB-INF/classes/schema.sql] in 1 ms.
14:11:14,912 ERROR [stderr] (ServerService Thread Pool -- 300) INFO 9/27/18
2:11 PM: liquibase: Successfully acquired change log lock
14:11:15,292 ERROR [stderr] (ServerService Thread Pool -- 300) INFO 9/27/18
2:11 PM: liquibase: Reading from my_schema.databasechangelog
14:11:15,320 ERROR [stderr] (ServerService Thread Pool -- 300) INFO 9/27/18
2:11 PM: liquibase: Successfully released change log lock
I just put schema.sql with CREATE SCHEMA IF NOT EXISTS my_schema; into resources dir and all working properly.
Thanks all for help.
Update: It's work for Spring boot 1.X. If you are use Spring Boot 2, you should enable schema.sql in properties file, with spring.datasource.initialization-mode=always.
More info in Spring Boot - Loading Initial Data
Update 2: In Spring Boot 2.5.2 (maybe in earlier versions too) this solution is not working now, as #peterh has wrote in comment. Sad but true. The last version I was try this solution and it's work was Spring Boot 2.0.9 In docs Spring Boot says that it was redesigned from Spring Boot 2.5.x
Update 3: Some information why they kill this feature -> https://github.com/spring-projects/spring-boot/issues/22741
You can use Spring Boot Pre-Liquibase module for this. It is exactly what it is meant for. It executes some SQL prior to executing Liquibase itself. Pre-Liquibase sets itself up in the Spring Boot AutoConfigure chain so that it is guaranteed to always execute before Liquibase.
Step 1
Add the following Starter to your project:
<dependency>
<groupId>net.lbruun.springboot</groupId>
<artifactId>preliquibase-spring-boot-starter</artifactId>
<version> ---latest-version--- </version>
</dependency>
Step 2
Add a SQL file to src/main/resources/preliquibase with a name of postgresql.sql and content like this:
CREATE SCHEMA IF NOT EXISTS ${spring.liquibase.default-schema};
The ${} syntax denotes a placeholder variable. Pre-Liquibase will resolve it from the properties in your Spring Environment.
Step 3
Set application properties like this:
spring.liquibase.default-schema=${my.db.schemaname}
spring.jpa.properties.hibernate.default_schema=${my.db.schemaname}
Now - in this example - the only thing left to decide is where the my.db.schemaname value comes from. That is your choice. The example project advocates that it should come from an OS environment variable, in particular if your are deploying to a cloud.
Final words
WARNING: Pre-Liquibase is possibly way too flexible in that it allows to execute any SQL code. Don't be tempted to put stuff in Pre-Liquibase files which rightfully belong in an Liquibase ChangeSet. Honestly, the only usage I can think of for Pre-Liquibase is to set up a database "home" (meaning a schema or a catalog) where Liquibase db objects can live so that instances of the same application can be separated by schema or catalog while residing on the same database server.
(Disclosure: I'm the author of Pre-Liquibase module)
To solve this, we need to run a SQL statement that creates the schema during Spring Boot initialization at the point when DataSource bean had been already initialized so DB connections can be easily obtained but before Liquibase runs.
By default, Spring Boot runs Liquibase by creating an InitializingBean named SpringLiquibase. This happens in LiquibaseAutoConfiguration.
Knowing this, we can use AbstractDependsOnBeanFactoryPostProcessor to configure SpringLiquibase to depend on our custom schema creating bean (SchemaInitBean in the example below) which depends on DataSource. This arranges the correct execution order.
My application.properties:
db.schema=my_schema
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.open-in-view=false
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.properties.hibernate.default_schema=${db.schema}
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.liquibase.enabled=true
spring.liquibase.change-log=classpath:db/changelog/db.changelog-master.xml
spring.liquibase.defaultSchema=${db.schema}
Add the #Configuration class below to the project, for example put it in a package processed by component scan.
#Slf4j
#Configuration
#ConditionalOnClass({ SpringLiquibase.class, DatabaseChange.class })
#ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled", matchIfMissing = true)
#AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class })
#Import({SchemaInit.SpringLiquibaseDependsOnPostProcessor.class})
public class SchemaInit {
#Component
#ConditionalOnProperty(prefix = "spring.liquibase", name = "enabled", matchIfMissing = true)
public static class SchemaInitBean implements InitializingBean {
private final DataSource dataSource;
private final String schemaName;
#Autowired
public SchemaInitBean(DataSource dataSource, #Value("${db.schema}") String schemaName) {
this.dataSource = dataSource;
this.schemaName = schemaName;
}
#Override
public void afterPropertiesSet() {
try (Connection conn = dataSource.getConnection();
Statement statement = conn.createStatement()) {
log.info("Going to create DB schema '{}' if not exists.", schemaName);
statement.execute("create schema if not exists " + schemaName);
} catch (SQLException e) {
throw new RuntimeException("Failed to create schema '" + schemaName + "'", e);
}
}
}
#ConditionalOnBean(SchemaInitBean.class)
static class SpringLiquibaseDependsOnPostProcessor extends AbstractDependsOnBeanFactoryPostProcessor {
SpringLiquibaseDependsOnPostProcessor() {
// Configure the 3rd party SpringLiquibase bean to depend on our SchemaInitBean
super(SpringLiquibase.class, SchemaInitBean.class);
}
}
}
This solution does not require external libraries like Spring Boot Pre-Liquibase and not affected by limitations on data.sql / schema.sql support. My main motivation for finding this solution was a requirement I had that schema name must be a configurable property.
Putting everything in one class and using plain JDBC is for brevity.
Simple solution based on Pavel D. answer.
It also can be used without liquibase
#Slf4j
#Component
public class SchemaConfig implements BeanPostProcessor {
#Value("${db.schema}")
private String schemaName;
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (!StringUtils.isEmpty(schemaName) && bean instanceof DataSource) {
DataSource dataSource = (DataSource) bean;
try (Connection conn = dataSource.getConnection();
Statement statement = conn.createStatement()) {
log.info("Going to create DB schema '{}' if not exists.", schemaName);
statement.execute("create schema if not exists " + schemaName);
} catch (SQLException e) {
throw new RuntimeException("Failed to create schema '" + schemaName + "'", e);
}
}
return bean;
}
}
I have a simple Web MVC application using Spring Boot that communicates with a database; the DB is H2 and has been in memory until now. I want to change that, and thus use a jdbc:h2:file:... URL.
Up until now, I have not needed to add any XML to configure my application, and I'd prefer it to stay that way if possible. But I can't figure out how to specify a different JDBC URL. I obtained and inspected the data source by passing it to an #Bean method:
org.apache.tomcat.jdbc.pool.DataSource#745e6f01{ConnectionPool[
defaultAutoCommit=null;
defaultReadOnly=null;
defaultTransactionIsolation=-1;
defaultCatalog=null;
driverClassName=org.h2.Driver;
maxActive=100;
maxIdle=100;
minIdle=10;
initialSize=10;
maxWait=30000;
testOnBorrow=false;
testOnReturn=false;
timeBetweenEvictionRunsMillis=5000;
numTestsPerEvictionRun=0;
minEvictableIdleTimeMillis=60000;
testWhileIdle=false;
testOnConnect=false;
password=********;
url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;
username=sa;
validationQuery=null;
validationQueryTimeout=-1;
validatorClassName=null;
validationInterval=30000;
accessToUnderlyingConnectionAllowed=true;
removeAbandoned=false;
removeAbandonedTimeout=60;
logAbandoned=false;
connectionProperties=null;
initSQL=null;
jdbcInterceptors=null;
jmxEnabled=true;
fairQueue=true;
useEquals=true;
abandonWhenPercentageFull=0;
maxAge=0;
useLock=false;
dataSource=null;
dataSourceJNDI=null;
suspectTimeout=0;
alternateUsernameAllowed=false;
commitOnReturn=false;
rollbackOnReturn=false;
useDisposableConnectionFacade=true;
logValidationErrors=false;
propagateInterruptState=false;
ignoreExceptionOnPreLoad=false;
}
(newlines mine)
The setup of that bean seems rather intricate, so I want to interfere with it as little as possible - just replace the default JDBC URL.
How can I configure individual properties for Spring to create the datasource? Preferably in Java, but if there is a concise XML way I'm happy as well. I just want to avoid adding 100 lines of boilerplate for something equivalent to url=...
A DataSource is auto configured by Spring Boot for you. To influence how and what there are several properties you can set. Those are prefixed with spring.datasource, for a list take a look at the Spring Boot Reference Guide for a full list.
In your case simply add the following to the application.properties file
spring.datasource.url=jdbc:h2:file:...
This will tell Spring Boot to use this URL instead of the default.
As H2 is considered an in-memory database and not a regular database, when using JPA this will lead to your database to be dropped when the application is stopped. To fix this simply add the following
spring.jpa.hibernate.ddl-auto=update
To specify a dialect simply add the following
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
or even simpler
spring.jpa.database=H2