Change datasource properties for JPA Spring Configuration during runtime - java

I am working on a Spring project that needs to connect to multiple databases.
The examples I have used are all connecting to one database using properties, set at the start of the application.
My current JPA configuration looks like this:
#Configuration
#EnableTransactionManagement
public class PersistenceJPAConfig{
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "com.google.persistence.model" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/spring_jpa");
dataSource.setUsername( "user" );
dataSource.setPassword( "password" );
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return properties;
}
}
In this case, the datasource properties (driver, url, username, password) are already set. But is it possible to modify this current bean or create a new method that would allow me to modify those properties during runtime?
For example, while the application is running, is there a way for me to manually disconnect from the current database, modify the datasource properties, and reconnect to the new database instead?

You can't change this properties in runtime but if you need to use multiple databases you can create multiple persistence units. Here you can find example:
http://www.baeldung.com/spring-data-jpa-multiple-databases

The feature you're looking for is called multi-tenancy.
See, for example, this guide.

Related

Refresh the datasource on password rotation in SpringBoot Application

Can someone please guide me, how I can refresh the datasource whenever the password is rotated for the database.
Basically I don't want any manual step to refresh the datasource(like calling any endpoint). Rather than I can poll a file to see if DB credentials are rotated using FileWatcher service.
I have already read few solutions over stackoverflow regarding the same ask. But I couldn't able to implement it successfully.
Since I am new to stackoverflow can't comment on others question to get solution clarified.
Below is simple class for creating the connection pool.
#Configuration
#EnableTransactionManagement
public class JpaConfig {
#Value("${db.username}")
private String username;
#Value("${db.password}")
private String password;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.example");
factory.setDataSource(dataSource());
Properties properties = new Properties();
properties.put("hibernate.format_sql", true);
properties.put("hibernate.show_sql", true);
properties.put("hibernate.hbm2ddl.auto", "none");
properties.put("hibernate.generate_statistics", false);
factory.setJpaProperties(properties);
return factory;
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
private DataSource dataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl("db_connection_url");
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
hikariConfig.setPoolName("test_pool");
HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);
return hikariDataSource;
}
}
You can use "Spring Cloud Bus". And whenever you change the datasource password configuration, your service can listen for this event so that it can create a new bean based on the new configuration.
Please look at this document
https://spring.io/projects/spring-cloud-bus
This repo has solution for this question : https://github.com/visa2learn/spring-cloud-vault-db-cred-rotation
I find it quite useful.
Just to make solution better read about SecretLeaseContainer :
Event-based container to request secrets from Vault and renew the associated Lease. Secrets can be rotated, depending on the requested RequestedSecret.getMode()

HibernateProperties not considered in creating LocalContainerEntityManagerFactoryBean manually

I'm trying to setup multiple databases in an existing spring boot application.
So, I've created a configuration bean like this for each database:
#Configuration
#ComponentScan("com.example.user.data")
#EnableJpaRepositories(basePackages = "com.example.user.data.dao",
entityManagerFactoryRef = "userEntityManagerFactory",
transactionManagerRef = "userTransactionManager")
public class UserDataConfig {
#Bean(name = "userDataSourceProps")
#ConfigurationProperties("app.user.datasource")
DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
#Bean(name = "userDataSource")
DataSource dataSource() {
return dataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class)
.build();
}
#Bean(name = "userEntityManagerFactory")
LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder entityManagerFactoryBuilder) {
return entityManagerFactoryBuilder
.dataSource(dataSource())
.packages("com.example.user.data.dao.entity")
.persistenceUnit("users")
.build();
}
#Bean(name = "userTransactionManager")
PlatformTransactionManager transactionManager(#Qualifier("userEntityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
}
And disabled these auto-configurations:
DataSourceAutoConfiguration
DataSourceTransactionManagerAutoConfiguration
Everything works fine, but the jpa/hibernate properties does not applied;
For example i have entities which follows naming convention : SpringPhysicalNamingStrategy (which should be used by default); After these changes, the naming convention does not work, even if i define the property:
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
I've debugged EntityManagerFactoryBuilder creation, its jpaProperties field is empty!
Have i missed something? or I should pass jpa-properties and/or hibernate-properties manually in creating LocalContainerEntityManagerFactoryBean? For example:
return entityManagerFactoryBuilder
.dataSource(dataSource())
.properties(jpaAndHibernateProperties())
.packages("com.example.user.data.dao.entity")
.persistenceUnit("users")
.build();
UPDATE:
After testing, I realized that only HibernateProperties (spring.jpa.hibernate path) does not applied. But JpaProperties (spring.jpa path) applied.
Based on org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration#entityManagerFactory, we need to create vendor-properties and pass it to EntityManagerFactoryBuilder.properties:
return entityManagerFactoryBuilder
.dataSource(dataSource())
.properties(vendorProperties())
.packages("com.example.user.data.dao.entity")
.persistenceUnit(persistenceUnit)
.build();
protected Map<String, Object> vendorProperties() {
return new LinkedHashMap<>(this.hibernateProperties
.determineHibernateProperties(jpaProperties.getProperties(),
new HibernateSettings()
.ddlAuto(() -> "none")
.hibernatePropertiesCustomizers(new ArrayList<>()))
);
}

Hibernate Spring: spring.jpa.hibernate.ddl-auto property doesn't create generate tables doesn't work

I'm using Spring MVC with Hibernate. I configure Hibernate using configuration Java class:
DbConfig.java:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "testproject", entityManagerFactoryRef = "entityManagerFactory")
public class DbConfig {
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(getDatasource());
entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
entityManagerFactoryBean.setJpaProperties(getHibernateProperties());
entityManagerFactoryBean.setPackagesToScan("testproject");
return entityManagerFactoryBean;
}
#Bean
public DataSource getDatasource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://127.0.0.1:5432/testproject");
dataSource.setUsername("postgres");
dataSource.setPassword("mypass");
return dataSource;
}
#Bean
public SessionFactory getSessionFactory() throws IOException {
LocalSessionFactoryBean sessionFactoryBean = new LocalSessionFactoryBean();
sessionFactoryBean.setPackagesToScan("testproject");
//getHibernateProperties method is a private method
sessionFactoryBean.setHibernateProperties(getHibernateProperties());
sessionFactoryBean.setDataSource(getDatasource());
sessionFactoryBean.afterPropertiesSet();
return sessionFactoryBean.getObject();
}
#Bean(name = "transactionManager")
public HibernateTransactionManager getTransactionManager() throws IOException {
HibernateTransactionManager transactionManager = new HibernateTransactionManager();
transactionManager.setSessionFactory(getSessionFactory());
return transactionManager;
}
private static Properties getHibernateProperties() {
Properties hibernateProperties = new Properties();
hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
hibernateProperties.put("hibernate.show_sql", false);
hibernateProperties.put("spring.jpa.hibernate.ddl-auto", "create");
System.out.println();
// other properties
return hibernateProperties;
}
}
The auto-creation of tables is configured inside getHibernateProperties method:
hibernateProperties.put("spring.jpa.hibernate.ddl-auto", "create");
However it doesn't seem to work: after I deploy server and try to insert an entity I get an error and the database has no tables.
The property spring.jpa.hibernate.ddl-auto is of type org.springframework.boot.autoconfigure.orm.jpa.JpaProperties.Hibernate which is from Spring Boot. But you are using Spring MVC.
use hibernate.hbm2ddl.auto instead.
Further Reading
Metadata Format in Spring Boot Reference
This is the properties code that works:
#Bean
private static Properties getHibernateProperties() {
Properties hibernateProperties = new Properties();
hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
hibernateProperties.put("hibernate.show_sql", true);
hibernateProperties.put( "hibernate.hbm2ddl.auto", "create-drop");
return hibernateProperties;
}

New Spring Boot #EntityScan doesn't work

As of Spring ORM v1.4 org.springframework.boot.orm.jpa.EntityScan was deprecated in favor of org.springframework.boot.autoconfigure.domain.EntityScan.
I was going to remove deprecated annotation in favor of new one, but such replacement cause IllegalStateException:
Caused by: java.lang.IllegalStateException: No persistence units parsed from {classpath*:META-INF/persistence.xml}
at org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager.obtainDefaultPersistenceUnitInfo(DefaultPersistenceUnitManager.java:680) ~[spring-orm-4.3.5.RELEASE.jar:4.3.5.RELEASE]
With org.springframework.boot.orm.jpa.EntityScan annotation, application starts and works correctly.
Here is my config:
#Configuration
#EntityScan("com.app.domain")
#EnableJpaRepositories("com.app.persistence.jpa")
public class JpaInfrastructureConfig {
// ... config props
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
entityManagerFactory.setJpaVendorAdapter(vendorAdapter);
entityManagerFactory.setDataSource(dataSource());
entityManagerFactory.setJpaProperties(new Properties() {{
put("hibernate.dialect", hibernateDialect);
put("hibernate.show_sql", hibernateShowSql);
put("hibernate.hbm2ddl.auto", hibernateHbm2ddl);
}});
return entityManagerFactory;
}
#Bean
public JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
// ...
}
It seams that I've missed something, haven't I?
If you take a look at this issue on Spring Boot's issue tracker, you'll find the behaviour changes with the new annotation, as now documented in the release notes.
In your example, the simplest change would be to add a line to call LocalContainerEntityManagerFactoryBean.setPackagesToScan(…​), as suggested in the release notes.

Hibernate ("showSql", "true") in javaconfig doesn't work

I use JPA with Hibernate as persistence provider, and I configure it with below configuration class.
The problem is that I never see SQL statements compiled by Hibernate though I have showSql=true in additional properties. Please check below. Now it is properties.setProperty("hibernate.showsql", "true");, but I also tried properties.setProperty("showSql", "true"); with no effect.
I don't have persistence.xml and I don't use root-context.xml.
My servlet-context.xml contains only
<mvc:annotation-driven />
<beans:bean
class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<task:annotation-driven />
Also, my meta-inf/log4j.xml has all loggers on INFO level including
<logger name="org.hibernate.SQL">
<level value="info" />
</logger>
which I added without any effect as well.
What am I doing wrong?
#Configuration
#EnableTransactionManagement
#ComponentScan("com")
public class MySQLconfiguration {
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "com" });
em.setPersistenceProviderClass(HibernatePersistenceProvider.class);
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean(destroyMethod = "close")
public DataSource dataSource(){
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("com.mysql.jdbc.Driver");
hikariConfig.setJdbcUrl("jdbc:mysql://localhost:3306/test");
hikariConfig.setUsername("user");
hikariConfig.setPassword("user");
hikariConfig.setMaximumPoolSize(5);
hikariConfig.setMaxLifetime(30000);
hikariConfig.setIdleTimeout(30000);
hikariConfig.setConnectionTestQuery("SELECT 1");
hikariConfig.setPoolName("springHikariCP");
hikariConfig.addDataSourceProperty("dataSource.cachePrepStmts", "true");
hikariConfig.addDataSourceProperty("dataSource.prepStmtCacheSize", "250");
hikariConfig.addDataSourceProperty("dataSource.prepStmtCacheSqlLimit", "2048");
hikariConfig.addDataSourceProperty("dataSource.useServerPrepStmts", "true");
HikariDataSource dataSource = new HikariDataSource(hikariConfig);
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "update");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect");
properties.setProperty("hibernate.archive.autodetection", "class");
properties.setProperty("hibernate.showSql", "true");
return properties;
}
}
The property key should be hibernate.show_sql, not hibernate.showSql.
To verify, you can take a look at Hibernate class AvailableSettings, which lists the available configuration options, one of which is:
/**
* Enable logging of generated SQL to the console
*/
String SHOW_SQL = "hibernate.show_sql";

Categories