Spring boot Hibernate error "Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set" when working with multiple data sources - oracle

I am building a Springboot application that needs to talk to 2 different databases (DB2 and Oracle).
Spring-boot-starter-parent version 2.6.6
hibernate version 5.6.7.Final
I first added DB2 support and that was working fine until I added oracle related settings in application.properties file below.
Adding oracle related settings causes following error:
org.hibernate.HibernateException: Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set
Below are the databases and hibernate related settings from my application.properties:
# ==============================
# = DB2
db2.datasource.jdbc-url=jdbc:db2://SERVER1:PORT/DATABASE-1:currentSchema=SCHEMA;
db2.datasource.username=USER1
db2.datasource.password=PASSWORD1
db2.datasource.driver-class-name=com.ibm.db2.jcc.DB2Driver
db2.jpa.properties.hibernate.dialect=org.hibernate.dialect.DB2390Dialect
# ==============================
# = ORACLE
oracle.datasource.jdbc-url=jdbc:oracle:thin:#SERVER2:PORT/DATABASE-2
oracle.datasource.username=USER2
oracle.datasource.password=PASSWORD2
oracle.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
# ==============================
# = JPA / HIBERNATE
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
My pom.xml contains dependencies for oracle and DB2 amongs other required dependencies:
...
<dependency>
<groupId>com.ibm.db2</groupId>
<artifactId>jcc</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<scope>runtime</scope>
</dependency>
...
I have placed my entities, repositories, and data source configurations into different packages as I read is required in this article https://www.javadevjournal.com/spring-boot/multiple-data-sources-with-spring-boot/. My package structure looks like:
project
- dataconfig
- db2
- config
- entity
- repository
- oracle
- config
- entity
- repository
I also added some entities and repositories and my configuration classes.
Here is my DB2Configuration class:
package project.dataconfig.db2.config;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "db2EntityManagerFactory",
transactionManagerRef = "db2TransactionManager",
basePackages = {
"project.dataconfig.db2.repository"
}
)
public class Db2Configuration {
#Primary
#Bean(name = "db2DataSource")
#ConfigurationProperties(prefix = "db2.datasource")
public DataSource db2DataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "db2EntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder
, #Qualifier("db2DataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("project.dataconfig.db2.entity")
.persistenceUnit("db2persistanceunit")
.build();
}
#Primary
#Bean(name = "db2TransactionManager")
public PlatformTransactionManager db2TransactionManager(
#Qualifier("db2EntityManagerFactory") EntityManagerFactory db2EntityManagerFactory) {
return new JpaTransactionManager(db2EntityManagerFactory);
}
}
Here is my OracleConfiguration class:
package project.dataconfig.oracle.config;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "oracleEntityManagerFactory",
transactionManagerRef = "oracleTransactionManager",
basePackages = {
"project.dataconfig.oracle.repository"
}
)
public class OracleConfiguration {
#Bean(name = "oracleDataSource")
#ConfigurationProperties(prefix = "oracle.datasource")
public DataSource oracleDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "oracleEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder
, #Qualifier("oracleDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("project.dataconfig.oracle.entity")
.persistenceUnit("oraclepersistanceunit")
.build();
}
#Bean(name = "oracleTransactionManager")
public PlatformTransactionManager oracleTransactionManager(
#Qualifier("oracleEntityManagerFactory") EntityManagerFactory oracleEntityManagerFactory) {
return new JpaTransactionManager(oracleEntityManagerFactory);
}
}
Before I added oracle related settings in application.properties file, my application worked just find with only DB2 settings as described above.
Once I added oracle related settings and configuration, I started getting this error:
org.hibernate.HibernateException: Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set
Based on the error, I think the problem is caused by my settings for Hibernate dialect in application.properties file. I think it is caused by one of the two settings
...
db2.jpa.properties.hibernate.dialect=org.hibernate.dialect.DB2390Dialect
...
oracle.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
How do I resolve this issue?

I figure it out.
Modify method entityManagerFactory for both Db2Configuration and OracleConfiguration to supply them with the information about hibernate dialect:
for DB2
#Primary
#Bean(name = "db2EntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder
, #Qualifier("db2DataSource") DataSource dataSource) {
final HashMap<String, Object> hibernateProperties = new HashMap<String, Object>();
hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.DB2390Dialect");
return builder
.dataSource(dataSource)
.packages("project.dataconfig.db2.entity")
.properties(hibernateProperties)
.persistenceUnit("db2persistanceunit")
.build();
}
for Oracle
#Bean(name = "oracleEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder
, #Qualifier("oracleDataSource") DataSource dataSource) {
final HashMap<String, Object> hibernateProperties = new HashMap<String, Object>();
hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.Oracle10gDialect");
return builder
.dataSource(dataSource)
.packages("project.dataconfig.oracle.entity")
.properties(hibernateProperties)
.persistenceUnit("oraclepersistanceunit")
.build();
}
After this, my console shows when running app, indicating all is good:
HHH000400: Using dialect: org.hibernate.dialect.DB2390Dialect
Initialized JPA EntityManagerFactory for persistence unit 'db2persistanceunit'
HHH000204: Processing PersistenceUnitInfo [name: oraclepersistanceunit]
HHH000400: Using dialect: org.hibernate.dialect.Oracle10gDialect
Initialized JPA EntityManagerFactory for persistence unit 'oraclepersistanceunit'
My actuator /health endpoint also sees both databases as up and running.

Related

Exception while connecting spring data config to IBM DB2 z/os

I am getting the following exception while bringing up the spring boot app(app doesnt not have any code just the DB related configs and connection parameters which are defined in the application.properties file)
Could not fetch the SequenceInformation from the database
com.ibm.db2.jcc.am.SqlSyntaxErrorException: DB2 SQL Error: SQLCODE=-204, SQLSTATE=42704, SQLERRMC=SYSCAT.SEQUENCES, DRIVER=4.19.49
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>db2.jcc</groupId> <!-- internal from private repo -->
<artifactId>db2jcc_license_cu</artifactId>
<version>4.19.49</version>
</dependency>
<dependency>
<groupId>db2.jcc</groupId> <!-- internal from private repo -->
<artifactId>db2jcc4</artifactId>
<version>4.19.49</version>
</dependency>
DB connection properties:
spring.jpa.hibernate.ddl-auto=validate
spring.datasource.driver-class-name=com.ibm.db2.jcc.DB2Driver
spring.jpa.hibernate.dialect=org.hibernate.dialect.DB2Dialect
spring.jpa.hibernate.synonyms=true
spring.jpa.show-sql=true
spring.db2.datasource.url=jdbc:db2://HOSTNAME:PORT/DBNAME
spring.db2.datasource.username=somename
spring.db2.datasource.password=password
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
DB config code in spring boot:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "db2EntityManagerFactory", transactionManagerRef = "db2TransactionManager", basePackages = {
"com.example.db.repositories" })
public class DB2Config {
private Logger log = LogManager.getLogger(DB2Config.class);
#Value("${spring.datasource.driver-class-name}")
String driverClassName;
#Value("${spring.db2.datasource.url}")
String dataSourceUrl;
#Value("${spring.db2.datasource.username}")
String username;
#Value("${spring.db2.datasource.password}")
String passkey;
#Value("${spring.jpa.hibernate.ddl-auto}")
String hbm2ddl;
#Value("${spring.jpa.hibernate.dialect}")
String dialect;
#Bean
public DataSource db2DataSource() {
log.info("Loading db2 datasource");
final DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(dataSourceUrl);
dataSource.setUsername(username);
dataSource.setPassword(passkey);
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean db2EntityManagerFactory() {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(db2DataSource());
em.setPackagesToScan("com.example.db");
final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
final HashMap<String, Object> properties = new HashMap<String, Object>();
properties.put("hibernate.hbm2ddl.auto", hbm2ddl);
properties.put("hibernate.dialect", dialect);
em.setJpaPropertyMap(properties);
em.setPersistenceUnitName("db2");
return em;
}
#Bean
public PlatformTransactionManager db2TransactionManager() {
final JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(db2EntityManagerFactory().getObject());
return transactionManager;
}
}
Per comment thread, when working with Db2, always be aware that the target platform (Z/OS, i-series , Linux/Unix/Windows) determines the SQL dialect , along with many other things. The platform determines the SQL dialect.
In your case, as you are working with Db2 for Z/OS, it was necessary to use the DB2390Dialect with your toolchain so that the correct catalog objects get referenced on the target database. Specifically SYSIBM is the schema for Db2-for-Z/OS catalog objects, while SYSCAT is the schema used for Linux/Unix/Windows.

HibernateException: hibernate.dialect not set, but I did set it in application.properties

I am working on a Spring Boot application that uses a number of databases. I define datasources, I define entitymanagers, and I keep getting this error
org.hibernate.HibernateException: Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set
at org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl.determineDialect(DialectFactoryImpl.java:100)
at org.hibernate.engine.jdbc.dialect.internal.DialectFactoryImpl.buildDialect(DialectFactoryImpl.java:54)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:137)
at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:35)
at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:94)
at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263)
This is the #Configuration file for one database:
#Configuration
public class CustomerDbConfig {
#Bean
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "customerEntityManager")
#PersistenceContext(unitName = "customerEntityManager")
public LocalContainerEntityManagerFactoryBean customerEntityManagerFactory(
EntityManagerFactoryBuilder builder, DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.example.report_service").build();
}
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(emf);
return jpaTransactionManager;
}
#Bean
public EntityManagerFactoryBuilder entityManagerFactoryBuilder() {
return new EntityManagerFactoryBuilder(new HibernateJpaVendorAdapter(), new HashMap<>(), null);
}
}
and these are the application.properties
spring.datasource.url=jdbc:postgresql://dv1.example.com:5432/customer
spring.datasource.username=${DATABASECUSTOMERUSERNAME:customer}
spring.datasource.password=${DATABASECUSTOMERPASSWORD:customer}
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.maximum-pool-size=${MAX_POOL_SIZE:20}
spring.datasource.tomcat.validation-query=SELECT 1
spring.datasource.tomcat.validation-query-timeout=5000
spring.datasource.tomcat.test-on-borrow=true
spring.datasource.tomcat.test-on-connect=true
spring.transaction.default-timeout=${TRANSACTION_TIMEOUT:3600}
spring.jpa.properties.hibernate.proc.param_null_passing=true
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation= true
spring.jpa.open-in-view=false
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.show_sql=false
spring.jpa.properties.hibernate.use_sql_comments=true
spring.jpa.properties.hibernate.type=trace
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
When I explicitely set a persistenceUnit name in the entitymanager, the error changes, it complains about not finding a PU with the name I specified.
I limited my application to having only one database, trying to figure out what's happening. When I remove the configuration file there's no issues. What makes this application special is that it uses native queries only, it doesn't have #Entity classes because the database is updated by other applictions. When I remove the .packages(..) part from the code, it complaines like
No persistence units parsed from {classpath*:META-INF/persistence.xml}
I suspect the issue is related to the fact that we don't have classes that need to be scanned.
Edit:
Following M.Deinum's answer, I replaced the config by
#Configuration
#EnableTransactionManagement
public class CustomerDbConfig {
#ConfigurationProperties(prefix = "customer.datasource")
#Bean(name = "customer-datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "customer-jdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(#Qualifier("customer-datasource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
and I use the four templates in the daos. Works like a charm.
When using JPA you need entities. Using the JPA classes to just execute SQL queries is overkill. Just use a plain Jdbctemplate or NamedParameterJdbcTemplate for that.

Spring Boot + Camel JPA with multiple Datasources

I need to create a Camel route that polls a DB, transforms the retrieved data and then inserts the new entities into another DB. I need help with the configuration.
These are the jpa endpoints:
from("jpa://" + Entity1.class.getName()
+ "?"
+ "persistenceUnit=entity1PU&"
+ "consumer.namedQuery=query1&"
+ "consumeDelete=false"
)
//various operations...
.to("direct:route2");
from("direct:route2")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
//processing...
}
})
.to("jpa://" + Entity2.class.getName()
+ "?"
+ "persistenceUnit=entity2PU&"
+ "entityType=java.util.ArrayList&"
+ "usePersist=true&"
+ "flushOnSend=true");
I'd like to configure the persistence units by code and annotations, instead of using persistence.xml; these are the relative classes. This is the first:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
basePackages = "com.foo.entity1.repo",
entityManagerFactoryRef = "entity1EntityManagerFactory",
transactionManagerRef = "entity1TransactionManager"
)
public class Entity1PersistenceConfig {
#Autowired
#Qualifier("datasource1")
private DataSource dataSource;
#Primary
public DataSource dataSource() {
return this.dataSource;
}
#Primary
#Bean(name="entity1EntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.foo.entity1.domain");
factory.setDataSource(this.dataSource());
factory.setPersistenceProviderClass(HibernatePersistenceProvider.class);
factory.setPersistenceUnitName("entity1PU");
Properties hibernateProps = setJpaHibernateCommonProperties();
hibernateProps.setProperty("hibernate.dialect", environment.getProperty("spring.jpa.properties.hibernate.oracle.dialect"));
factory.setJpaProperties(hibernateProps);
return factory;
}
#Primary
#Bean(name="entity1TransactionManager")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return jpaTransactionManager;
}
}
and the second one:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
basePackages = "com.foo.entity2.repo",
entityManagerFactoryRef = "entity2EntityManagerFactory",
transactionManagerRef = "entity2TransactionManager"
)
public class Entity2PersistenceConfig {
#Autowired
#Qualifier("datasource2")
private DataSource dataSource;
public DataSource dataSource() {
return this.dataSource;
}
#Bean(name="entity2EntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.foo.entity2.domain");
factory.setDataSource(this.dataSource());
factory.setPersistenceProviderClass(HibernatePersistenceProvider.class);
factory.setPersistenceUnitName("entity2PU");
Properties hibernateProps = setJpaHibernateCommonProperties();
hibernateProps.setProperty("hibernate.dialect", environment.getProperty("spring.jpa.properties.hibernate.mysql.dialect"));
factory.setJpaProperties(hibernateProps);
return factory;
}
#Bean(name="entity2TransactionManager")
public PlatformTransactionManager transactionManager() {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return jpaTransactionManager;
}
}
Entities and repositories are in the correct packages; also, the configuration of the databases is correctly done in a specific class, and are correctly injected.
When I try to run the project, I get the following:
2018-05-30 11:38:36.481 INFO 1056 --- [main] o.h.j.b.internal.PersistenceXmlParser: HHH000318: Could not find any META-INF/persistence.xml file in the classpath
and
javax.persistence.PersistenceException: No Persistence provider for EntityManager named entity1PU
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:61) ~[hibernate-jpa-2.1-api-1.0.0.Final.jar:1.0.0.Final]
at org.springframework.orm.jpa.LocalEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalEntityManagerFactoryBean.java:96) ~[spring-orm-4.3.17.RELEASE.jar:4.3.17.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:384) ~[spring-orm-4.3.17.RELEASE.jar:4.3.17.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:371) ~[spring-orm-4.3.17.RELEASE.jar:4.3.17.RELEASE]
at org.apache.camel.component.jpa.JpaEndpoint.createEntityManagerFactory(JpaEndpoint.java:552) ~[camel-jpa-2.21.1.jar:2.21.1]
at org.apache.camel.component.jpa.JpaEndpoint.getEntityManagerFactory(JpaEndpoint.java:250) ~[camel-jpa-2.21.1.jar:2.21.1]
at org.apache.camel.component.jpa.JpaEndpoint.validate(JpaEndpoint.java:545) ~[camel-jpa-2.21.1.jar:2.21.1]
at org.apache.camel.component.jpa.JpaEndpoint.createConsumer(JpaEndpoint.java:165) ~[camel-jpa-2.21.1.jar:2.21.1]
at org.apache.camel.impl.EventDrivenConsumerRoute.addServices(EventDrivenConsumerRoute.java:69) ~[camel-core-2.21.1.jar:2.21.1]
at org.apache.camel.impl.DefaultRoute.onStartingServices(DefaultRoute.java:103) ~[camel-core-2.21.1.jar:2.21.1]
at org.apache.camel.impl.RouteService.doWarmUp(RouteService.java:172) ~[camel-core-2.21.1.jar:2.21.1]
at org.apache.camel.impl.RouteService.warmUp(RouteService.java:145) ~[camel-core-2.21.1.jar:2.21.1]
Why is it looking for a persistence.xml file instead of using annotations? I'm using Spring Boot 1.5.13.RELEASE with Camel 2.21.1.
Basically to make it work I had to abandon using annotations and create an equivalent persistence.xml file, because Apache Camel was looking for configuration only in it.
As soon as I could upgrade to Spring Boot 2 (because Apache Camel got updated and started to support it), I managed to make configuration by annotations work and dropped the persistence.xml file.

Spring Data JPA - Multiple EnableJpaRepositories

My application has multiple data sources , so i have created two data source configuration classes based on this URL .
But while running the spring boot application am getting error
Description:
Field userDataRepo in com.cavion.services.UserDataService required a bean named 'entityManagerFactory' that could not be found.
Action:
Consider defining a bean named 'entityManagerFactory' in your configuration.
From this Question on StackOverflow helped me to figure out the issue.i need to specify the entityManagerFactoryRef on my JPA repositories .
But i have many repository classes some of them uses Entitymanager 'A' and some of them uses 'B' . my current spring boot application class is like this
#SpringBootApplication(exclude = { DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class })
#EnableTransactionManagement
#EntityScan("com.info.entity")
#ComponentScan({"com.info.services","com.info.restcontroller"})
#EnableJpaRepositories("com.info.repositories")
public class CavionApplication {
public static void main(String[] args) {
SpringApplication.run(CavionApplication.class, args);
}
#Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
System.out.println("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
};
}}
I have given the EnableJpaRepositories on the spring boot class , so how can i configure multiple EnableJpaRepositories so that i can configure multiple entityManagerFactory ?
Please suggest the best way to setup the multiple data sources .
In order to let spring knows what DataSource is related to what Repository you should define it at the #EnableJpaRepositories annotation. Let's assume that we have two entities, the Servers entity and the Domains entity and each one has its own Repo then each Repository has its own JpaDataSource configuration.
1. Group all the repositories based on the Data Source that they are related to. For example
Repository for Domains entities (package: org.springdemo.multiple.datasources.repository.domains):
package org.springdemo.multiple.datasources.repository.domains;
import org.springdemo.multiple.datasources.domain.domains.Domains;
import org.springframework.data.jpa.repository.JpaRepository;
public interface DomainsRepository extends JpaRepository<Domains,Long> {
}
Repository for Servers entities (package: org.springdemo.multiple.datasources.repository.servers)
package org.springdemo.multiple.datasources.repository.servers;
import org.springdemo.multiple.datasources.domain.servers.Servers;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ServersRepository extends JpaRepository<Servers,Long> {
}
2. For each JPA Data Soruce you need to define a configuration, in this example I show how to configure two different DataSources
Domains Jpa Configuration: the relationship between the Data Source and the repository is defined in the basePackages value, that is the reason why is necessary to group the repositories in different packages depending on the entity manager that each repo will use.
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "domainsEntityManager",
transactionManagerRef = "domainsTransactionManager",
basePackages = {"org.springdemo.multiple.datasources.repository.domains"}
)
public class DomainsConfig {
Servers Data Source Configuration: as you can see the basePackages value has the package name of the Servers Repository , and also the values of entityManagerFactoryRef and transactionManagerRef are different in order to let spring separate each entityManager.
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "serversEntityManager",
transactionManagerRef = "serversTransactionManager",
basePackages = {"org.springdemo.multiple.datasources.repository.servers"}
)
public class ServersConfig {
3. Set one Datasource as primary
In order to avoid the error message: Parameter 0 of constructor in org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration required a single bean, but 2 were found: just set one of the datasource as #Primary, in this example I select the Servers Datasource as primary:
#Bean("serversDataSourceProperties")
#Primary
#ConfigurationProperties("app.datasource.servers")
public DataSourceProperties serversDataSourceProperties(){
return new DataSourceProperties();
}
#Bean("serversDataSource")
#Primary
#ConfigurationProperties("app.datasource.servers")
public DataSource serversDataSource(#Qualifier("serversDataSourceProperties") DataSourceProperties serversDataSourceProperties) {
return serversDataSourceProperties().initializeDataSourceBuilder().build();
}
If you need more information please see the full example for each configuration:
Servers JPA Configuration
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "serversEntityManager",
transactionManagerRef = "serversTransactionManager",
basePackages = {"org.springdemo.multiple.datasources.repository.servers"}
)
public class ServersConfig {
#Bean(name = "serversEntityManager")
public LocalContainerEntityManagerFactoryBean getServersEntityManager(EntityManagerFactoryBuilder builder,
#Qualifier("serversDataSource") DataSource serversDataSource){
return builder
.dataSource(serversDataSource)
.packages("org.springdemo.multiple.datasources.domain.servers")
.persistenceUnit("servers")
.properties(additionalJpaProperties())
.build();
}
Map<String,?> additionalJpaProperties(){
Map<String,String> map = new HashMap<>();
map.put("hibernate.hbm2ddl.auto", "create");
map.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
map.put("hibernate.show_sql", "true");
return map;
}
#Bean("serversDataSourceProperties")
#Primary
#ConfigurationProperties("app.datasource.servers")
public DataSourceProperties serversDataSourceProperties(){
return new DataSourceProperties();
}
#Bean("serversDataSource")
#Primary
#ConfigurationProperties("app.datasource.servers")
public DataSource serversDataSource(#Qualifier("serversDataSourceProperties") DataSourceProperties serversDataSourceProperties) {
return serversDataSourceProperties().initializeDataSourceBuilder().build();
}
#Bean(name = "serversTransactionManager")
public JpaTransactionManager transactionManager(#Qualifier("serversEntityManager") EntityManagerFactory serversEntityManager){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(serversEntityManager);
return transactionManager;
}
}
Domains JPA Configuration
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "domainsEntityManager",
transactionManagerRef = "domainsTransactionManager",
basePackages = {"org.springdemo.multiple.datasources.repository.domains"}
)
public class DomainsConfig {
#Bean(name = "domainsEntityManager")
public LocalContainerEntityManagerFactoryBean getdomainsEntityManager(EntityManagerFactoryBuilder builder
,#Qualifier("domainsDataSource") DataSource domainsDataSource){
return builder
.dataSource(domainsDataSource)
.packages("org.springdemo.multiple.datasources.domain.domains")
.persistenceUnit("domains")
.properties(additionalJpaProperties())
.build();
}
Map<String,?> additionalJpaProperties(){
Map<String,String> map = new HashMap<>();
map.put("hibernate.hbm2ddl.auto", "create");
map.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
map.put("hibernate.show_sql", "true");
return map;
}
#Bean("domainsDataSourceProperties")
#ConfigurationProperties("app.datasource.domains")
public DataSourceProperties domainsDataSourceProperties(){
return new DataSourceProperties();
}
#Bean("domainsDataSource")
public DataSource domainsDataSource(#Qualifier("domainsDataSourceProperties") DataSourceProperties domainsDataSourceProperties) {
return domainsDataSourceProperties.initializeDataSourceBuilder().build();
}
#Bean(name = "domainsTransactionManager")
public JpaTransactionManager transactionManager(#Qualifier("domainsEntityManager") EntityManagerFactory domainsEntityManager){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(domainsEntityManager);
return transactionManager;
}
}
In order to separate each datasource I put the configuration in the application.properties file, like this:
app.datasource.domains.url=jdbc:h2:mem:~/test
app.datasource.domains.driver-class-name=org.h2.Driver
app.datasource.servers.driver-class-name=com.mysql.jdbc.Driver
app.datasource.servers.url=jdbc:mysql://localhost:3306/v?autoReconnect=true&useSSL=false
app.datasource.servers.username=myuser
app.datasource.servers.password=mypass
If you need more information please see the following documentation:
Spring Documentation: howto-two-datasources
A similar example of how configure two different databases: github example
The answered provided by #Daniel C. is correct. Small correction/observation from my side.
#Primary is not required if you don't want to mark any datasource as
default one, otherwise necessary.
If you are defining any of the EntityManagerFactoryBean with #Bean name as entityManagerFactory then it's better to mark it #Primary to avoid conflict.
#ConfigurationProperties("app.datasource.servers")
can be marked at class level instead of defining at method level.
Better to return HikariDataSource as datasource if you using Spring
Boot 2.x or higher version as it has been changed.
Make sure you define exact property for jdbc-url which is being used by
HikariDataSource to refer JDBC Connection URL.
I just added a module aware multi database aware library for mysql in github.Some application properties need to be added and you are done .
Documentation and other details could be found at :-
https://github.com/yatharthamishra0419/spring-boot-data-multimodule-mysql

how to use #DataJpaTest with multiple datasourse

I tried to write integration test using annotation #DataJpaTest .
I have two datasource: Primary and secondary (class config)
in result i have an error:
expected single matching bean but found 2: primaryDataSource,secondary
then i tried to add a annotation
#AutoConfigureTestDatabase(replace= AutoConfigureTestDatabase.Replace.AUTO_CONFIGURED)
and With AUTO_CONFIGURED only DataSources configured by properties will be replaced but instead embedded h2 i saw Dialect : HHH000400: Using dialect: org.hibernate.dialect.Oracle10gDialect
how using #DataJpaTest with multiple datasources ?
public class DataSourcesConfig {
#Bean
#Primary
#ConfigurationProperties(prefix="spring.datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "secondary")
#ConfigurationProperties(prefix="datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
}
I have a #Primary configuration class
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
basePackages = {"com.something"}
)
public class APrimaryDBDBConfiguration {
#Primary
#Bean
#ConfigurationProperties("spring.datasource")
public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties();
}
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties("spring.datasource.hikari")
public HikariDataSource dataSource() {
return dataSourceProperties()
.initializeDataSourceBuilder()
.type(HikariDataSource.class).build();
}
#Primary
#Bean(name = "entityManagerFactory")
#Profile("!test")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("com")
.persistenceUnit("some_persistence_unit")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Later, in my repository test classes:
#RunWith(SpringRunner.class)
#DataJpaTest
#ActiveProfiles("test")
#Import(APrimaryDBDBConfiguration.class)
Finally, my test properties have:
spring.jpa.hibernate.ddl-auto=create-drop
Check if you have h2 database added as dependency in test scope. If not, add and try:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
Found a possible solution here.
Basically you manually configure the H2 database appropriately instead of letting Spring do it automatically.
Create an application.properties file in “src/test/resources” with the following content
# Let Spring autodetect the different SQL Dialects of each datasource
spring.jpa.database=default
# Generate the DB schema in the In-Memory H2 databases based on the JPA Entities
spring.jpa.generate-ddl=true
# H2 In-Memory Database "foo" (used in tests instead of a real PostgreSQL DB)
spring.datasource.url=jdbc:h2:mem:foo;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
# H2 In-Memory Database "bar" (used in tests instead of a real PostgreSQL DB)
bar.datasource.url=jdbc:h2:mem:bar;DB_CLOSE_ON_EXIT=FALSE
bar.datasource.username=sa
bar.datasource.password=
bar.datasource.driver-class-name=org.h2.Driver

Resources