Spring Boot change DataSource and JPA properties at runtime - java

I am writing a desktop Spring Boot and Data-JPA application.
Initial settings come from application.properties (some spring.datasource.* and spring.jpa.*)
One of the features of my program is possibility to specify database settings (rdbms type,host,port,username,password and so on) via ui.
That's why I want to redefine already initialized db properties at runtime.
That's why I am finding a way to do that.
I tried to do the following:
1) I wrote custom DbConfig where DataSource bean declared in Singleton Scope.
#Configuration
public class DBConfig {
#ConfigurationProperties(prefix = "spring.datasource")
#Bean
#Scope("singleton")
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.build();
}
}
2) In some DBSettingsController I got the instance of this bean and update new settings:
public class DBSettingsController {
...
#Autowired DataSource dataSource;
...
public void applySettings(){
if (dataSource instanceof org.apache.tomcat.jdbc.pool.DataSource){
org.apache.tomcat.jdbc.pool.DataSource tomcatDataSource = (org.apache.tomcat.jdbc.pool.DataSource) dataSource;
PoolConfiguration poolProperties = tomcatDataSource.getPoolProperties();
poolProperties.setUrl("new url");
poolProperties.setDriverClassName("new driver class name");
poolProperties.setUsername("new username");
poolProperties.setPassword("new password");
}
}
}
But it has no effect. Spring Data Repositories are steel using initialy initialized DataSource properties.
Also I heard about Spring Cloud Config and #RefreshScope. But i think it's a kind of overhead to run http webserver alongside of my small desktop application.
Might it is possible to write custom scope for such beans?
Or by some way bind changes made in application.properties and corresponding beans properties?

Here is my solution (it might be outdated as it was created in 2016th):
DbConfig (It does not really needed, I just added for completeness config)
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.jta.JtaTransactionManager;
import javax.sql.DataSource;
#Configuration
public class DBConfig extends HibernateJpaAutoConfiguration {
#Value("${spring.jpa.orm}")
private String orm; // this is need for my entities declared in orm.xml located in resources directory
#SuppressWarnings("SpringJavaAutowiringInspection")
public DBConfig(DataSource dataSource, JpaProperties jpaProperties, ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider) {
super(dataSource, jpaProperties, jtaTransactionManagerProvider);
}
#Override
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder factoryBuilder)
{
final LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = super.entityManagerFactory(factoryBuilder);
entityManagerFactoryBean.setMappingResources(orm);
return entityManagerFactoryBean;
}
}
DataSourceConfig
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
#Configuration
public class DataSourceConfig {
#Bean
#Qualifier("default")
#ConfigurationProperties(prefix = "spring.datasource")
protected DataSource defaultDataSource(){
return DataSourceBuilder
.create()
.build();
}
#Bean
#Primary
#Scope("singleton")
public AbstractRoutingDataSource routingDataSource(#Autowired #Qualifier("default") DataSource defaultDataSource){
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.addDataSource(RoutingDataSource.DEFAULT,defaultDataSource);
routingDataSource.setDefaultTargetDataSource(defaultDataSource);
return routingDataSource;
}
}
My extension of RoutingDataSource:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
public class RoutingDataSource extends AbstractRoutingDataSource {
static final int DEFAULT = 0;
static final int NEW = 1;
private volatile int key = DEFAULT;
void setKey(int key){
this.key = key;
}
private Map<Object,Object> dataSources = new HashMap();
RoutingDataSource() {
setTargetDataSources(dataSources);
}
void addDataSource(int key, DataSource dataSource){
dataSources.put(new Integer(key),dataSource);
}
#Override
protected Object determineCurrentLookupKey() {
return new Integer(key);
}
#Override
protected DataSource determineTargetDataSource() {
return (DataSource) dataSources.get(key);
}
}
And here it's special spring component to swith datasource in runtime:
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
#Component
public class DBSettingsSwitcher {
#Autowired
private AbstractRoutingDataSource routingDataSource;
#Value("${spring.jpa.orm}")
private String ormMapping;
public void applySettings(DBSettings dbSettings){
if (routingDataSource instanceof RoutingDataSource){
// by default Spring uses DataSource from apache tomcat
DataSource dataSource = DataSourceBuilder
.create()
.username(dbSettings.getUserName())
.password(dbSettings.getPassword())
.url(dbSettings.JDBConnectionURL())
.driverClassName(dbSettings.driverClassName())
.build();
RoutingDataSource rds = (RoutingDataSource)routingDataSource;
rds.addDataSource(RoutingDataSource.NEW,dataSource);
rds.setKey(RoutingDataSource.NEW);
updateDDL(dbSettings);
}
}
private void updateDDL(DBSettings dbSettings){
/** worked on hibernate 5*/
StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
.applySetting("hibernate.connection.url", dbSettings.JDBConnectionURL())
.applySetting("hibernate.connection.username", dbSettings.getUserName())
.applySetting("hibernate.connection.password", dbSettings.getPassword())
.applySetting("hibernate.connection.driver_class", dbSettings.driverClassName())
.applySetting("hibernate.dialect", dbSettings.dialect())
.applySetting("show.sql", "false")
.build();
Metadata metadata = new MetadataSources()
.addResource(ormMapping)
.addPackage("specify_here_your_package_with_entities")
.getMetadataBuilder(registry)
.build();
new SchemaUpdate((MetadataImplementor) metadata).execute(false,true);
}
}
Where DB settings is just an interface (you should implement it according to your needs):
public interface DBSettings {
int getPort();
String getServer();
String getSelectedDataBaseName();
String getPassword();
String getUserName();
String dbmsType();
String JDBConnectionURL();
String driverClassName();
String dialect();
}
Having your own implementation of DBSettings and builded DBSettingsSwitcher in your Spring context, now you can just call DBSettingsSwitcher.applySettings(dbSettingsIml) and your data requests will be routed to new data source.

Related

Table is not being created, despite `hibernate.hbm2ddl.auto=update` in application.properties

For some reason, my db tables are not being created according to the entity model in the codebase. I am looking for the codebase to lead, and create the tables in the db, every time the server starts.
I have this in src/main/resources/application.properties
################### DataSource Configuration ##########################
jdbc.driverClassName=org.h2.Driver
jdbc.url=jdbc:h2:~/test
jdbc.username=sa
jdbc.password=
################### spring config ####################################
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create
################### Hibernate Configuration ##########################
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.show_sql=true
hibernate.hbm2ddl.auto=update
I bet some of that is redundant or maybe even contradictory. I doubt I need spring.jpa and hibernate, since they conflict. My current preference is to use Hibernate for ORM.
I also have:
package huru.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* Trivial JPA entity for vertx-spring demo
*/
#Entity
#Table(name="HURU_USER")
public class User {
#Id
#Column(name="ID")
private Integer productId;
#Column
private String description;
public Integer getProductId() {
return this.productId;
}
public String getDescription() {
return this.description;
}
}
and I have this:
package huru.repository;
import huru.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* Spring Data JPA repository to connect our service bean to data
*/
public interface UserRepository extends JpaRepository<User, Integer> {
}
and here is the SpringConfiguration
package huru.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
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;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.Properties;
/**
* Simple Java Spring configuration to be used for the Spring example application. This configuration is mainly
* composed of a database configuration and initial population via the script "products.sql" of the database for
* querying by our Spring service bean.
* <p>
* The Spring service bean and repository are scanned for via #EnableJpaRepositories and #ComponentScan annotations
*/
#Configuration
#EnableJpaRepositories(basePackages = {"huru.repository"})
#PropertySource(value = {"classpath:application.properties"})
#ComponentScan("huru.service")
public class SpringConfiguration {
#Autowired
private Environment env;
#Bean
#Autowired
public DataSource dataSource(DatabasePopulator populator) {
final DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(env.getProperty("jdbc.driverClassName"));
ds.setUrl(env.getProperty("jdbc.url"));
ds.setUsername(env.getProperty("jdbc.username"));
ds.setPassword(env.getProperty("jdbc.password"));
DatabasePopulatorUtils.execute(populator, ds);
return ds;
}
#Bean
#Autowired
public LocalContainerEntityManagerFactoryBean entityManagerFactory(final DataSource dataSource) {
final LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource);
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(Boolean.TRUE);
vendorAdapter.setShowSql(Boolean.TRUE);
factory.setDataSource(dataSource);
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("huru.entity");
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
jpaProperties.put("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
factory.setJpaProperties(jpaProperties);
return factory;
}
#Bean
#Autowired
public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
#Bean
public DatabasePopulator databasePopulator() {
final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setContinueOnError(false);
populator.addScript(new ClassPathResource("users.sql"));
return populator;
}
}
package huru.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
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;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.Properties;
/**
* Simple Java Spring configuration to be used for the Spring example application. This configuration is mainly
* composed of a database configuration and initial population via the script "products.sql" of the database for
* querying by our Spring service bean.
* <p>
* The Spring service bean and repository are scanned for via #EnableJpaRepositories and #ComponentScan annotations
*/
#Configuration
#EnableJpaRepositories(basePackages = {"huru.repository"})
#PropertySource(value = {"classpath:application.properties"})
#ComponentScan("huru.service")
public class SpringConfiguration {
#Autowired
private Environment env;
#Bean
#Autowired
public DataSource dataSource(DatabasePopulator populator) {
final DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(env.getProperty("jdbc.driverClassName"));
ds.setUrl(env.getProperty("jdbc.url"));
ds.setUsername(env.getProperty("jdbc.username"));
ds.setPassword(env.getProperty("jdbc.password"));
DatabasePopulatorUtils.execute(populator, ds);
return ds;
}
#Bean
#Autowired
public LocalContainerEntityManagerFactoryBean entityManagerFactory(final DataSource dataSource) {
final LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource);
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(Boolean.TRUE);
vendorAdapter.setShowSql(Boolean.TRUE);
factory.setDataSource(dataSource);
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("huru.entity");
Properties jpaProperties = new Properties();
jpaProperties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
jpaProperties.put("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
factory.setJpaProperties(jpaProperties);
return factory;
}
#Bean
#Autowired
public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory.getObject());
}
#Bean
public DatabasePopulator databasePopulator() {
final ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.setContinueOnError(false);
populator.addScript(new ClassPathResource("users.sql"));
return populator;
}
}
so yeah like I said, I am hoping to see a table called "USER" in the db, but it doesn't seem to be happening.
Yeah the datasource info in application.properties was wrong, should have looked like this instead:
jdbc.driverClassName=org.postgresql.Driver
jdbc.url=jdbc:postgresql://localhost:5432/oleg
jdbc.username=postgres
jdbc.password=postgres

Create DataSource dynamically in multi-tenancy using Spingboot

I'm trying to implement multi-tenancy architecture in my project. I am referring "http://anakiou.blogspot.com/2015/08/multi-tenant-application-with-spring.html" example. Everything is working fine in this example. But problem in this example is whenever new datasource to be add then we should add it manually. But i want something like adding datasource dynamically without application restart.
This is my application.properties
spring.jpa.hibernate.ddl-auto=none
spring.thymeleaf.cache=false
spring.datasource.datasource1.url=jdbc:mysql://localhost:3306/tenant_1
spring.datasource.datasource1.username=root
spring.datasource.datasource1.password=root
spring.datasource.datasource1.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.datasource2.url=jdbc:mysql://localhost:3306/tenant_2
spring.datasource.datasource2.username=root
spring.datasource.datasource2.password=root
spring.datasource.datasource2.driver-class-name=com.mysql.jdbc.Driver
This is DataSourceConfig.java
package com.anakiou.mt.config;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
#Configuration
public class DataSourceConfig {
#Autowired
private MultitenancyProperties multitenancyProperties;
#Bean(name = { "dataSource", "dataSource1" })
#ConfigurationProperties(prefix = "spring.datasource.datasource1")
public DataSource dataSource1() {
DataSourceBuilder factory = DataSourceBuilder
.create(this.multitenancyProperties.getDatasource1().getClassLoader())
.driverClassName(this.multitenancyProperties.getDatasource1().getDriverClassName())
.username(this.multitenancyProperties.getDatasource1().getUsername())
.password(this.multitenancyProperties.getDatasource1().getPassword())
.url(this.multitenancyProperties.getDatasource1().getUrl());
return factory.build();
}
#Primary
#Bean(name = "dataSource2")
#ConfigurationProperties(prefix = "spring.datasource.datasource2")
public DataSource dataSource2() {
DataSourceBuilder factory = DataSourceBuilder
.create(this.multitenancyProperties.getDatasource2().getClassLoader())
.driverClassName(this.multitenancyProperties.getDatasource2().getDriverClassName())
.username(this.multitenancyProperties.getDatasource2().getUsername())
.password(this.multitenancyProperties.getDatasource2().getPassword())
.url(this.multitenancyProperties.getDatasource2().getUrl());
return factory.build();
}
}
MultitenancyProperties.java
package com.anakiou.mt.config;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
#ConfigurationProperties("spring.datasource")
public class MultitenancyProperties {
#NestedConfigurationProperty
private DataSourceProperties datasource1;
#NestedConfigurationProperty
private DataSourceProperties datasource2;
public DataSourceProperties getDatasource1() {
return datasource1;
}
public void setDatasource1(DataSourceProperties datasource1) {
this.datasource1 = datasource1;
}
public DataSourceProperties getDatasource2() {
return datasource2;
}
public void setDatasource2(DataSourceProperties datasource2) {
this.datasource2 = datasource2;
}
}
DataSourceBasedMultiTenantConnectionProviderImpl.java
package com.anakiou.mt.util;
import org.hibernate.engine.jdbc.connections.spi.AbstractDataSourceBasedMultiTenantConnectionProviderImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
#Component
public class DataSourceBasedMultiTenantConnectionProviderImpl extends
AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
private static final long serialVersionUID = 8168907057647334460L;
private static final String DEFAULT_TENANT_ID = "tenant_1";
#Autowired
private DataSource dataSource1;
#Autowired
private DataSource dataSource2;
private Map<String, DataSource> map;
#PostConstruct
public void load() {
map = new HashMap<>();
map.put("tenant_1", dataSource1);
map.put("tenant_2", dataSource2);
}
#Override
protected DataSource selectAnyDataSource() {
return map.get(DEFAULT_TENANT_ID);
}
#Override
protected DataSource selectDataSource(String tenantIdentifier) {
return map.get(tenantIdentifier);
}
}

Query is always execute before than AOP in SpringBoot and MyBatis application for dynamic datasource

Here , I want to make a SpringBoot and MyBatis application use dynamic datasource by AOP; But the AOP is always execute after query from database, so switch datasource is invalid because select is finished.
All my code is in https://github.com/helloworlde/SpringBoot-DynamicDataSource/tree/aspect_dao
My dependence is
compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.1')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-aop')
runtime('mysql:mysql-connector-java')
And application.properties
application.server.db.master.driver-class-name=com.mysql.jdbc.Driver
application.server.db.master.url=jdbc:mysql://localhost/redisapi?useSSL=false
application.server.db.master.port=3306
application.server.db.master.username=root
application.server.db.master.password=ihaveapen*^##
#application.server.db.master.database=123456
#
## application common config
application.server.db.slave.driver-class-name=com.mysql.jdbc.Driver
application.server.db.slave.url=jdbc:mysql:/localhost/redisapi2?useSSL=false
application.server.db.slave.port=3306
application.server.db.slave.username=root
application.server.db.slave.password=123456
#application.server.db.slave.database=redisapi
mybatis.type-aliases-package=cn.com.hellowood.dynamicdatasource.mapper
mybatis.mapper-locations=mappers/**Mapper.xml
Table
CREATE TABLE product(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
price DOUBLE(10,2) NOT NULL DEFAULT 0
);
DataSourceConfigur.java
package cn.com.hellowood.dynamicdatasource.configuration;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
#Configuration
public class DataSourceConfigurer {
#Bean("master")
#Primary
#ConfigurationProperties(prefix = "application.server.db.master")
public DataSource master() {
return DataSourceBuilder.create().build();
}
#Bean("slave")
#ConfigurationProperties(prefix = "application.server.db.slave")
public DataSource slave() {
return DataSourceBuilder.create().build();
}
#Bean("dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put("master", master());
dataSourceMap.put("slave", slave());
// Set master datasource as default
dynamicRoutingDataSource.setDefaultTargetDataSource(master());
// Set master and slave datasource as target datasource
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
// To put datasource keys into DataSourceContextHolder to judge if the datasource is exist
DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());
return dynamicRoutingDataSource;
}
#Bean
#ConfigurationProperties(prefix = "mybatis")
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// Here is very important, if don't config this, will can't switch datasource
// put all datasource into SqlSessionFactoryBean, then will autoconfig SqlSessionFactory
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
return sqlSessionFactoryBean;
}
#Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
DynamicRoutingDataSource.java
package cn.com.hellowood.dynamicdatasource.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private final Logger logger = LoggerFactory.getLogger(getClass());
#Override
protected Object determineCurrentLookupKey() {
logger.info("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey());
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
DynamicDataSourceContextHolder.java
package cn.com.hellowood.dynamicdatasource.configuration;
import java.util.ArrayList;
import java.util.List;
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
#Override
protected String initialValue() {
return "master";
}
};
public static List<Object> dataSourceKeys = new ArrayList<>();
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
public static String getDataSourceKey() {
return contextHolder.get();
}
public static void clearDataSourceKey() {
contextHolder.remove();
}
public static boolean containDataSourceKey(String key) {
return dataSourceKeys.contains(key);
}
}
DynamicDataSourceAspect.java
package cn.com.hellowood.dynamicdatasource.configuration;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
#Aspect
#Order(-100) // To ensure execute before #Transactional
#Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
private final String QUERY_PREFIX = "select";
#Pointcut("execution( * cn.com.hellowood.dynamicdatasource.mapper.*.*(..))")
public void daoAspect() {
}
#Before("daoAspect()")
public void switchDataSource(JoinPoint point) {
if (point.getSignature().getName().startsWith(QUERY_PREFIX)) {
DynamicDataSourceContextHolder.setDataSourceKey("slave");
logger.info("Switch DataSource to [{}] in Method [{}]",
DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
}
}
#After("daoAspect())")
public void restoreDataSource(JoinPoint point) {
DynamicDataSourceContextHolder.clearDataSourceKey();
logger.info("Restore DataSource to [{}] in Method [{}]",
DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
}
}
And have Controller, Service and Dao for query, But although I set Order of aspect as -100, it still execute query before AOP, could anyone find where is wrong, Thank you very much.
This is log screenshot
Finally I fixed this issue, Because I injected Bean of DataSourceTransactionManager, So transaction will be open in Service, so the aspect of DAO is not work until transaction finished.
Delete this code:
#Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}

create ConnectController in SocialConfigurerAdapter

My configuration is as below.
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.env.Environment;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.UserIdSource;
import org.springframework.social.config.annotation.ConnectionFactoryConfigurer;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.social.facebook.api.Facebook;
import org.springframework.social.facebook.connect.FacebookConnectionFactory;
import org.springframework.social.security.AuthenticationNameUserIdSource;
#Configuration
#EnableSocial
#PropertySource("classpath:social.properties")
public class SocialConfig extends SocialConfigurerAdapter {
#Autowired
private DataSource dataSource;
#Autowired
private Environment env;
#Override
public void addConnectionFactories(ConnectionFactoryConfigurer cfConfig, Environment env) {
FacebookConnectionFactory facebookConnectionFactory = new FacebookConnectionFactory(
this.env.getProperty("facebook.clientId"),
this.env.getProperty("facebook.clientSecret"));
cfConfig.addConnectionFactory(facebookConnectionFactory);
}
#Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
return new JdbcUsersConnectionRepository (
dataSource,
connectionFactoryLocator,
Encryptors.noOpText() // refer http://stackoverflow.com/questions/12619986/what-is-the-correct-way-to-configure-a-spring-textencryptor-for-use-on-heroku
);
//https://github.com/naturalprogrammer/spring-boot-security-social-sample/tree/master/src
// Create the table in the database by executing the commands at
// http://docs.spring.io/spring-social/docs/1.1.0.RELEASE/reference/htmlsingle/#section_jdbcConnectionFactory
// and then execute
// create unique index UserConnectionProviderUser on UserConnection(providerId, providerUserId);
}
#Bean
public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator, UsersConnectionRepository connectionRepository) {
return new ProviderSignInUtils(connectionFactoryLocator, connectionRepository);
};
#Bean
#Scope(value="request", proxyMode=ScopedProxyMode.INTERFACES)
public Facebook facebook(ConnectionRepository repository) {
Connection<Facebook> connection = repository.findPrimaryConnection(Facebook.class);
return connection != null ? connection.getApi() : null;
}
#Override
public UserIdSource getUserIdSource() {
return new AuthenticationNameUserIdSource();
}
}
I need to create a #bean of ConnectController, because I need to check if an account is associated with facebook account or not. And I need to disconnect or connect to facebook account. Except for these functionalities, I can use org.springframework.social.facebook.api.Facebook to pull or publish data to facebook.
So when I try to create a the ConnectController bean in this configuration, I cannot do it. As shown below, I do not have connectionFactoryLocator() and connectionRepository().
#Bean
public ConnectController connectController() {
ConnectController controller = new ConnectController(connectionFactoryLocator(), connectionRepository());
controller.setApplicationUrl(env.getRequiredProperty("application.url"));
return controller;
}
I do not know how to create these two methods. Also, I wonder why this configuration works without these two methods. Everything works, but the ConnectController.
One workaround would be that I create a customized ConnectController that extends SpringSoical's ConnectController. However, I do not know if this is a proper way.
Thanks,
What about:
#Bean
public ConnectController connectController(ConnectionFactoryLocator connectionFactoryLocator,
ConnectionRepository connectionRepository) {
ConnectController controller = new ConnectController(connectionFactoryLocator, connectionRepository);
controller.setApplicationUrl(env.getRequiredProperty("application.url"));
return controller;
}

Adding a second datasource - SpringBoot RepositoryRestService PersistenceConfig

I'm trying to find the best approach to adding a second datasource to our application. The main purpose is to expose CRUD ops against the db via rest, & need to bounce against a 2nd db for authentication and role management. We are not using XML configs.
Is there a way to simply add a second datasource bean in the existing PersistenceConfig.java file, or do we need to replicate the whole config class for the second db instance?
The application:
package foo;
import foo.config.PersistenceConfig;
import foo.config.RepositoryRestConfig;
import foo.config.WebConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#Configuration
#ComponentScan
#EnableJpaRepositories
#Import({PersistenceConfig.class, WebConfig.class, RepositoryRestConfig.class})
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The Repo:
package foo.repository;
import foo.Widget;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import java.util.List;
#RepositoryRestResource(collectionResourceRel = "widgets", path = "widgets")
public interface WidgetsRepository extends CrudRepository<Widget, Long> {
List<Widget> findByWidgetId(#Param("widgetid") long widgetId);
}
The persistence config :
package foo.config;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.orm.jpa.JpaDialect;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.PersistenceContext;
import javax.sql.DataSource;
#Configuration
#Import(RepositoryRestMvcConfiguration.class)
#EnableJpaRepositories
#EnableTransactionManagement
public class PersistenceConfig {
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.SQL_SERVER);
vendorAdapter.setShowSql(true);
final LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("foo.model");
factory.setDataSource(dataSource());
return factory;
}
#Bean(destroyMethod = "close")
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
dataSource.setUrl("jdbc:sqlserver://127.0.0.1:1433;databaseName=fooDB");
dataSource.setUsername("sa");
dataSource.setPassword("*******");
dataSource.setTestOnBorrow(true);
dataSource.setTestOnReturn(true);
dataSource.setTestWhileIdle(true);
dataSource.setTimeBetweenEvictionRunsMillis(1800000L);
dataSource.setNumTestsPerEvictionRun(3);
dataSource.setMinEvictableIdleTimeMillis(1800000L);
dataSource.setValidationQuery("SELECT 1");
return dataSource;
}
#Bean
public JpaDialect jpaDialect() {
return new HibernateJpaDialect();
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory().getObject());
return txManager;
}
}
Thank you for your assistance...
Take a look at #Qualifier annotation. With this annotation you are able to define various beans of the same type and assign them names. It is equivalent of id parameter in bean XML tag.
This is relevant part of Spring documentation.
First of all, it's worth noting that almost all of the configuration in PersistenceConfig is redundant as Spring Boot will automatically configure it for you. Pretty much the only thing that is non-default and you need to specify is your DataSource configuration, for example the SQLServer URL.
There's a section in the documentation that describes how to configure two DataSources using #Primary and application.properties:
Creating more than one data source works the same as creating the first one. You might want to mark one of them as #Primary if you are using the default auto-configuration for JDBC or JPA (then that one will be picked up by any #Autowired injections)."
#Bean
#Primary
#ConfigurationProperties(prefix="datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix="datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
You'd then configure these two DataSources using application.properties and the datasource.primary and datasource.secondary prefixes:
For example:
datasource.primary.jdbcUrl=jdbc:sqlserver://127.0.0.1:1433;databaseName=fooDB
datasource.primary.user=sa
datasource.primary.password=secret
datasource.primary.jdbcUrl=jdbc:sqlserver://127.0.0.1:1433;databaseName=barDB
datasource.primary.user=sa
datasource.primary.password=secret

Categories

Resources