Despite some fairly clear examples, I'm failing to get a manual configuration of a spring boot application to work. I usually allow it to configure right from the application.properties file but the requirement demands that I use multiple data sources. And, yes, I have tried to recreate the Baeldung example (https://www.baeldung.com/spring-data-jpa-multiple-databases) as recommended by the posts I've seen here. Here is my config:
#Configuration
#EnableJpaRepositories(basePackages = "com.ezcorp.costumerrefresh.db.sqlserver", entityManagerFactoryRef = "sqlserverEntityManagerFactory", transactionManagerRef = "sqlserverTransactionManager")
#EnableTransactionManagement
public class SqlServerConfig {
#Bean(name="sqlserverDataSource")
#ConfigurationProperties(prefix = "spring.datasource")
#Primary
public DataSource sqlserverDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
dataSource.setUrl("jdbc:sqlserver://aswn-tbd-db01:1433;databaseName=DE_RTDS");
dataSource.setUsername("cs_user");
dataSource.setPassword("cs_user");
return dataSource;
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean sqlserverEntityManagerFactory(final EntityManagerFactoryBuilder builder) {
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.dialect", "org.hibernate.dialect.SQLServer2008Dialect");
return builder
.dataSource(sqlserverDataSource())
.properties(properties)
.packages("com.ezcorp.costumerrefresh.domain.sqlserver")
.persistenceUnit("adminPersistenceUnit")
.build();
}
#Bean
#Primary
public JpaTransactionManager sqlserverTransactionManager(#Qualifier("sqlserverEntityManagerFactory") final EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}
}
Service:
#Service
public class CustomerRefreshService {
#Autowired
private ExistingLoanModelDataRepository existingLoanModelDataRepository;
public void execute() {
System.out.println("show me");
}
}
Repository:
#Repository
public interface ExistingLoanModelDataRepository extends CrudRepository<ExistingLoanModelData, Long> {
ExistingLoanModelData findByCustomerId(Long customerId);
}
error:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.ezcorp.customerrefresh.db.sqlserver.ExistingLoanModelDataRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {#org.springframework.beans.factory.annotation.Autowired(required=true)}
...
What am I doing wrong? This is about as straight-forward as it gets. I've eliminated the other (mongo) config setup to simplify. This has to be an obvious one...just not to me :-)
Looks like a typo.
#EnableJpaRepositories(basePackages = "com.ezcorp.costumerrefresh.db.sqlserver", entityManagerFactoryRef = "sqlserverEntityManagerFactory", transactionManagerRef = "sqlserverTransactionManager")
Related
I have an application in Spring Boot 1.4 which I'm trying to add additional datasources to.
First I setup a primary datasource and ran the application to check it still worked, and it did. Then I went ahead and added a second datasource, but when I did that I got the following error;
Description:
Field userRepo in com.nationallocums.config.CustomUserDetailsService required a single bean, but 2 were found:
- nlDoctorsEntityManager: defined by method 'nlDoctorsEntityManager' in class path resource [com/nationallocums/config/NLDoctorsDataSourceConfiguration.class]
- primaryEntityManager: defined by method 'primaryEntityManager' in class path resource [com/nationallocums/config/PrimaryDataSourceConfiguration.class]
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
I don't understand why I'm seeing this error, as I've clearly marked one of the datasources with #Primary, but it seems Spring Boot isn't picking that up.
Here's my two datasource configurations;
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "primaryEntityManager",
transactionManagerRef = "primaryTransactionManager",
basePackages = { "com.nationallocums.repository" })
#EnableTransactionManagement
public class PrimaryDataSourceConfiguration {
#Bean(name = "primaryDataSource")
#ConfigurationProperties(prefix = "spring.datasource")
#Primary
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "primaryEntityManager")
#Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManager(final EntityManagerFactoryBuilder builder, #Qualifier("primaryDataSource") final DataSource dataSource) {
final Map<String, String> properties = new HashMap<>();
return builder
.dataSource(dataSource)
.properties(properties)
.packages("com.nationallocums.model")
.persistenceUnit("primary")
.build();
}
#Bean(name = "primaryTransactionManager")
#Primary
public PlatformTransactionManager nlDoctorsTransactionManager(#Qualifier("primaryEntityManager") final EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
and...
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "nlDoctorsEntityManager",
transactionManagerRef = "nlDoctorsTransactionManager",
basePackages = { "com.nationallocums.eclipse.nldoctorsrepository" })
#EnableTransactionManagement
public class NLDoctorsDataSourceConfiguration {
#Bean(name = "nlDoctorsDataSource")
#ConfigurationProperties(prefix = "spring.nldoctors-datasource")
public DataSource nlDoctorsDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "nlDoctorsEntityManager")
public LocalContainerEntityManagerFactoryBean nlDoctorsEntityManager(final EntityManagerFactoryBuilder builder, #Qualifier("nlDoctorsDataSource") final DataSource dataSource) {
final Map<String, String> properties = new HashMap<>();
return builder
.dataSource(dataSource)
.properties(properties)
.packages("com.nationallocums.eclipse.model")
.persistenceUnit("nlDoctors")
.build();
}
#Bean(name = "nlDoctorsTransactionManager")
public PlatformTransactionManager nlDoctorsTransactionManager(#Qualifier("nlDoctorsEntityManager") final EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Can anyone spot what I've done wrong?
I managed to fix this by changing my repository from...
#PersistenceContext
private EntityManager entityManager;
to...
#Autowired
#Qualifier("primaryDataSource")
private EntityManager entityManager;
I have multiple database connections with named EntityManagers:
#Bean(name = "integDB")
#Primary
#ConfigurationProperties(prefix="spring.datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "integEM")
public LocalContainerEntityManagerFactoryBean integDbEntityManagerFactory(EntityManagerFactoryBuilder builder) {
....
}
How do I specify which entity manager to use here?
#Repository
public interface IntControleFilaRepository extends JpaRepository<IntControleFilaEntity, String> {
}
Spring is complaining about it:
Parameter 0 of constructor in ... required a bean named 'entityManagerFactory' that could not be found.
Create a configuration class for each database and specify the entityManager associated with each one:
#Configuration
#EnableJpaRepositories(basePackages = {"..."}, entityManagerFactoryRef = "integEM", transactionManagerRef = "integTM")
public class IntegReposConfig {
}
I was trying this code from github:https://github.com/kodinor/spring-data-many-dbs
It's an example of how to use multiple db's from a Spring-boot application. it worked fine but I added the Spring-boot-starter-web dependency and now I'm getting an error:
>Method requestMappingHandlerMapping in >org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$En>ableWebMvcConfiguration required a single bean, but 2 were found:
> - productDSEmFactory: defined by method 'productDSEmFactory' in class >path resource [com/kodinor/configuration/ProductDBConfiguration.class]
> - userDSEmFactory: defined by method 'userDSEmFactory' in class path >resource [com/kodinor/configuration/UserDBConfiguration.class]
>
>
>Action:
>
>Consider marking one of the beans as #Primary, updating the consumer to >accept multiple beans, or using #Qualifier to identify the bean that should >be consumed<br>
I have two config files:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = UserRepository.class, entityManagerFactoryRef = "userDSEmFactory", transactionManagerRef = "userDSTransactionManager")
public class UserDBConfiguration {
#Primary
#Bean
#ConfigurationProperties("spring.datasource1")
public DataSourceProperties userDSProperties() {
return new DataSourceProperties();
}
#Primary
#Bean
public DataSource userDS(#Qualifier("userDSProperties") DataSourceProperties userDSProperties) {
return userDSProperties.initializeDataSourceBuilder().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean userDSEmFactory(#Qualifier("userDS") DataSource userDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(userDS).packages(User.class).build();
}
#Primary
#Bean
public PlatformTransactionManager userDSTransactionManager(EntityManagerFactory userDSEmFactory) {
return new JpaTransactionManager(userDSEmFactory);
}
}
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = ProductRepository.class, entityManagerFactoryRef = "productDSEmFactory", transactionManagerRef = "productDSTransactionManager")
public class ProductDBConfiguration {
#Bean
#ConfigurationProperties("spring.datasource2")
public DataSourceProperties productDSProperties() {
return new DataSourceProperties();
}
#Bean
public DataSource productDS(#Qualifier("productDSProperties") DataSourceProperties productDSProperties) {
return productDSProperties.initializeDataSourceBuilder().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean productDSEmFactory(#Qualifier("productDS") DataSource productDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(productDS).packages(Product.class).build();
}
#Bean
public PlatformTransactionManager productDSTransactionManager(EntityManagerFactory productDSEmFactory) {
return new JpaTransactionManager(productDSEmFactory);
}
}
Application.properties:
spring.jpa.hibernate.ddl-auto=create
spring.jpa.generate-ddl=true
spring.datasource1.url=jdbc:mysql://localhost:3306/userdb?autoReconnect=true&useSSL=false
spring.datasource1.username=user
spring.datasource1.password=pass
spring.datasource2.url=jdbc:mysql://localhost:3306/productdb?autoReconnect=true&useSSL=false
spring.datasource2.username=user
spring.datasource2.password=pass
And a simple main app that adds some init data:
#SpringBootApplication
public class SpringDataManyDbsApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDataManyDbsApplication.class, args);
}
#Autowired
private UserRepository userRepository;
#Autowired
private ProductRepository productRepository;
#PostConstruct
void init () {
User user = new User("john", "doe");
userRepository.save(user);
Product product = new Product("chair", 5);
productRepository.save(product);
}
}
I tried to add the #Primary annotation to userDSEmFactory. The error goes away but the product data isn't saved anymore. Any ideas how to save this problem? I don't have a lot of experience with Spring-boot and I've read dozens of articles but many seem to do things in a different way. Thanks so much for helping me out!
update
I've added the #Primary annotation like this:
#Primary
#Bean
public LocalContainerEntityManagerFactoryBean userDSEmFactory(#Qualifier("userDS") DataSource userDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(userDS).packages(User.class).build();
}
No there are no more errors, but only the user is being saved in the primary db and nothing is being saved to the product (second) database.
So if anybody has suggestions what could be the cause of that..
You are missing #Primary annotation on your userDSEmFactory
#Primary
#Bean
public LocalContainerEntityManagerFactoryBean userDSEmFactory(#Qualifier("userDS") DataSource userDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(userDS).packages(User.class).build();
}
And also it is goot to use once #EnableTransactionManagement at your main class.
I have switched my application to use two data sources, the code runs fine and both are picked up, however my unit tests have started to fail, code is below, help much appreciated.
Caused by: org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Cannot determine embedded database driver class for database type NONE. If you want an embedded database please put a supported one on the classpath. If you have database settings to be loaded from a particular profile you may need to active it (no profiles are currently active).
at org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.getDriverClassName(DataSourceProperties.java:180)
at org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration$NonEmbeddedConfiguration.dataSource(DataSourceAutoConfiguration.java:120)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:162)
... 71 more
application.properties (in main and in test folders):
datasource.primary.url = <url>
datasource.primary.username = <user>
datasource.primary.password = <password>
datasource.secondary.url = <url>
datasource.secondary.username = <user>
datasource.secondary.password = <pass>
Main program:
#EnableAutoConfiguration
#Configuration
#EntityScan({"com.example.domain","com.example.common.domain"})
#PropertySource(value = "classpath:application.properties")
#EnableScheduling
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Primary Data source config:
#Configuration
#EnableJpaRepositories(basePackages = "com.example.repository",
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager")
#EnableTransactionManagement
public class PrimaryConfiguration {
#Bean
#ConfigurationProperties(prefix = "datasource.primary")
#Primary
public DataSource primaryDataSource()
{
return DataSourceBuilder.create().build();
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(final EntityManagerFactoryBuilder builder)
{
return builder
.dataSource(primaryDataSource())
.packages("uk.gov.dwp.pss.roc.domain")
.persistenceUnit("primaryPersistenceUnit")
.build();
}
#Bean
#Primary
public JpaTransactionManager primaryTransactionManager(#Qualifier("primaryEntityManagerFactory") final EntityManagerFactory factory)
{
return new JpaTransactionManager(factory);
}
}
Secondary config class:
#Configuration
#EnableJpaRepositories(basePackages = "com.example.common.repository",
entityManagerFactoryRef = "secondaryEntityManagerFactory",
transactionManagerRef = "secondaryTransactionManager")
#EnableTransactionManagement
public class SecondaryConfiguration {
#Bean
#ConfigurationProperties(prefix = "datasource.secondary")
public DataSource secondaryDataSource()
{
return DataSourceBuilder.create().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(final EntityManagerFactoryBuilder builder)
{
return builder
.dataSource(secondaryDataSource())
.packages("uk.gov.dwp.pss.commons.domain.security")
.persistenceUnit("secondaryPersistenceUnit")
.build();
}
#Bean
public JpaTransactionManager secondaryTransactionManager(#Qualifier("secondaryEntityManagerFactory") final EntityManagerFactory factory)
{
return new JpaTransactionManager(factory);
}
}
Repository class:
public interface MyRepository extends JpaRepository<MyObject, String>,JpaSpecificationExecutor<MyObject> {
}
Unit test class use annotation:
#SpringApplicationConfiguration(classes = MyApplication.class)
A database driver was not found on the classpath for the AutoConfiguration to setup while running your test cases.
As #ndrone stated you need to set your database driver. It is unclear if you are attempting to use AutoConfigureTestDatabase. If so you should reference AutoConfigureTestDatabase from the Spring Boot docs.
If this is the case you could specify the Embedded database to use by using the following in your test (configuration):
#AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
Otherwise, you could modify your application.properties in your test folder to specify the connection type such as:
datasource.primary.driver-class-name=org.h2.Driver
datasource.secondary.driver-class-name=org.h2.Driver
I need to use multiple databases.
I am using Spring Boot + Spring Data JPA,
so I have two configuration classes:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages="com.rest.dao.first",
entityManagerFactoryRef = "firstEntityManagerFactory", transactionManagerRef = "firstTransactionManager")
public static class DnbbJdbcConfig {
#Primary
#Bean
#ConfigurationProperties(prefix="datasource.first")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "firstEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(dataSource())
.packages("com.rest.dao.first")
.persistenceUnit("first")
.build();
}
#Primary
#Bean(name = "firstTransactionManager")
PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactory(builder).getObject());
}
}
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages="com.rest.dao.second",
entityManagerFactoryRef = "secondEntityManagerFactory", transactionManagerRef = "secondTransactionManager")
public static class SmsJdbcConfig {
#Bean
#ConfigurationProperties(prefix="datasource.second")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "secondEntityManagerFactory")
#PersistenceContext(unitName = "second")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(dataSource())
.packages("com.rest.dao.second")
.persistenceUnit("second")
.build();
}
#Bean(name = "secondTransactionManager")
PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactory(builder).getObject());
}
}
I guess there is not error and correct.
So, when I use default repository then not error.
(e.g userRepository.findById() - not error in multi datasources)
But, When I use custom repository then error occur.
(https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations)
Custom Implements
public class FirstRepositoryImpl extends QueryDslRepositorySupport implements FirstCustomRepository {
public FirstRepositoryImpl() {
super(First.class);
}
#PersistenceContext(unitName = "first")
private EntityManager entityManager;
private QFirst first = QFirst.first;
#Override
public List<String> messages() {
JPAQuery query = new JPAQuery(entityManager);
return query.from(first).list(first.message);
}
}
ExceptionTrace
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [javax.persistence.EntityManagerFactory] is defined: expected single matching bean but found 2: firstEntityManagerFactory,secondEntityManagerFactory
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findDefaultEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:582) ~[spring-orm-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.findEntityManagerFactory(PersistenceAnnotationBeanPostProcessor.java:541) ~[spring-orm-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$PersistenceElement.resolveEntityManager(PersistenceAnnotationBeanPostProcessor.java:707) ~[spring-orm-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor$PersistenceElement.getResourceToInject(PersistenceAnnotationBeanPostProcessor.java:680) ~[spring-orm-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:178) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) ~[spring-beans-4.2.3.RELEASE.jar:4.2.3.RELEASE]
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.postProcessPropertyValues(PersistenceAnnotationBeanPostProcessor.java:354) ~[spring-orm-4.2.3.RELEASE.jar:4.2.3.RELEASE]
... 45 common frames omitted
Did I misconfigure something?
I wrote sample code in my github repository..
https://github.com/okihouse/spring-boot-multiple-datasource-with-querydsl
Solved myself:
I'm check QueryDslRepositorySupport.class and found out.
#PersistenceContext
public void setEntityManager(EntityManager entityManager) {
Assert.notNull(entityManager);
this.querydsl = new Querydsl(entityManager, builder);
this.entityManager = entityManager;
}
#PersistenceContext have not "unitName"
So, Spring can't inject EntityManager.
I create QueryDslRepositorySupportWrapper.java
and inject EntityManager manually.
And It works.
https://github.com/okihouse/spring-boot-multiple-datasource-with-querydsl