opencodez

Connecting Multiple Databases With Spring Data JPA – Source Code On GitHub

Usually, when you build an application you intend to connect to a single database. Its often not required to connect to multiple data sources unless you working on an ETL kind of project.

Connecting Multiple Databases With Spring Data JPA:

In this article, we will see how you can Configure multiple Databases and Connect to Multiple databases with Spring Data JPA

Softwares used

To simulate the real-life scenario, in our demonstration we will connect to two databases Oracle and MySQL.

Maven Dependencies

Below are some of the key maven dependencies that we have used.

xdependencyx
	xgroupIdxorg.springframework.bootx/groupIdx
	xartifactIdxspring-boot-starter-data-jpax/artifactIdx
x/dependencyx

x!-- https://mvnrepository.com/artifact/oracle/ojdbc6 --x
xdependencyx
	xgroupIdxcom.oraclex/groupIdx
	xartifactIdxojdbc6x/artifactIdx
	xversionx11.2.0x/versionx
x/dependencyx

x!-- Database and Pooling --x
xdependencyx
	xgroupIdxmysqlx/groupIdx
	xartifactIdxmysql-connector-javax/artifactIdx
x/dependencyx

Now, we need to make a note here. Oracle jars are not available through maven. So you need to download the jar and install in your local repository before you build your project.

You can read this article x How to add Oracle JDBC driver in your Maven local repository

We will define database details in our properties file. The file will look like below

#Oracle DB Config
db.dialect=org.hibernate.dialect.Oracle10gDialect
db.driver=oracle.jdbc.driver.OracleDriver
db.url=jdbc:oracle:thin:@localhost:1521:xe
db.user=pavans
db.password=******

#MySQL DB Config
mysql.db.dialect=org.hibernate.dialect.MySQLDialect
mysql.db.driver=com.mysql.jdbc.Driver
mysql.db.url=jdbc:mysql://localhost:3306/localdb?useSSL=false
mysql.db.user=lessroot
mysql.db.password=******

Now when we define two data source beans, Spring needs to know which one is primary and which bean is treated as secondary. If you donxt define them Spring will fail to start your application as there will be two beans of the same type it tries to make ready for injection.

You can define any bean primary with annotation @Primary

We will configure our data source bean in the similar way we have done in one of the previous articles.

Read x Java Web Application Starter Template with Spring Boot

Primary Datasource Configuration

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
		entityManagerFactoryRef = "entityManagerFactory", 
		transactionManagerRef = "transactionManager", 
		basePackages = "com.opencodez.dao.oracle.repo"
)
public class PrimaryDbConfig {

	public static final String PROPERTY_NAME_HIBERNATE_JDBC_BATCH_SIZE = "hibernate.jdbc.batch_size";
	public static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
	public static final String PROPERTY_NAME_HIBERNATE_FMT_SQL = "hibernate.format_sql";
	public static final String[] ENTITYMANAGER_PACKAGES_TO_SCAN = { "com.opencodez.dao.oracle.domain" };

	public static final String DB_URL = "db.url";
	public static final String DB_USER = "db.user";
	public static final String DB_PASSWORD = "db.password";
	public static final String DB_DRIVER = "db.driver";
	public static final String DB_DIALECT = "db.dialect";
	
	@Autowired
	private Environment env;

	
	@Bean
	public AnnotationMBeanExporter annotationMBeanExporter() {
	    AnnotationMBeanExporter annotationMBeanExporter = new AnnotationMBeanExporter();
	    annotationMBeanExporter.addExcludedBean("dataSource");
	    annotationMBeanExporter.setRegistrationPolicy(RegistrationPolicy.IGNORE_EXISTING);
	    return annotationMBeanExporter;
	}
	
	@Bean(destroyMethod = "close")
	@Primary
	public DataSource dataSource() {
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		try {
			dataSource.setDriverClass(env.getProperty(DB_DRIVER));

		} catch (PropertyVetoException e) {
			e.printStackTrace();
		}
		dataSource.setJdbcUrl(env.getProperty(DB_URL));
		dataSource.setUser(env.getProperty(DB_USER));
		dataSource.setPassword(env.getProperty(DB_PASSWORD));
		dataSource.setAcquireIncrement(5);
		dataSource.setMaxStatementsPerConnection(20);
		dataSource.setMaxStatements(100);
		dataSource.setMaxPoolSize(500);
		dataSource.setMinPoolSize(5);
		return dataSource;
	}

	@Bean(name = "transactionManager")
	@Primary
	public JpaTransactionManager jpaTransactionManager() {
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());
		return transactionManager;
	}

	@Bean(name = "entityManagerFactory")
	@Primary
	public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
		LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
		entityManagerFactoryBean.setJpaVendorAdapter(vendorAdaptor());
		entityManagerFactoryBean.setDataSource(dataSource());
		entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
		entityManagerFactoryBean.setPersistenceUnitManager(persistenceUnitManager());
		entityManagerFactoryBean.setPersistenceUnitName("orcl");
		entityManagerFactoryBean.setPackagesToScan(ENTITYMANAGER_PACKAGES_TO_SCAN);
		entityManagerFactoryBean.setJpaProperties(jpaHibernateProperties());

		return entityManagerFactoryBean;
	}

	@Bean
	@Primary
	public DefaultPersistenceUnitManager persistenceUnitManager() {
		DefaultPersistenceUnitManager persistenceUnitManager = new DefaultPersistenceUnitManager();
		persistenceUnitManager.setDefaultDataSource(dataSource());
		return persistenceUnitManager;
	}

	private HibernateJpaVendorAdapter vendorAdaptor() {
		HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
		vendorAdapter.setDatabasePlatform(env.getProperty(DB_DIALECT));
		vendorAdapter.setShowSql(false);
		return vendorAdapter;
	}

	private Properties jpaHibernateProperties() {
		Properties properties = new Properties();
		properties.put(PROPERTY_NAME_HIBERNATE_FMT_SQL, env.getProperty(PROPERTY_NAME_HIBERNATE_FMT_SQL));
		properties.put(PROPERTY_NAME_HIBERNATE_JDBC_BATCH_SIZE, env.getProperty(PROPERTY_NAME_HIBERNATE_JDBC_BATCH_SIZE));
		properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, env.getProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
		return properties;
	}
}

Please take a look at how we have added @Primary annotation on the data source, entity manager factory. Also, we have given references to these beans with the qualifier in the @EnableJpaRepositories annotation.

Secondary Datasource Configuration

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
		entityManagerFactoryRef = "mysqlEntityManager", 
		transactionManagerRef = "mysqlTransactionManager", 
		basePackages = "com.opencodez.dao.mysql.repo"
)
public class SecondaryDbConfig {

	public static final String PROPERTY_NAME_HIBERNATE_JDBC_BATCH_SIZE = "hibernate.jdbc.batch_size";
	public static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.show_sql";
	public static final String PROPERTY_NAME_HIBERNATE_FMT_SQL = "hibernate.format_sql";
	public static final String[] ENTITYMANAGER_PACKAGES_TO_SCAN = { "com.opencodez.dao.mysql.domain" };

	public static final String DB_URL = "mysql.db.url";
	public static final String DB_USER = "mysql.db.user";
	public static final String DB_PASSWORD = "mysql.db.password";
	public static final String DB_DRIVER = "mysql.db.driver";
	public static final String DB_DIALECT = "mysql.db.dialect";

	@Autowired
	private Environment env;

	@Bean
	public AnnotationMBeanExporter annotationMBeanExporter() {
		AnnotationMBeanExporter annotationMBeanExporter = new AnnotationMBeanExporter();
		annotationMBeanExporter.addExcludedBean("dataSource");
		annotationMBeanExporter.setRegistrationPolicy(RegistrationPolicy.IGNORE_EXISTING);
		return annotationMBeanExporter;
	}

	@Bean(name = "mysqlDataSource", destroyMethod = "close")
	public DataSource dataSource() {
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		try {
			dataSource.setDriverClass(env.getProperty(DB_DRIVER));

		} catch (PropertyVetoException e) {
			e.printStackTrace();
		}
		dataSource.setJdbcUrl(env.getProperty(DB_URL));
		dataSource.setUser(env.getProperty(DB_USER));
		dataSource.setPassword(env.getProperty(DB_PASSWORD));
		dataSource.setAcquireIncrement(5);
		dataSource.setMaxStatementsPerConnection(20);
		dataSource.setMaxStatements(100);
		dataSource.setMaxPoolSize(500);
		dataSource.setMinPoolSize(5);
		return dataSource;
	}

	@Bean(name = "mysqlTransactionManager")
	public JpaTransactionManager jpaTransactionManager() {
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());
		return transactionManager;
	}

	@Bean(name = "mysqlEntityManager")
	public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
		LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
		entityManagerFactoryBean.setJpaVendorAdapter(vendorAdaptor());
		entityManagerFactoryBean.setDataSource(dataSource());
		entityManagerFactoryBean.setPersistenceProviderClass(HibernatePersistenceProvider.class);
		entityManagerFactoryBean.setPersistenceUnitManager(persistenceUnitManager());
		entityManagerFactoryBean.setPersistenceUnitName("mysql");
		entityManagerFactoryBean.setPackagesToScan(ENTITYMANAGER_PACKAGES_TO_SCAN);
		
		entityManagerFactoryBean.setJpaProperties(jpaHibernateProperties());

		return entityManagerFactoryBean;
	}

	@Bean(name = "mysqlpersistenceUnitManager")
	public DefaultPersistenceUnitManager persistenceUnitManager() {
		DefaultPersistenceUnitManager persistenceUnitManager = new DefaultPersistenceUnitManager();
		persistenceUnitManager.setDefaultDataSource(dataSource());
		return persistenceUnitManager;
	}

	private HibernateJpaVendorAdapter vendorAdaptor() {
		HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
		vendorAdapter.setDatabasePlatform(env.getProperty(DB_DIALECT));
		vendorAdapter.setShowSql(false);
		return vendorAdapter;
	}

	private Properties jpaHibernateProperties() {
		Properties properties = new Properties();
		properties.put(PROPERTY_NAME_HIBERNATE_FMT_SQL, env.getProperty(PROPERTY_NAME_HIBERNATE_FMT_SQL));
		properties.put(PROPERTY_NAME_HIBERNATE_JDBC_BATCH_SIZE,
				env.getProperty(PROPERTY_NAME_HIBERNATE_JDBC_BATCH_SIZE));
		properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, env.getProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
		return properties;
	}
}

The secondary dao config doesnxt have @Primary annotation but the bean qualifiers have to be uniquely referenced.

So far we have completed our database configuration now let us use them. I have defined two tables in each of the database TblOracle and TblMysql. Each of these tables has the same structure.  Below are their corresponding entities

TblOracle

@Entity
@Table(name = "TBL_ORCL")
public class TblOracle {

	@Id
	@GeneratedValue
	@Column(name = "MESSAGE_ID")
	private Long id;

	@Column(name = "MESSAGE")
	private String message;

	@Column(name = "CREATED_DATE")
	private Date created;

	//Getters and Setters

}

TblMysql

@Entity
@Table(name = "tbl_mysql")
public class TblMysql {

	@Id
	@GeneratedValue
	@Column(name = "MESSAGE_ID")
	private Long id;

	@Column(name = "MESSAGE")
	private String message;

	@Column(name = "CREATED_DATE")
	private Date created;

	//Getters and Setters

}

Corresponding persistence.xml

x?xml version="1.0" encoding="UTF-8"?x
xpersistence xmlns="http://java.sun.com/xml/ns/persistence"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
	version="1.0"x

	xpersistence-unit name="orcl" transaction-type="RESOURCE_LOCAL"x
		xclassxcom.opencodez.dao.oracle.domain.TblOraclex/classx
		xexclude-unlisted-classesxtruex/exclude-unlisted-classesx
	x/persistence-unitx

	xpersistence-unit name="mysql" transaction-type="RESOURCE_LOCAL"x
		xclassxcom.opencodez.dao.mysql.domain.TblMysqlx/classx
		xexclude-unlisted-classesxtruex/exclude-unlisted-classesx
	x/persistence-unitx

x/persistencex

For our demo, we will define a simple rest controller and will use the Spring Data JPA Repositories in them.

We will define repositories as

@Autowired
private OracleMessageRepo oracleMessageRepo;

@Autowired
private MysqlMessageRepo mysqlMessageRepo;

These will be referenced in an example as

ListxTblOraclex messages = oracleMessageRepo.findAll();

ListxTblMysqlx messages = mysqlMessageRepo.findAll();

Above is the usage we have seen using JPA repositories.  What if we want to use the traditional way by defining entity manager?

Define Entity Manager

@Autowired
@Qualifier("entityManagerFactory")
private EntityManager oracleEM;
	
@Autowired
@Qualifier("mysqlEntityManager")
private EntityManager mysqlEM;

See how we have autowired the factories, we need to provide the unique qualifier we have set in the config.

Usage

try {
	String sql = "select t from TblOracle t";
	Query query = oracleEM.createQuery(sql);
	ListxTblOraclex list =(ListxTblOraclex)query.getResultList( );
		
} catch (Exception e) {
	e.printStackTrace();
}

try {
	String sql = "select t from TblMysql t";
	Query query = mysqlEM.createQuery(sql);
	ListxTblMysqlx list=(ListxTblMysqlx)query.getResultList( );
	
} catch (Exception e) {
	e.printStackTrace();
}

Check below screen to see them in action

Conclusion

In this article, we have seen how it is easy to connect to Multiple databases with Spring Data JPA. We have seen what precautions we need to take while doing the configuration for multiple databases.

The complete code is available at our GitHub repo. Please feel free to download and try.

Download Code