Getting NoUniqueBeanDefinitionException when extends QueryDslRepositorySupport and multiple datasources - spring

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

Related

Spring Boot, required a single bean, but 2 were found when creating multiple datasources

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;

Couldn't determine Dialect for "oracle"

I am using Spring boot(2.3.5), Oracle19c DB, and Hibernate(5.4).
I tried to make multi-datasource connection, but I keep getting a dialect error Couldn't determine Dialect for "oracle".
Error creating bean with name 'jdbcDialect' defined in class path resource [org/springframework/boot/autoconfigure/data/jdbc/JdbcRepositoriesAutoConfiguration$SpringBootJdbcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.relational.core.dialect.Dialect]: Factory method 'jdbcDialect' threw exception; nested exception is org.springframework.data.jdbc.repository.config.DialectResolver$NoDialectException: Cannot determine a dialect for org.springframework.jdbc.core.JdbcTemplate#2ba9ed19. Please provide a Dialect.
I basically followed this tutorial to configure multiple data sources.
application.properties:
spring.datasource-primary.username=oracleprimary
spring.datasource-primary.password=oracleprimary
spring.datasource-primary.url=jdbc:oracle:thin:#//localhost:1521/orcl
spring.datasource-secondary.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource-secondary.username=oraclesecondary
spring.datasource-secondary.password=oraclesecondary
spring.datasource-secondary.url=jdbc:oracle:thin:#//localhost:1521/orcl
Primary configuration:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager",
basePackages = {"com.foo.primary.repository"})
public class PrimaryDataSourceConfiguration {
#Primary
#Bean(name = "primaryDataSourceProperties")
#ConfigurationProperties("spring.datasource-primary")
public DataSourceProperties primaryDataSourceProperties() {
return new DataSourceProperties();
}
#Primary
#Bean(name = "primaryDataSource")
#ConfigurationProperties("spring.datasource-primary.configuration")
public DataSource primaryDataSource(#Qualifier("primaryDataSourceProperties") DataSourceProperties primaryDataSourceProperties) {
return primaryDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
#Primary
#Bean(name = "primaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory(
EntityManagerFactoryBuilder primaryEntityManagerFactoryBuilder, #Qualifier("primaryDataSource") DataSource primaryDataSource) {
Map<String, String> primaryJpaProperties = new HashMap<>();
primaryJpaProperties.put("hibernate.dialect", "org.hibernate.dialect.Oracle12cDialect");
return primaryEntityManagerFactoryBuilder
.dataSource(primaryDataSource)
.packages("com.foo.primary.model")
.persistenceUnit("primaryDataSource")
.properties(primaryJpaProperties)
.build();
}
#Primary
#Bean(name = "primaryTransactionManager")
public PlatformTransactionManager primaryTransactionManager(
#Qualifier("primaryEntityManagerFactory") EntityManagerFactory primaryEntityManagerFactory) {
return new JpaTransactionManager(primaryEntityManagerFactory);
}
Second configuration:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "secondaryEntityManagerFactory",
transactionManagerRef = "secondaryTransactionManager",
basePackages = {"com.foo.secondary.repository"})
public class SecondaryDataSourceConfiguration {
#Bean(name = "secondaryDataSourceProperties")
#ConfigurationProperties("spring.datasource-secondary")
public DataSourceProperties secondaryDataSourceProperties() {
return new DataSourceProperties();
}
#Bean(name = "secondaryDataSource")
#ConfigurationProperties("spring.datasource-secondary.configuration")
public DataSource secondaryDataSource(#Qualifier("secondaryDataSourceProperties") DataSourceProperties secondaryDataSourceProperties) {
return secondaryDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
#Bean(name = "secondaryEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean secondaryEntityManagerFactory(
EntityManagerFactoryBuilder secondaryEntityManagerFactoryBuilder, #Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
Map<String, String> secondaryJpaProperties = new HashMap<>();
secondaryJpaProperties.put("hibernate.dialect", "org.hibernate.dialect.Oracle12cDialect");
return secondaryEntityManagerFactoryBuilder
.dataSource(secondaryDataSource)
.packages("com.foo.secondary.model")
.persistenceUnit("secondaryDataSource")
.properties(secondaryJpaProperties)
.build();
}
#Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager secondaryTransactionManager(
#Qualifier("secondaryEntityManagerFactory") EntityManagerFactory secondaryEntityManagerFactory) {
return new JpaTransactionManager(secondaryEntityManagerFactory);
}
}
I also tried org.hibernate.dialect.Oracle10gDialect, and set spring.jpa.database-platform=org.hibernate.dialect.Oracle12cDialect in application.properties, but nothing changed.
How can I properly configure dialect for oracle?
Spring Data JDBC does not support oracle dialect. You need to define your dialect that implements JdbcDialectProvider.
public final class OracleDialect implements DialectResolver.JdbcDialectProvider {
private static Dialect getDialect(Connection connection) throws SQLException {
DatabaseMetaData metaData = connection.getMetaData();
String name = metaData.getDatabaseProductName().toLowerCase(Locale.ROOT);
if (name.contains("oracle")) {
return AnsiDialect.INSTANCE;
}
return null;
}
#Override
public Optional<Dialect> getDialect(JdbcOperations operations) {
return Optional.ofNullable(operations.execute((ConnectionCallback<Dialect>) OracleDialect::getDialect));
}
}
Add spring-boot-starter-data-jdbc dependency in your build.gradle or pom.xml.
Then, as mentioned in the blog, create spring.factories file in resources/META-INF, and paste the following command:
org.springframework.data.jdbc.repository.config.DialectResolver$JdbcDialectProvider=<your-package>.OracleDialect
Also, since both databases you use are the same (OracleDB), you do not need to set .properties() for entity manager. As #SternK mentioned, you can have spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle12cDialect in your application.properties only.

SpringBoot JPA TransactionRequiredException when saveAndFlush is called

I'm writing a SpringBoot REST server. I have some problems with the data access layer. In my saveReport() service method, if I call save() method of JPA repository, I can't see any records in the db. If I use saveAndFlush() method of JPA repository, I get the following exception:
Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
Db config class:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "qaEntityManagerFactory",
basePackages = { "qa.repository" }
)
public class QADbConfig {
#Bean(name = "qaDataSource")
#ConfigurationProperties(prefix = "spring.qa-datasource")
public HikariDataSource dataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
#Bean(name = "qaEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("qaDataSource") DataSource dataSource
) {
return builder
.dataSource(dataSource)
.packages("qa.model")
.persistenceUnit("qa")
.build();
}
#Bean(name = "qaTransactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("qaEntityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
In my service layer, I added #Transactional
#Service
public class ReportService implements IReportService {
static final ObjectMapper objectMapper = new ObjectMapper();
#Autowired
ReportRepository reportJpaRepository;
#Transactional(propagation=Propagation.REQUIRED)
#Override
public Report saveReport(Report report) {
reportJpaRepository.save(report); // I get no transaction exception if I use saveAndFlush method here
return report;
}
Also in the repository class, I added #Transactional
#Transactional
#Repository
public interface ReportJpaRepository extends JpaRepository<Report, Integer> {
List<Report> findByPrivateReport(Boolean privateReport);
}
Do you see what is wrong with my code?
Thanks

How to set EntityManagerFactory name on a Spring JPA Repository?

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 {
}

problem with writing to 2 databases from Spring boot

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.

Resources