Adding a second datasource - SpringBoot RepositoryRestService PersistenceConfig - java

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

Related

How can I configure multiple(more than two) data sources in my spring boot application in MySQL and MariaDB,

I have to connect to multiple data sources at runtime in a spring boot application with amazon RDS deployed MySQL and MariaDB instances respectively. For now, both are MariaDB instances residing separately. I have used the usual spring boot configurations like here
Multiple data source and schema creation in Spring Boot
I am facing a problem here and that is I am not able to connect to the secondary data source where as I am able to connect to the primary data source.
Here are some code snippets.
Also, I want to know any mechanism to fall back to other database instances of the same schemas at runtime if the original ones are down without affecting the application or restarting it. I have no idea about that.
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
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 org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.HashMap;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "a.b.c.d.e",
entityManagerFactoryRef = "aEntityManagerFactory",
transactionManagerRef = "aTransactionManager"
)
public class aConfiguration {
#Bean
#Primary
#ConfigurationProperties("app.datasource.a")
public DataSourceProperties aDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("app.datasource.a.configuration")
public DataSource aDataSource() {
return aDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
#Bean
public EntityManagerFactoryBuilder entityManagerFactoryBuilder() {
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), new HashMap<>(), null);
}
#Primary
#Bean(name = "aEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean aEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(aDataSource())
.packages(SomeQualifiedEntity.class)
.build();
}
#Primary
#Bean
public PlatformTransactionManager aTransactionManager(
final #Qualifier("aEntityManagerFactory") LocalContainerEntityManagerFactoryBean aEntityManagerFactory) {
return new JpaTransactionManager(aEntityManagerFactory.getObject());
}
}
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
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 org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.HashMap;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "u.v.w.x.y",
entityManagerFactoryRef = "bEntityManagerFactory",
transactionManagerRef = "bTransactionManager"
)
public class aConfiguration {
#Bean
#Primary
#ConfigurationProperties("app.datasource.b")
public DataSourceProperties bDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("app.datasource.b.configuration")
public DataSource bDataSource() {
return bDataSourceProperties().initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
#Bean
public EntityManagerFactoryBuilder entityManagerFactoryBuilder() {
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), new HashMap<>(), null);
}
#Primary
#Bean(name = "bEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean bEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(bDataSource())
.packages(SomeQualifiedEntity.class)
.build();
}
#Primary
#Bean
public PlatformTransactionManager bTransactionManager(
final #Qualifier("bEntityManagerFactory") LocalContainerEntityManagerFactoryBean bEntityManagerFactory) {
return new JpaTransactionManager(aEntityManagerFactory.getObject());
}
}
app.datasource.a.url=
app.datasource.a.username=
app.datasource.a.password=
app.datasource.a.driverClassName=org.mariadb.jdbc.Driver
app.datasource.b.url=
app.datasource.b.username=
app.datasource.b.password=
app.datasource.b.driverClassName=org.mariadb.jdbc.Driver
I wish I could provide the entities and the repositories, but I can't. Sorry for that.
I configured my database / datasource in spring in the application.properties file. Although I just had one DB, I tried it with multiple out of curiosity and it worked. So maybe try it there? The format is as such:
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.data.jpa.repositories.bootstrap-mode=default

Relationship between two entities of different schemes - Spring Boot Data JPA

Is it possible to cross join between two different database tables (2 DataSources) in Spring Boot using JPA? If so, how would you configure this configuration? In the application.properties, 4 databases are already being defined and 4 files have been created that represent the configurations of the DataSources. The scenario is that there is a DataSource 1 table that has a FOREIGN KEY with a DataSource 2 table, but when the application is raised an Exception is generated:
#OneToOne or #ManyToOne on com.smart4.bdcentral.domain.files.IndividuoFoto .persons references an unknown entity: com.smart4.bdcentral.domain.main.IndividuoPessoa
DataSource 1
package com.smart4.configBases;
import java.util.HashMap;
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.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
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.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import com.google.common.base.Preconditions;
#Configuration
#PropertySource({ "classpath:application.properties" })
#EnableJpaRepositories(basePackages = {"com.smart4.bdcentral.repository.main"},
entityManagerFactoryRef = "bdCentralMainEntityManager",
transactionManagerRef = "bdCentralMainTransactionManager")
public class BdCentralMain {
#Autowired
private Environment env;
public BdCentralMain() {
super();
}
#Bean
public LocalContainerEntityManagerFactoryBean bdCentralMainEntityManager() {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(bdCentralMainDataSource());
em.setPackagesToScan(new String[] { "com.smart4.bdcentral.domain.main" });
em.setPersistenceUnitName("bdcentralmain");
final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.MYSQL);
vendorAdapter.setShowSql(false);
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
em.setJpaVendorAdapter(vendorAdapter);
final HashMap<String, Object> properties = new HashMap<String, Object>();
properties.put("hibernate.hbm2ddl.auto", env.getProperty("bdcentralmain.hibernate.hbm2ddl.auto"));
properties.put("hibernate.dialect", env.getProperty("bdcentralmain.hibernate.dialect"));
em.setJpaPropertyMap(properties);
return em;
}
#Bean
public DataSource bdCentralMainDataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource
.setDriverClassName(Preconditions.checkNotNull(env.getProperty("bdcentralmain.jdbc.driverClassName")));
dataSource.setUrl(Preconditions.checkNotNull(env.getProperty("bdcentralmain.jdbc.url")));
dataSource.setUsername(Preconditions.checkNotNull(env.getProperty("bdcentralmain.jdbc.user")));
dataSource.setPassword(Preconditions.checkNotNull(env.getProperty("bdcentralmain.jdbc.pass")));
return dataSource;
}
#Bean
public PlatformTransactionManager bdCentralMainTransactionManager() {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(bdCentralMainEntityManager().getObject());
return transactionManager;
}
}
DataSource 2
package com.smart4.configBases;
import java.util.HashMap;
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.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
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.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import com.google.common.base.Preconditions;
#Configuration
#PropertySource({ "classpath:application.properties" })
#EnableJpaRepositories(basePackages = {"com.smart4.bdcentral.repository.files"},
entityManagerFactoryRef = "bdCentralFilesEntityManager",
transactionManagerRef = "bdCentralFilesTransactionManager")
public class BdCentralFiles {
#Autowired
private Environment env;
public BdCentralFiles() {
super();
}
#Bean
public LocalContainerEntityManagerFactoryBean bdCentralFilesEntityManager() {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(bdCentralFilesDataSource());
em.setPackagesToScan(new String[] { "com.smart4.bdcentral.domain.files" });
em.setPersistenceUnitName("bdcentralfiles");
final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.MYSQL);
vendorAdapter.setShowSql(false);
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
em.setJpaVendorAdapter(vendorAdapter);
final HashMap<String, Object> properties = new HashMap<String, Object>();
properties.put("hibernate.hbm2ddl.auto", env.getProperty("bdcentralfiles.hibernate.hbm2ddl.auto"));
properties.put("hibernate.dialect", env.getProperty("bdcentralfiles.hibernate.dialect"));
em.setJpaPropertyMap(properties);
return em;
}
#Bean
public DataSource bdCentralFilesDataSource() {
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource
.setDriverClassName(Preconditions.checkNotNull(env.getProperty("bdcentralfiles.jdbc.driverClassName")));
dataSource.setUrl(Preconditions.checkNotNull(env.getProperty("bdcentralfiles.jdbc.url")));
dataSource.setUsername(Preconditions.checkNotNull(env.getProperty("bdcentralfiles.jdbc.user")));
dataSource.setPassword(Preconditions.checkNotNull(env.getProperty("bdcentralfiles.jdbc.pass")));
return dataSource;
}
#Bean
public PlatformTransactionManager bdCentralFilesTransactionManager() {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(bdCentralFilesEntityManager().getObject());
return transactionManager;
}
}
not much to say with just the datasources, check the annotation configuration and make sure your objects are #Entity annotated. It doesn't sound like you actually want a "cross join", also you're looking at Hibernate, I kept reading Spring Boot JPA as spring "Data" JPA and was getting confused as to why it wasn't data jpa lol.

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

Spring Boot Multiple Databse : No qualifying bean of type EntityManagerFactoryBuilder

We have two databases in our Spring Boot Application called source and target. Here is the configuration for those
Source Configuration
package com.alex.myapp.config;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "sourceManagerFactory",
transactionManagerRef = "sourceTransactionManager",
basePackages = {"com.alex.myapp.source.repository"}
)
public class SourceDbConfiguration {
#Autowired
private Environment env;
#Primary
#Bean(name = "sourceManagerFactory")
public LocalContainerEntityManagerFactoryBean
sourceManagerFactory(EntityManagerFactoryBuilder builder) {
LocalContainerEntityManagerFactoryBean em = builder
.dataSource(sourceDataSource())
.packages("com.alex.myapp.source.entity")
.persistenceUnit("source")
.build();
return em;
}
#Primary
#Bean
public DataSource sourceDataSource() {
DriverManagerDataSource dataSource
= new DriverManagerDataSource();
dataSource.setDriverClassName(
env.getProperty("spring.datasource.driver-class-name"));
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));
dataSource.setPassword(env.getProperty("spring.datasource.password"));
return dataSource;
}
#Primary
#Bean(name = "sourceTransactionManager")
public PlatformTransactionManager sourceTransactionManager(
#Qualifier("sourceManagerFactory") EntityManagerFactory
sourceManagerFactory
) {
return new JpaTransactionManager(sourceManagerFactory);
}
}
Target Configuration
package com.alex.myapp.config;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "targetManagerFactory",
transactionManagerRef = "targetTransactionManager",
basePackages = {"com.alex.myapp.target.repository"}
)
public class TargetDbConfiguration {
#Autowired
private Environment env;
#Primary
#Bean(name = "targetManagerFactory")
public LocalContainerEntityManagerFactoryBean
targetManagerFactory(EntityManagerFactoryBuilder builder) {
LocalContainerEntityManagerFactoryBean em = builder
.dataSource(targetDataSource())
.packages("com.alex.myapp.target.entity")
.persistenceUnit("target")
.build();
return em;
}
#Primary
#Bean
public DataSource targetDataSource() {
DriverManagerDataSource dataSource
= new DriverManagerDataSource();
dataSource.setDriverClassName(
env.getProperty("target.datasource.driver-class-name"));
dataSource.setUrl(env.getProperty("target.datasource.url"));
dataSource.setUsername(env.getProperty("target.datasource.username"));
dataSource.setPassword(env.getProperty("target.datasource.password"));
return dataSource;
}
#Bean(name = "targetTransactionManager")
public PlatformTransactionManager targetTransactionManager(
#Qualifier("targetManagerFactory") EntityManagerFactory
targetManagerFactory) {
return new JpaTransactionManager(targetManagerFactory);
}
}
When I try to start server it throws below mentioned error
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2018-09-19 13:30:53 - Application run failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'sourceManagerFactory' defined in class path resource [com/alex/myapp/config/SourceDbConfiguration.class]: Unsatisfied dependency expressed through method 'sourceManagerFactory' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:732)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:474)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1247)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1089)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:859)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
Jut in case if I comment out target configuration's class level annotation everything works fine. It seems both Database Configurations are conflicting with each other.
#Primary must be used exactly on one bean among the required types.
Extract from #Primary javadoc
Indicates that a bean should be given preference when multiple candidates are qualified to autowire a single-valued dependency. If exactly one 'primary' bean exists among the candidates, it will be the autowired value.
You have an error in your code. You specify
#Primary
Annotation for both of datasources, therefore Spring claims. So you need to remove this annotation from one of your class and all will be ok.
Also please note that Primary annotation is helpful whenever we’re going to implicitly or explicitly inject the transaction manager without specifying which one by name.

Spring Boot change DataSource and JPA properties at runtime

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.

Categories

Resources