Spring + jOOQ - transactional routing to read and write db's - java

We use Spring and jOOQ and at present read/write from primary DB. We do have replica DB's and are trying to have non critical reads (like from quartz jobs, pure read api's, etc.) to go to read.
I referred to https://vladmihalcea.com/read-write-read-only-transaction-routing-spring/ and understand that we need a transactional routing layer introduced which would point one way or the other depending on
#Transactional (readonly = true)
However, this doesn't seem to be working in our case, wherein all routing seems to be happening to primary db itself.
Here's the code configurations we use for primary
#Bean(name = "primaryTransactionAwareDataSourceProxy")
public TransactionAwareDataSourceProxy primaryTransactionAwareDataSource(
#Qualifier("primaryDataSource") DataSource primaryDataSource) {
return new TransactionAwareDataSourceProxy(primaryDataSource);
}
#Bean(name = "xyz")
public DataSourceConnectionProvider primaryConnectionProvider(
#Qualifier("primaryTransactionAwareDataSourceProxy") TransactionAwareDataSourceProxy datasource) {
return new DataSourceConnectionProvider(datasource);
}
#Bean(name = "primaryDsl")
#Primary
public DefaultDSLContext primaryDsl(
#Qualifier("primaryConfiguration") DefaultConfiguration configuration) {
return new DefaultDSLContext(configuration);
}
#Bean(name = "primaryConfiguration")
public DefaultConfiguration primaryConfiguration(
#Qualifier("primaryDatabaseProperties") DatabaseProperties props,
#Qualifier("xyz") DataSourceConnectionProvider provider) {
DefaultConfiguration jooqConfiguration = new DefaultConfiguration();
jooqConfiguration.set(provider);
jooqConfiguration.set(SQLDialect.MYSQL);
Settings settings = new Settings()
.withRenderMapping(new RenderMapping()
.withSchemata(new MappedSchema()
.withInput(<name>)
.withOutput(props.getName())));
jooqConfiguration.set(settings);
return jooqConfiguration;
}
and for replica we have along similar lines
#Bean(name = "replicaTransactionAwareDataSourceProxy")
public TransactionAwareDataSourceProxy replicaTransactionAwareDataSource(
#Qualifier("replicaDataSource") DataSource replicaDataSource) {
return new TransactionAwareDataSourceProxy(replicaDataSource);
}
#Bean(name = "xyz")
public DataSourceConnectionProvider replicaConnectionProvider(
#Qualifier("replicaTransactionAwareDataSourceProxy") TransactionAwareDataSourceProxy datasource) {
return new DataSourceConnectionProvider(datasource);
}
#Bean(name = "replicaDsl")
public DefaultDSLContext replicaDsl(
#Qualifier("replicaConfiguration") DefaultConfiguration configuration) {
return new DefaultDSLContext(configuration);
}
#Bean(name = "replicaConfiguration")
public DefaultConfiguration replicaConfiguration(
#Qualifier("replicaDatabaseProperties") DatabaseProperties props,
#Qualifier("xyz") DataSourceConnectionProvider provider) {
DefaultConfiguration jooqConfiguration = new DefaultConfiguration();
jooqConfiguration.set(provider);
jooqConfiguration.set(SQLDialect.MYSQL);
Settings settings = new Settings()
.withRenderMapping(new RenderMapping()
.withSchemata(new MappedSchema()
.withInput(<name>)
.withOutput(props.getName())));
jooqConfiguration.set(settings);
return jooqConfiguration;
}
The data sources are configured as
#Bean(name = "primaryDatabaseProperties")
#ConfigurationProperties(prefix = "database")
public DatabaseProperties primaryDatabaseProperties() {
return new DatabaseProperties ();
}
#Bean(name = "replicaDatabaseProperties")
#ConfigurationProperties(prefix = "database.replica")
public DatabaseProperties replicaDatabaseProperties() {
return new DatabaseProperties ();
}
#Bean(name = "primaryDataSource")
public DataSource primaryDataSource(
#Qualifier("primaryDatabaseProperties") DatabaseProperties props) {
return dataSource(props);
}
#Bean(name = "replicaDataSource")
public DataSource replicaDataSource(
#Qualifier("replicaDatabaseProperties") DatabaseProperties props) {
return dataSource(props);
}
And transactional routing data source defined as
#Bean
public TransactionRoutingDataSource actualDataSource(
#Qualifier("primaryTransactionAwareDataSourceProxy") DataSource primaryDataSource,
#Qualifier("replicaTransactionAwareDataSourceProxy") DataSource replicaDataSource) {
TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>() {{
put(DataSourceType.READ_WRITE, primaryDataSource);
put(DataSourceType.READ_ONLY, replicaDataSource);
}};
routingDataSource.setTargetDataSources(dataSourceMap);
return routingDataSource;
}
I have an implementation of AbstractRoutingDataSource defined to do the conditional check of if the current transaction is ready only and route to correct datasource accordingly
public class TransactionRoutingDataSource extends AbstractRoutingDataSource {
#Nullable
#Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ?
DataSourceType.READ_ONLY : DataSourceType.READ_WRITE;
}
}
And I use annotations to indicate the necessary routing in the service layer
#Override
#Transactional(readOnly = true)
public <return_type> <method_name>() {}
Any pointers as to what I am missing to have jOOQ route correctly to read db?

Related

Spring boot multiple external datasource stored in internal database

I have a spring boot project and I have one internal database with the configuration on the application.properties. In this database I have a Company table which contains the connection informations to external databases (all the external databases have same structure).
I created a class which create datasource when we need :
public class PgDataSource {
private static Map<Long, DataSource> dataSourceMap = new HashMap<>();
private static void createDataSource(Company company) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setMaximumPoolSize(10);
hikariConfig.setMinimumIdle(1);
hikariConfig.setJdbcUrl("jdbc:postgresql://"+company.getUrl()+"/"+company.getIdClient());
hikariConfig.setUsername(company.getUsername());
hikariConfig.setPassword(company.getPassword());
dataSourceMap.put(company.getId(), new HikariDataSource(hikariConfig));
}
public static DataSource getDataSource(Company company) {
if (!dataSourceMap.containsKey(company.getId()))
createDataSource(company);
return dataSourceMap.get(company.getId());
}
}
Could you tell me if this solution is the best and if I can use JPA with this solution ?
Thanks
The difficulty in your setup is not multiple datasources, but the fact that they are dynamic, i.e. determined at run time.
In addition to a DataSource JPA uses EntityManagerFactory and TransactionManager, which are determined statically i.e. at compile time. So it's not easy to use JPA with dynamic data sources.
In Spring Boot 2 you can try the AbstractRoutingDataSource, which allows to route JPA calls to a different datasource based on some (thread-bound) context. Here's an example of how it can be used and a demo application.
Alternatively you can turn your setup into a static setup, then use the regular multiple datasource approach. The downside is that the list of "companies" will be fixed at compile time, and so is probably not what you want.
Thanks it's work fine !
My solution :
I create a first datasource for my local database with #Primary annotation.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "localEntityManagerFactory",
basePackages = {"fr.axygest.akostaxi.local"}
)
public class LocalConfig {
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "localEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("fr.axygest.akostaxi.local.model")
.persistenceUnit("local")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("localEntityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Next, for the x external databases save in the company table of the local database, I use AbstractRoutingDataSource
I store a current context as a ThreadLocal :
public class ThreadPostgresqlStorage {
private static ThreadLocal<Long> context = new ThreadLocal<>();
public static void setContext(Long companyId) {
context.set(companyId);
}
public static Long getContext() {
return context.get();
}
}
I defined the RoutingSource to extend the AbstractRoutingDataSource :
public class RoutingSource extends AbstractRoutingDataSource
{
#Override
protected Object determineCurrentLookupKey() {
return ThreadPostgresqlStorage.getContext();
}
}
And the config class create all the databases connection saved in company table :
#Configuration
#EnableJpaRepositories(
basePackages = {"fr.axygest.akostaxi.postgresql"},
entityManagerFactoryRef = "pgEntityManager"
)
#EnableTransactionManagement
public class PgConfig {
private final CompanyRepository companyRepository;
#Autowired
public PgConfig(CompanyRepository companyRepository) {
this.companyRepository = companyRepository;
}
#Bean(name = "pgDataSource")
public DataSource pgDataSource() {
RoutingSource routingSource = new RoutingSource();
List<Company> companies = companyRepository.findAll();
HashMap<Object, Object> map = new HashMap<>(companies.size());
companies.forEach(company -> {
map.put(company.getId(), createDataSource(company));
});
routingSource.setTargetDataSources(map);
routingSource.afterPropertiesSet();
return routingSource;
}
#Bean(name = "pgEntityManager")
public LocalContainerEntityManagerFactoryBean pgEntityManager(
EntityManagerFactoryBuilder builder,
#Qualifier("pgDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("fr.axygest.akostaxi.postgresql.model")
.persistenceUnit("pg")
.properties(jpaProperties())
.build();
}
private DataSource createDataSource(Company company) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setMaximumPoolSize(10);
hikariConfig.setMinimumIdle(1);
hikariConfig.setJdbcUrl("jdbc:postgresql://" + company.getUrl() + "/" + company.getIdClient());
hikariConfig.setUsername(company.getUsername());
hikariConfig.setPassword(company.getPassword());
return new HikariDataSource(hikariConfig);
}
private Map<String, Object> jpaProperties() {
Map<String, Object> props = new HashMap<String, Object>();
props.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return props;
}
}

DataSource Router problem in multiples access of multiples datasources

I have project spring-boot using datasource routes in three diferents datasources.
This is my configuration:
#Configuration
#EnableCaching
public class CachingConfiguration extends CachingConfigurerSupport {
#Override
public KeyGenerator keyGenerator() {
return new EnvironmentAwareCacheKeyGenerator();
}
}
--
public class DatabaseContextHolder {
private static final ThreadLocal<DatabaseEnvironment> CONTEXT =
new ThreadLocal<>();
public static void set(DatabaseEnvironment databaseEnvironment) {
CONTEXT.set(databaseEnvironment);
}
public static DatabaseEnvironment getEnvironment() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}
--
#Configuration
#EnableJpaRepositories(basePackageClasses = UsuarioRepository.class,
entityManagerFactoryRef = "customerEntityManager",
transactionManagerRef = "customerTransactionManager")
#EnableTransactionManagement
public class DatasourceConfiguration {
#Bean
#ConfigurationProperties(prefix = "spring.ciclocairu.datasource")
public DataSource ciclocairuDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "spring.palmas.datasource")
public DataSource palmasDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "spring.megabike.datasource")
public DataSource megabikeDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#Primary
public DataSource customerDataSource() {
DataSourceRouter router = new DataSourceRouter();
final HashMap<Object, Object> map = new HashMap<>(3);
map.put(DatabaseEnvironment.CICLOCAIRU, ciclocairuDataSource());
map.put(DatabaseEnvironment.PALMAS, palmasDataSource());
map.put(DatabaseEnvironment.MEGABIKE, megabikeDataSource());
router.setTargetDataSources(map);
return router;
}
#Autowired(required = false)
private PersistenceUnitManager persistenceUnitManager;
#Bean
#Primary
#ConfigurationProperties("spring.jpa")
public JpaProperties customerJpaProperties() {
return new JpaProperties();
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean customerEntityManager(
final JpaProperties customerJpaProperties) {
EntityManagerFactoryBuilder builder =
createEntityManagerFactoryBuilder(customerJpaProperties);
return builder.dataSource(customerDataSource()).packages(Users.class)
.persistenceUnit("customerEntityManager").build();
}
#Bean
#Primary
public JpaTransactionManager customerTransactionManager(
#Qualifier("customerEntityManager") final EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}
private JpaVendorAdapter createJpaVendorAdapter(
JpaProperties jpaProperties) {
AbstractJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(jpaProperties.isShowSql());
adapter.setDatabase(jpaProperties.getDatabase());
adapter.setDatabasePlatform(jpaProperties.getDatabasePlatform());
//adapter.setGenerateDdl(jpaProperties.isGenerateDdl());
return adapter;
}
private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(
JpaProperties customerJpaProperties) {
JpaVendorAdapter jpaVendorAdapter =
createJpaVendorAdapter(customerJpaProperties);
return new EntityManagerFactoryBuilder(jpaVendorAdapter,
customerJpaProperties.getProperties(), this.persistenceUnitManager);
}
}
--
public class DataSourceRouter extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
if(DatabaseContextHolder.getEnvironment() == null)
DatabaseContextHolder.set(DatabaseEnvironment.CICLOCAIRU);
return DatabaseContextHolder.getEnvironment();
}
}
--
public class EnvironmentAwareCacheKeyGenerator implements KeyGenerator {
#Override
public Object generate(Object target, Method method, Object... params) {
String key = DatabaseContextHolder.getEnvironment().name() + "-" + (
method == null ? "" : method.getName() + "-") + StringUtils
.collectionToDelimitedString(Arrays.asList(params), "-");
return key;
}
}
I set datasource using
DatabaseContextHolder.set(DatabaseEnvironment.CICLOCAIRU);
Go to problem:
For example, two users in diferents datasources: 1 and 2
if one user using datasource 1, and send a request,
The other user that using datasource 2,
yours next request , instead of datasource 2, this get datasource 1. I think that this ThreadLocal<DatabaseEnvironment> CONTEXT =
new ThreadLocal<>(); was exclusive for request, But this does not seem to be so.
Iam sorry if this not be clear.
In realy, i need that DataSurceRouter were exclusive for each request, and an request not intefer in another.
I wrong about i think of DatasourceRouter or my code is bad ?
The issue probably occurs because of server thread pool: you have a given number of threads, and each request is served rolling among them.
When the server recycles a thread, the thread local variable has that value already set from the previous cycle, so you need to flush that value after each request, leaving the thread in a clean state.

Using PropertySourcesPlaceholderConfigurer to retrieve values from properties file as well as database

I am new to Spring Boot. I am trying to achieve a PropertySourcesPlaceholderConfigurer configuration that would return properties from a property file as well as database table. Here's what I wrote:
#Configuration
#PropertySource(value = { "classpath:application.properties" }, ignoreResourceNotFound = false)
public class SpringPropertiesConfig implements EnvironmentAware {
private static final Logger log = LoggerFactory.getLogger(SpringPropertiesConfig.class);
#Inject
private org.springframework.core.env.Environment env;
#PostConstruct
public void initializeDatabasePropertySourceUsage() {
MutablePropertySources propertySources = ((ConfigurableEnvironment) env).getPropertySources();
System.out.println("propertySources : " + propertySources);
try {
// dataSource, Table Name, Key Column, Value Column
DatabaseConfiguration databaseConfiguration = new DatabaseConfiguration(dataSource(),
"APPLICATION_CONFIGURATION", "KEY", "VALUE");
Properties dbProps = ConfigurationConverter.getProperties(databaseConfiguration);
PropertiesPropertySource dbPropertySource = new PropertiesPropertySource("dbPropertySource", dbProps);
propertySources.addFirst(dbPropertySource);
} catch (Exception e) {
log.error("Error during database properties setup", e);
throw new RuntimeException(e);
}
}
#Bean(name = "pspc")
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
pspc.setIgnoreUnresolvablePlaceholders(true);
// System.out.println("propertySourcesPlaceholderConfigurer = " +
// pspc.getAppliedPropertySources());
return pspc;
}
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getProperty("dev.datasource.driver-class-name"));
dataSource.setUrl(env.getProperty("dev.datasource.url"));
dataSource.setUsername(env.getProperty("dev.datasource.username"));
dataSource.setPassword(env.getProperty("dev.datasource.password"));
return dataSource;
}
#Override
public void setEnvironment(Environment paramEnvironment) {
this.env = paramEnvironment;
}
}
I found that properties from application.properties were getting resolved correctly.
#Value("${spnego.defaultRealm}")
private String defRealm;
Here, 'defRealm' contained the correct value. However properties from database were not getting resolved.
#Value("${enviromentName}")
private String envir;
If I print the value of envir, it prints '${enviromentName}'.
In the SpringPropertiesConfig class, the table is getting read correctly and the Properties object 'dbProps' prints all the rows in the APPLICATION_CONFIGURATION table.
Any ideas?
Thank you M. Deinum, I followed your suggestion and implemented the following :
public class SpringPropertiesConfig implements ApplicationContextInitializer<ConfigurableApplicationContext> {
public DataSource getDataSource(org.springframework.core.env.PropertySource<?> propSrc) {
String profile = (String) propSrc.getProperty("spring.profiles.active");
if (profile.equals("dev")) {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName((String) propSrc.getProperty("dev.datasource.driver-class-name"));
dataSource.setUrl((String) propSrc.getProperty("dev.datasource.url"));
dataSource.setUsername((String) propSrc.getProperty("dev.datasource.username"));
dataSource.setPassword((String) propSrc.getProperty("dev.datasource.password"));
return dataSource;
} else if (profile.equals("prod")) {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
DataSource dataSource = dataSourceLookup
.getDataSource((String) propSrc.getProperty("prd.datasource.jndi-name"));
return dataSource;
}
return null;
}
#Override
public void initialize(ConfigurableApplicationContext ctx) {
org.springframework.core.env.PropertySource<?> p = ctx.getEnvironment().getPropertySources()
.get("applicationConfigurationProperties");
DatabaseConfiguration databaseConfiguration = new DatabaseConfiguration(getDataSource(p),
"APPLICATION_CONFIGURATION", "KEY", "VALUE");
System.out.println("databaseConfiguration created : " + databaseConfiguration);
Properties dbProps = ConfigurationConverter.getProperties(databaseConfiguration);
System.out.println("dbProps=" + dbProps);
PropertiesPropertySource dbPropertySource = new PropertiesPropertySource("dbPropertySource", dbProps);
ctx.getEnvironment().getPropertySources().addFirst(dbPropertySource);
}
}
But what i am not sure is whether
org.springframework.core.env.PropertySource<?> p = ctx.getEnvironment().getPropertySources().get("applicationConfigurationProperties");
is the best way to read from application.properties file.
The above ApplicationContextInitializer is registered with Spring as follows -
#SpringBootApplication
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(final SpringApplicationBuilder application) {
return application.sources(Application.class);
}
public static void main(final String[] args) {
new SpringApplicationBuilder(Application.class).initializers(new SpringPropertiesConfig()).run(args);
}
#Bean
public RestTemplate restTemplate(final RestTemplateBuilder builder) {
return builder.build();
}
}

Tables not getting created in multiple databases in spring boot application

I am working on spring boot multi tenancy application. I have configured multi datasources as shown below :
application.properties
spring.multitenancy.datasource1.url=jdbc:mysql://localhost:3306/db1
spring.multitenancy.datasource1.username=root
spring.multitenancy.datasource1.password=****
spring.multitenancy.datasource1.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.multitenancy.datasource2.url=jdbc:mysql://localhost:3306/db2
spring.multitenancy.datasource2.username=root
spring.multitenancy.datasource2.password=****
spring.multitenancy.datasource2.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.multitenancy.datasource3.url=jdbc:mysql://localhost:3306/db3
spring.multitenancy.datasource3.username=root
spring.multitenancy.datasource3.password=****
spring.multitenancy.datasource3.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
DataSourceBasedMultiTenantConnectionProviderImpl.java
#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;
#Autowired
private DataSource dataSource3;
private Map<String, DataSource> map;
#PostConstruct
public void load() {
map = new HashMap<>();
map.put("tenant_1", dataSource1);
map.put("tenant_2", dataSource2);
map.put("tenant_3", dataSource3);
}
#Override
protected DataSource selectAnyDataSource() {
return map.get(DEFAULT_TENANT_ID);
}
#Override
protected DataSource selectDataSource(String tenantIdentifier) {
return map.get(tenantIdentifier);
}
}
MultitenancyProperties.java
#ConfigurationProperties("spring.multitenancy")
public class MultitenancyProperties {
#NestedConfigurationProperty
private DataSourceProperties datasource1;
#NestedConfigurationProperty
private DataSourceProperties datasource2;
#NestedConfigurationProperty
private DataSourceProperties datasource3;
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;
}
public DataSourceProperties getDatasource3() {
return datasource3;
}
public void setDatasource3(DataSourceProperties datasource3) {
this.datasource3 = datasource3;
}
}
MultiTenancyJpaConfiguration.java
#Configuration
#EnableConfigurationProperties(JpaProperties.class)
public class MultiTenancyJpaConfiguration {
#Autowired
private DataSource dataSource;
#Autowired
private JpaProperties jpaProperties;
#Autowired
private MultiTenantConnectionProvider multiTenantConnectionProvider;
#Autowired
private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
Map<String, Object> hibernateProps = new LinkedHashMap<>();
hibernateProps.putAll(jpaProperties.getHibernateProperties(dataSource));
hibernateProps.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
hibernateProps.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
hibernateProps.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
hibernateProps.put(Environment.DIALECT, "org.hibernate.dialect.MySQLDialect");
return builder.dataSource(dataSource).packages(HotelEntity.class.getPackage().getName()).properties(hibernateProps).jta(false).build();
}
}
Application launcher
#SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
#EnableConfigurationProperties(MultitenancyProperties.class)
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
}
When I run the boot application, all tables are created in only first data source.
1) How can I create tables in all data sources on application startup?
2) How to see connections opened/closed for each of the data sources?
3) Is there a better way of configuring multi tenancy application using spring boot for better performance?
As #Alex said, you need differnt EntityManagers, TransactionManager and Datasources. Here is how I would do it
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "dataSource1EntityManagerFactory",
transactionManagerRef = "dataSource1TransactionManager",
basePackageClasses = dataSource1Repository.class)
public class DataSource1Config extends SqlConfig{// Put all common code in base class SqlConfig. If not remove it
#Bean
#Primary
public DataSource dataSource1() {
//create dataSource using MultitenancyProperties::getDataSource1
}
#Primary
#Bean(name = "dataSource1TransactionManager")
PlatformTransactionManager dataSource1TransactionManager(EntityManagerFactory dataSource1EntityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(dataSource1EntityManagerFactory);
return txManager;
}
#Primary
#Bean(name = "dataSource1EntityManagerFactory")
LocalContainerEntityManagerFactoryBean dataSource1EntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource1());
em.setPackagesToScan(dataSource1Repository.class.getPackage().getName(), dataSource1ModelClass.class.getPackage().getName());
em.setPersistenceUnitName("dataSource1Db");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
}
You can create two other classes like this. Remember to use #Primary on only one instace of datasource, transactionmanger and entitymanager(doesn't matter which one). Another word of caution, make sure Repository classes are in different packages for all three databases.
Try adding the following properties:
spring.jpa.generate-ddl=true
You need 2 different persistence factories, not one, each should produce different EntityManagers for different datasources. Also each entity mapping should be marked to been used only with one entity manager.
See full solution here:
http://www.baeldung.com/spring-data-jpa-multiple-databases

How to set location of ehcache.xml in spring java based configuration?

I have this java based JPA configuration for my spring project:
#Configuration
#EnableJpaRepositories(basePackageClasses = {PackageMarker.class})
#EnableTransactionManagement(proxyTargetClass = true)
#EnableCaching
public class FooJPAConfig implements CachingConfigurer {
#Bean
#Override
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
return cacheManager;
}
#Bean
#Override
public KeyGenerator keyGenerator() {
return new DefaultKeyGenerator();
}
//...
}
How can I tell spring to use a specific ehcache.xml file?
You need to alter cacheManager in order to integrate EhCache. Your current code does not make EhCache enter the picture.
The configuration would look like
#Configuration
#EnableJpaRepositories(basePackageClasses = {PackageMarker.class})
#EnableTransactionManagement(proxyTargetClass = true)
#EnableCaching
public class FooJPAConfig implements CachingConfigurer {
#Bean
public EhCacheManagerFactoryBean cacheFactoryBean() {
EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("whatever-name.xml")); //this is where you set the location of the eh-cache configuration file
return ehCacheManagerFactoryBean;
}
#Bean
#Override
public CacheManager cacheManager() {
EhCacheCacheManager cacheManager = new EhCacheCacheManager();
cacheManager.setCacheManager(cacheFactoryBean().getObject());
return cacheManager;
}
#Bean
#Override
public KeyGenerator keyGenerator() {
return new DefaultKeyGenerator();
}
}
You will also have to have spring-context-support as a dependency on your classpath (applies for Spring 3.2)
Note that the code above activates Spring -EhCache integration, not JPA - EhCache integration. That means that you can use Spring's #Cacheable not EhCache's #Cache on entities.
After all I could solve the problem by adding this code to the configuration class:
protected static final String EHCACHE_CONFIGURATIONRESOURCENAME_PROPERTY = "net.sf.ehcache.configurationResourceName";
#Bean(name = BEAN_ENTITY_MANAGER_FACTORY)
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = createLocalContainerEntityManagerFactoryBean();
// ...
processOptionalProperty(EHCACHE_CONFIGURATIONRESOURCENAME_PROPERTY, em);
return em.getObject();
}
protected void processOptionalProperty(String propertyName, LocalContainerEntityManagerFactoryBean em) {
String value = "";// read propertyName from configuration file
setPropertyValue(propertyName, em, value);
}
protected void setPropertyValue(String propertyName, LocalContainerEntityManagerFactoryBean em, Object value) {
if (value != null) {
Map<String, Object> jpaPropertyMap = em.getJpaPropertyMap();
jpaPropertyMap.put(propertyName, value);
em.setJpaPropertyMap(jpaPropertyMap);
}
}

Categories

Resources