I want to configure Spring Boot to use 2 JNDI datasources. I tried this configuration:
application.properties
spring.production-datasource.jndi-name=java:/global/production_gateway
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = update
spring.warehouse-datasource.jndi-name=java:/global/production_warehouse
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
spring.jpa.show-sql = true
spring.jpa.hibernate.ddl-auto = update
primary database
#Configuration
#EnableJpaRepositories(
basePackages = "org.datalis.plugin.production.entity",
entityManagerFactoryRef = "productionEntityManager",
transactionManagerRef = "productionTransactionManager"
)
#EnableTransactionManagement
public class ContextProductionDatasource {
#Autowired
private Environment env;
#Primary
#Bean
#ConfigurationProperties(prefix="spring.production-datasource")
public DataSource productionDataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean
public EntityManager productionEntityManager(EntityManagerFactory emf) {
return emf.createEntityManager();
}
#Primary
#Bean
public PlatformTransactionManager productionTransactionManager(final EntityManagerFactory emf) {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Primary
#Bean
public PersistenceExceptionTranslationPostProcessor productionExceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
second datasource:
#Configuration
#EnableJpaRepositories(
basePackages = "org.datalis.plugin.warehouse.entity",
entityManagerFactoryRef = "warehouseEntityManager",
transactionManagerRef = "warehouseTransactionManager"
)
#EnableTransactionManagement
public class ContextWarehouseDatasource {
#Autowired
private Environment env;
#Primary
#Bean
#ConfigurationProperties(prefix="spring.warehouse-datasource")
public DataSource warehouseDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public EntityManager warehouseEntityManager(EntityManagerFactory emf) {
return emf.createEntityManager();
}
#Bean
public PlatformTransactionManager warehouseTransactionManager(final EntityManagerFactory emf) {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor warehouseExceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
When I deploy the code I get exception:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.sql.DataSource' available: more than one 'primary' bean found among candidates: [productio nDataSource, warehouseDataSource]"}}
Full error stack:
https://pastebin.com/EsNp2Fp9
Do you know how I can solve this issue?
You can have only 1 primary Datasource. Other confuguration looks fine.
Assuming ContextWarehouseDatasource as secondary connection
Remove #Primary from warehouseDataSource() like this and try.
#Bean
#ConfigurationProperties(prefix="spring.warehouse-datasource")
public DataSource warehouseDataSource() {
return DataSourceBuilder.create().build();
}
I want to configure two datasource for my spring boot project, but I have same entities in the two databases, so I ask if it is possible to make the entities in one package and initialise the specific datasource in the repositories.
This my configuration :
#Configuration
#EnableAutoConfiguration
public class NwlConfiguration {
#Bean
#Primary
#ConfigurationProperties("spring.datasource")
public DataSourceProperties source1DataSourceProperties() {
return new DataSourceProperties();
}
#Bean(name = "source1")
#Primary
#ConfigurationProperties("spring.datasource")
public DataSource source1DataSource() {
return source1DataSourceProperties().initializeDataSourceBuilder().build();
}
#Bean
#ConfigurationProperties("source2.datasource")
public DataSourceProperties source2DataSourceProperties() {
return new DataSourceProperties();
}
#Bean(name = "source2")
#ConfigurationProperties("source2.datasource")
public DataSource source2DataSource() {
return source2DataSourceProperties().initializeDataSourceBuilder().build();
}
}
Your Configuration looks ok. Only thing is you need to assign which Configuration will be used for which Repository.
If there are multiple entities you can put them in a common package.
Like this
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "barEntityManagerFactory",
transactionManagerRef = "barTransactionManager", basePackages = {"com.foobar.bar.repo"})
public class BarDbConfig {
Complete Code
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "barEntityManagerFactory",
transactionManagerRef = "barTransactionManager", basePackages = {"com.foobar.bar.repo"})
public class BarDbConfig {
#Bean(name = "barDataSource")
#ConfigurationProperties(prefix = "bar.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "barEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean barEntityManagerFactory(
EntityManagerFactoryBuilder builder, #Qualifier("barDataSource") DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.foobar.bar.domain").persistenceUnit("bar")
.build();
}
#Bean(name = "barTransactionManager")
public PlatformTransactionManager barTransactionManager(
#Qualifier("barEntityManagerFactory") EntityManagerFactory barEntityManagerFactory) {
return new JpaTransactionManager(barEntityManagerFactory);
}
}
2nd Datasource Config Class
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory",
basePackages = {"com.foobar.foo.repo"})
public class FooDbConfig {
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder, #Qualifier("dataSource") DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.foobar.foo.domain").persistenceUnit("foo")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Refer this article for more Detail
I have a class with config. And I have a method entityManagerFactory() that tagged like Bean.
#Configuration
#EnableTransactionManagement
#PropertySource({ "classpath:hibernate.properties" })
#EnableJpaRepositories
public class PersistenceJPAConfig {
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "java.entities" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/carpark?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTCx`");
dataSource.setUsername( "root" );
dataSource.setPassword( "1111" );
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
public Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "update");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return properties;
}
}
And I have a service that use EntityManager. EntityManager tagged like #Autowired, and spring create EntityManager is null. Where I'm configured incorrectly. I need to entityManager with fields from method entityManagerFactory() (in class PersistenceJPAConfig).
#Service
public class BusService {
#Autowired
private BusRepository busRepository;
#Autowired
private EntityManager entityManager;
public void getBus(){
try{
entityManager.getTransaction().begin();
Query query = entityManager.createNativeQuery("SELECT ID, NUMBER , Rote_ID FROM bus", Bus.class);
busRepository.save(query.getResultList());
System.out.println(busRepository.toString());
}finally {
entityManager.getTransaction().commit();
}
}
}
my packages are located like this
thanks in advance
You should use the `#PersistenceContext' annotation to inject the entity manager:
#Service
public class BusService {
#Autowired
private BusRepository busRepository;
#PersistenceContext
private EntityManager entityManager;
public void getBus(){
try{
entityManager.getTransaction().begin();
Query query = entityManager.createNativeQuery("SELECT ID, NUMBER , Rote_ID FROM bus", Bus.class);
busRepository.save(query.getResultList());
System.out.println(busRepository.toString());
} finally {
entityManager.getTransaction().commit();
}
}
there are several reasons to do that, you can get an overview here
and update you configuration class with the #ComponentScan annotation
#Configuration
#EnableTransactionManagement
#PropertySource({ "classpath:hibernate.properties" })
#EnableJpaRepositories
#ComponentScan(basePackages = {
"model.service"})
public class PersistenceJPAConfig {
....
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() { LocalContainerEntityManagerFactoryBean em = new
LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "model.entities" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
....
I have to connect to two databases (PostgreSQL , Oracle) which contains same tables.
When I create entities of same tables in different packages it doesn't work.
Even though using two database connections the application always points to one database connection only.
Is it possible in Hibernate to connect to same tables from different databases?
application.properties
#DataSource settings for Postgres
datasource.secondary.url =jdbc:postgresql://localhost:5433/****
datasource.secondary.username =postgres
datasource.secondary.password =Postgre#1234
datasource.secondary.driverClassName=org.postgresql.Driver
datasource.secondary.dialect=org.hibernate.dialect.PostgreSQLDialect
#DataSource settings for oracle
datasource.primary.url = jdbc:oracle:thin:#localhost:1521:xe
datasource.primary.username = ***
datasource.primary.password = ***
datasource.primary.driverClassName=oracle.jdbc.OracleDriver
Configuration
#Configuration
public class MultipleDBConfig {
#Primary
#Bean(name = "oracleDb")
#ConfigurationProperties(prefix = "datasource.primary")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "postgresDb")
#ConfigurationProperties(prefix = "datasource.secondary")
public DataSource postgresDataSource() {
return DataSourceBuilder.create().build();
}
}
Primary
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "primaryEntityManager",
transactionManagerRef = "primaryEntityManagerFactory",
basePackages = {"com.ubl.model.*"})
public class PrimaryDBConfig {
#Bean(name = "primaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] {"com.ubl.model.migration.entity.oracle"});
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalJpaProperties());
em.setPersistenceUnitName("customers");
return em;
}
Properties additionalJpaProperties(){
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "update");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.OracleDialect");
properties.setProperty("hibernate.show_sql", "true");
return properties;
}
#Bean
public DataSource dataSource(){
return DataSourceBuilder.create()
.url("jdbc:oracle:thin:#localhost:1521:xe")
.driverClassName("oracle.jdbc.OracleDriver")
.username("****")
.password("****")
.build();
}
#Bean(name = "primarytransactionManager")
public JpaTransactionManager transactionManager(EntityManagerFactory customerEntityManager){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(customerEntityManager);
return transactionManager;
}
}
Secondary
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "secondaryEntityManagerFactory",
transactionManagerRef = "secondaryTransactionManager",
basePackages = {"com.ubl.*"})
public class SecondaryDBConfig {
#Autowired
JpaVendorAdapter jpaVendorAdapter;
#Value("${datasource.secondary.url}")
private String databaseURL;
#Value("${datasource.secondary.username}")
private String username;
#Value("${datasource.secondary.password}")
private String password;
#Value("${datasource.secondary.driverClassName}")
private String driverClassName;
#Value("${datasource.secondary.dialect}")
private String dialect;
public SecondaryDBConfig() {
System.out.println("Secondary repository");
System.out.println("driverClassName: *************" +driverClassName);
}
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource(databaseURL, username, password);
dataSource.setDriverClassName(driverClassName);
return dataSource;
}
#Bean(name = "secondaryEntityManager")
public EntityManager entityManager() {
return entityManagerFactory().createEntityManager();
}
#Bean(name = "secondaryEntityManagerFactory")
public EntityManagerFactory entityManagerFactory() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", dialect);
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource());
emf.setJpaVendorAdapter(jpaVendorAdapter);
emf.setPackagesToScan("com.ubl.model.*"); // package for entities
emf.setPersistenceUnitName("secondaryPersistenceUnit");
emf.setJpaProperties(properties);
emf.afterPropertiesSet();
return emf.getObject();
}
#Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager(entityManagerFactory());
}
}
When I run the application I get below error:
Caused by: org.hibernate.tool.schema.extract.spi.SchemaExtractionException: More than one table found in namespace (, )
Your second config appears to use the same namespace as your first:
basePackages = {"com.ubl.model.*"}
basePackages = {"com.ubl.*"}
Once your second config looks for it's entities, it discovers the same as the fist does and thus resulting in the exception. You will want to separate your entities and both your configs.
basePackages = {"com.ubl.model.datasource1"}
basePackages = {"com.ubl.model.datasource2"} // well you get the idea and will find better names ;)
Then move all your entities in the respective folder. Although the table are "the same" you need one #Entity-Class for each individual Table, even though the tables you will want to use are structurally identical.
I'm trying to connect each #Repositories to different DataSource(s) with Spring Boot and Spring Data JPA. I used the following, http://xantorohara.blogspot.com/2013/11/spring-boot-jdbc-with-multiple.html, as a referrence. Here is the code I am using in an attempt to implement a similar solution using Spring Data JPA.
CustomerDbConfig.java(First data source connection)
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "orderEntityManager",
transactionManagerRef = "orderTransactionManager",
basePackages = {"com.mm.repository.customer"})
public class CustomerDbConfig {
#Bean(name = "customerEntityManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] {"com.mm.domain.customer"});
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalJpaProperties());
em.setPersistenceUnitName("customerPersistence");
em.setPackagesToScan("com.mm.domain.customer");
return em;
}
Properties additionalJpaProperties(){
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.setProperty("hibernate.show_sql", "true");
return properties;
}
#Bean
public DataSource dataSource(){
return DataSourceBuilder.create()
.url("jdbc:h2:mem:customer:H2")
.driverClassName("org.h2.Driver")
.username("sa")
.password("")
.build();
}
#Bean(name = "customerTransactionManager")
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
}
CustomerDbConfig.java (Second data source)
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "orderEntityManager",
transactionManagerRef = "orderTransactionManager",
basePackages = {"com.mm.repository.customer"})
public class CustomerDbConfig {
#Bean(name = "customerEntityManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] {"com.mm.domain.customer"});
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalJpaProperties());
em.setPersistenceUnitName("customerPersistence");
em.setPackagesToScan("com.mm.domain.customer");
return em;
}
Properties additionalJpaProperties(){
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.setProperty("hibernate.show_sql", "true");
return properties;
}
#Bean
public DataSource dataSource(){
return DataSourceBuilder.create()
.url("jdbc:h2:mem:customer:H2")
.driverClassName("org.h2.Driver")
.username("sa")
.password("")
.build();
}
#Bean(name = "customerTransactionManager")
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
}
Customer.java (model)
#Entity
#Table(name = "customer")
#Data
#EqualsAndHashCode(exclude = {"id"})
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "age", nullable = false)
private Integer age;
....
Order.java (model)
#Entity
#Table(name = "order")
#Data
#EqualsAndHashCode(exclude = {"id"})
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "code", nullable = false)
private Integer code;
#Column(name = "quality", nullable = false)
private Integer quality;
...
CustomerRepository.java
public interface CustomerRepository extends JpaRepository<Customer, Integer>{
}
OrderRepository.java
public interface OrderRepository extends JpaRepository<Order, Integer> {
}
Finally, Application.java
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application extends SpringApplication{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public ServletRegistrationBean h2Console() {
ServletRegistrationBean reg = new ServletRegistrationBean(new WebServlet(), "/console/*");
reg.setLoadOnStartup(1);
return reg;
}
}
During start the following exceptions are thrown:
-10-10 15:45:24.757 ERROR 1549 --- [ main] o.s.boot.SpringApplication : Application startup failed
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerTransactionManager' defined in class path resource [com/mm/boot/multidb/CustomerConfig.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [javax.persistence.EntityManagerFactory]: : No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: customerEntityManager,orderEntityManager; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: customerEntityManager,orderEntityManager
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:747)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:462)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1095)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:990)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:706)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:762)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:109)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:691)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:952)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:941)
at com.mm.boot.multidb.Application.main(Application.java:17)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: customerEntityManager,orderEntityManager
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:974)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:862)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:811)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:739)
... 18 common frames omitted
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'customerTransactionManager' defined in class path resource [com/mm/boot/multidb/CustomerConfig.class]: Unsatisfied dependency expressed through constructor argument with index 0 of type [javax.persistence.EntityManagerFactory]: : No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: customerEntityManager,orderEntityManager; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: customerEntityManager,orderEntityManager
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:747)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:462)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1095)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:990)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:475)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:298)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:706)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:762)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:482)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:109)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:691)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:952)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:941)
at com.mm.boot.multidb.Application.main(Application.java:17)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: customerEntityManager,orderEntityManager
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:974)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:862)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:811)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:739)
... 18 more
Full code for the sample can be found on GitHub (https://github.com/tonym2105/samples/tree/master/boot-multidb-sample)
Thank you in advance for the help.
There is another way to have multiple dataSources by using #EnableAutoConfiguration and application.properties.
Basically put multiple dataSource configuration info on application.properties and generate default setup (dataSource and entityManagerFactory) automatically for first dataSource by #EnableAutoConfiguration. But for next dataSource, create dataSource, entityManagerFactory and transactionManager all manually by the info from property file.
Below is my example to setup two dataSources.
First dataSource is setup by #EnableAutoConfiguration which can be assigned only for one configuration, not multiple. And that will generate 'transactionManager' by DataSourceTransactionManager, that looks default transactionManager generated by the annotation. However I have seen the transaction not beginning issue on the thread from scheduled thread pool only for the default DataSourceTransactionManager and also when there are multiple transaction managers.
So I create transactionManager manually by JpaTransactionManager also for the first dataSource with assigning 'transactionManager' bean name and default entityManagerFactory. That JpaTransactionManager for first dataSource surely resolves the weird transaction issue on the thread from ScheduledThreadPool.
Update for Spring Boot 1.3.0.RELEASE
I found my previous configuration with #EnableAutoConfiguration for default dataSource has issue on finding entityManagerFactory with Spring Boot 1.3 version. Maybe default entityManagerFactory is not generated by #EnableAutoConfiguration, once after I introduce my own transactionManager.
So now I create entityManagerFactory by myself. So I don't need to use #EntityScan. So it looks I'm getting more and more out of the setup by #EnableAutoConfiguration.
Second dataSource is setup without #EnableAutoConfiguration and create 'anotherTransactionManager' by manual way.
Since there are multiple transactionManager extends from PlatformTransactionManager, we should specify which transactionManager to use on each #Transactional annotation
Default Repository Config
#Configuration
#EnableTransactionManagement
#EnableAutoConfiguration
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager",
basePackages = {"com.mysource.repository"})
public class RepositoryConfig {
#Autowired
JpaVendorAdapter jpaVendorAdapter;
#Autowired
DataSource dataSource;
#Bean(name = "entityManager")
public EntityManager entityManager() {
return entityManagerFactory().createEntityManager();
}
#Primary
#Bean(name = "entityManagerFactory")
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource);
emf.setJpaVendorAdapter(jpaVendorAdapter);
emf.setPackagesToScan("com.mysource.model");
emf.setPersistenceUnitName("default"); // <- giving 'default' as name
emf.afterPropertiesSet();
return emf.getObject();
}
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(entityManagerFactory());
return tm;
}
}
Another Repository Config
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "anotherEntityManagerFactory",
transactionManagerRef = "anotherTransactionManager",
basePackages = {"com.mysource.anothersource.repository"})
public class AnotherRepositoryConfig {
#Autowired
JpaVendorAdapter jpaVendorAdapter;
#Value("${another.datasource.url}")
private String databaseUrl;
#Value("${another.datasource.username}")
private String username;
#Value("${another.datasource.password}")
private String password;
#Value("${another.dataource.driverClassName}")
private String driverClassName;
#Value("${another.datasource.hibernate.dialect}")
private String dialect;
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource(databaseUrl, username, password);
dataSource.setDriverClassName(driverClassName);
return dataSource;
}
#Bean(name = "anotherEntityManager")
public EntityManager entityManager() {
return entityManagerFactory().createEntityManager();
}
#Bean(name = "anotherEntityManagerFactory")
public EntityManagerFactory entityManagerFactory() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", dialect);
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(dataSource());
emf.setJpaVendorAdapter(jpaVendorAdapter);
emf.setPackagesToScan("com.mysource.anothersource.model"); // <- package for entities
emf.setPersistenceUnitName("anotherPersistenceUnit");
emf.setJpaProperties(properties);
emf.afterPropertiesSet();
return emf.getObject();
}
#Bean(name = "anotherTransactionManager")
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager(entityManagerFactory());
}
}
application.properties
# database configuration
spring.datasource.url=jdbc:h2:file:~/main-source;AUTO_SERVER=TRUE
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.continueOnError=true
spring.datasource.initialize=false
# another database configuration
another.datasource.url=jdbc:sqlserver://localhost:1433;DatabaseName=another;
another.datasource.username=username
another.datasource.password=
another.datasource.hibernate.dialect=org.hibernate.dialect.SQLServer2008Dialect
another.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
Choose proper transactionManager for #Transactional annotation
Service for first datasource
#Service("mainService")
#Transactional("transactionManager")
public class DefaultDataSourceServiceImpl implements DefaultDataSourceService
{
//
}
Service for another datasource
#Service("anotherService")
#Transactional("anotherTransactionManager")
public class AnotherDataSourceServiceImpl implements AnotherDataSourceService
{
//
}
here is my solution. base on spring-boot.1.2.5.RELEASE.
application.properties
first.datasource.driver-class-name=com.mysql.jdbc.Driver
first.datasource.url=jdbc:mysql://127.0.0.1:3306/test
first.datasource.username=
first.datasource.password=
first.datasource.validation-query=select 1
second.datasource.driver-class-name=com.mysql.jdbc.Driver
second.datasource.url=jdbc:mysql://127.0.0.1:3306/test2
second.datasource.username=
second.datasource.password=
second.datasource.validation-query=select 1
DataSourceConfig.java
#Configuration
public class DataSourceConfig {
#Bean
#Primary
#ConfigurationProperties(prefix="first.datasource")
public DataSource firstDataSource() {
DataSource ds = DataSourceBuilder.create().build();
return ds;
}
#Bean
#ConfigurationProperties(prefix="second.datasource")
public DataSource secondDataSource() {
DataSource ds = DataSourceBuilder.create().build();
return ds;
}
}
I checked the source code you provided on GitHub. There were several mistakes / typos in the configuration.
In CustomerDbConfig / OrderDbConfig you should refer to customerEntityManager and packages should point at existing packages:
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "customerEntityManager",
transactionManagerRef = "customerTransactionManager",
basePackages = {"com.mm.boot.multidb.repository.customer"})
public class CustomerDbConfig {
The packages to scan in customerEntityManager and orderEntityManager were both not pointing at proper package:
em.setPackagesToScan("com.mm.boot.multidb.model.customer");
Also the injection of proper EntityManagerFactory did not work. It should be:
#Bean(name = "customerTransactionManager")
public PlatformTransactionManager transactionManager(EntityManagerFactory customerEntityManager){
}
The above was causing the issue and the exception. While providing the name in a #Bean method you are sure you get proper EMF injected.
The last thing I have done was to disable to automatic configuration of JpaRepositories:
#EnableAutoConfiguration(exclude = JpaRepositoriesAutoConfiguration.class)
And with all fixes the application starts as you probably expect!
thanks to the answers of Steve Park and Rafal Borowiec I got my code working, however, I had one issue: the DriverManagerDataSource is a "simple" implementation and does NOT give you a ConnectionPool (check http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jdbc/datasource/DriverManagerDataSource.html).
Hence, I replaced the functions which returns the DataSource for the secondDB to.
public DataSource <secondaryDB>DataSource() {
// use DataSourceBuilder and NOT DriverManagerDataSource
// as this would NOT give you ConnectionPool
DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.url(databaseUrl);
dataSourceBuilder.username(username);
dataSourceBuilder.password(password);
dataSourceBuilder.driverClassName(driverClassName);
return dataSourceBuilder.build();
}
Also, if do you not need the EntityManager as such, you can remove both the entityManager() and the #Bean annotation.
Plus, you may want to remove the basePackages annotation of your configuration class: maintaining it with the factoryBean.setPackagesToScan() call is sufficient.
I have written a complete article at Spring Boot JPA Multiple Data Sources Example. In this article, we will learn how to configure multiple data sources and connect to multiple databases in a typical Spring Boot web application. We will use Spring Boot 2.0.5, JPA, Hibernate 5, Thymeleaf and H2 database to build a simple Spring Boot multiple data sources web application.
don't know why, but it works. Two configuration are the same, just change xxx to your name.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "xxxEntityManager",
transactionManagerRef = "xxxTransactionManager",
basePackages = {"aaa.xxx"})
public class RepositoryConfig {
#Autowired
private Environment env;
#Bean
#Primary
#ConfigurationProperties(prefix="datasource.xxx")
public DataSource xxxDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean xxxEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(xxxDataSource());
em.setPackagesToScan(new String[] {"aaa.xxx"});
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<String, Object>();
properties.put("hibernate.show_sql", env.getProperty("hibernate.show_sql"));
properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
properties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
em.setJpaPropertyMap(properties);
return em;
}
#Bean(name = "xxxTransactionManager")
public PlatformTransactionManager xxxTransactionManager() {
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(xxxEntityManager().getObject());
return tm;
}
}