How to configure multiple database in Spring Boot

By | July 1, 2023

In Enterprise applications, we may be required to have multiple databases for storing different kinds of information. For example, in an e-commerce application, it may be required to keep the user’s information in a different database, product-related information in a different database, and payment-related information in a separate database. In this tutorial, we will configure multiple database in Spring Boot application.

The multi-model database has the following benefits:

  • Improve reliability
  • Easy to handle different forms of data
  • Simplify data and database management
  • Easy to scale
  • Improve fault tolerance
  • Data consistency

In this example, we are going to create a Spring Boot application and add two different databases to it. One for storing user-related information and one for product information.

configure multiple database in Spring Boot
Fig 1- Multi-Model database application

Steps to configure multiple database in Spring Boot

Step 1- Create Databases

First, we need to create databases to store user and product-related information. We use MySQL and create two databases. To learn how to create a database in MySQL, please refer How to make database schema in MySQL Workbench?

Fig 2- user_db and product_db schema

Step 2- Create Spring Boot project

To create a new project in Spring Boot, please refer to How to Create a Spring Boot Project Note that you must select Maven as the project type.

Step 3- Adding Database configurations

After we have created our spring boot project, we need to add database configurations for connecting our application to the database. We will be adding the following database configuration in our application.properties file.

XML
#server configuration
server.port=8081

#we need to keep the prefix for data source different
#so, when we fetch the configurations this will helps 
#to determine which database is getting called

#first database configurations
first.datasource.url=jdbc:mysql://localhost:3306/user_db
first.datasource.username=root
first.datasource.password=root
first.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#second database configurations
second.datasource.url=jdbc:mysql://localhost:3306/product_db
second.datasource.username=root
second.datasource.password=root
second.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#similarly, we can add more DBs configurations
Note: We use spring.datasource.url to connect to our database. Here, as we have multiple databases, we need to read each configuration in our Java code. So, to make it easy to understand and differentiate between two different databases, we have used the prefixes first and second.

Step 4- Create Package structure

Before moving further, we need to create the following package structure for two different entities: i.e., user and product, and for both entities we are going to create three different folder structures: config, data, and repository.

Spring Boot package structure
Fig 3- Package structure

Step 5- Create Entities classes

In this step, we will create User.java and Product.java in the corresponding packages. i.e., com.paulsofts.multipledbservices.product.data and com.paulsofts.multipledbservices.user.data as entity classes and add the following codes to them:

User.java

Java
package com.paulsofts.multipledbservices.user.data;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "user_information")
public class User {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int user_id;
	private String user_name;
	
}

Product.java

Java
package com.paulsofts.multipledbservices.product.data;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "product_information")
public class Product {

	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	private int prd_id;
	private String prd_name;
	private double prd_price;
}

Step 6- Create Repositories interface

Now, we are required to create repository interfaces for our User and Product classes. For this, we go to the corresponding user.repository and product.repository packages and create two interfaces, UserRepository and ProductRepository, and extend them to JpaRepository<T, ID>.

UserRepository.java

Java
package com.paulsofts.multipledbservices.user.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.paulsofts.multipledbservices.user.data.User;

public interface UserRepository extends JpaRepository<User, Integer>{

}

ProductRepository.java

Java
package com.paulsofts.multipledbservices.product.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.paulsofts.multipledbservices.product.data.Product;

public interface ProductRepository extends JpaRepository<Product, Integer> {

}

Step 7- Create Database config classes

After we are done with our entity and repository classes, we need to create a configuration class for our database. For this, we go to the corresponding user.config and product.config packages and create two classes, UserDbConfig.java and ProductDbConfig.java. We annotate these classes with @Configuration, @EnableTransactionManagement and @EnableJpaRepositories.

What is @Configuration annotation in Spring Boot?

The @Configuration annotation is part of the Spring framework. It is a class-level annotation that indicates that the class has @Bean definition methods and it allows the Spring container to process the class and generate spring beans to be used in the application.

What is @EnableJpaRepositories annotation in Spring Boot?

The @EnableJpaRepositories annotation is used to create the repository classes from the Spring Data JPA interface.

What is @EnableTransactionManagement annotation in Spring Boot?

It is used to allow application for transaction management by configuring either classical, imperative or reactive transaction management.

UserDbConfig.java

Java
package com.paulsofts.multipledbservices.user.config;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;


@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
		
		entityManagerFactoryRef = "firstDbEntityManagerFactoryBean",
		basePackages = {"com.paulsofts.multipledbservices.user.repository"},
		transactionManagerRef = "firstDbPlatformTransactionManager"
		
		)
public class UserDbConfig {
	
	//this is used for reading configuration from properties file
	@Autowired
	private Environment environment;
	
	//DataSource bean
	@Bean(name = "firstDataSource")
	@Primary
	public DataSource dataSource() {
		//we will fetch and set database configurations from application.properties file
		DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
		driverManagerDataSource.setUrl(environment.getProperty("first.datasource.url"));
		driverManagerDataSource.setUsername(environment.getProperty("first.datasource.username"));
		driverManagerDataSource.setPassword(environment.getProperty("first.datasource.password"));
		driverManagerDataSource.setDriverClassName(environment.getProperty("first.datasource.driver-class-name"));
		
		return driverManagerDataSource;
	}
	
	//EntityManager bean
	@Bean(name = "firstDbEntityManagerFactoryBean")
	public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
		LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
		
		//setting data source
		bean.setDataSource(dataSource());
		
		//setting JPA adapter
		JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
		bean.setJpaVendorAdapter(jpaVendorAdapter);
		
		//setting hibernate properties
		Map<String, String> hibernate_properties = new HashMap<String, String>();
		hibernate_properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
		hibernate_properties.put("hibernate.show_sql", "true");
		hibernate_properties.put("hibernate.hbm2ddl.auto", "create");
		
		bean.setJpaPropertyMap(hibernate_properties);
		
		//setting entities(model class) package path
		bean.setPackagesToScan("com.paulsofts.multipledbservices.user.data");
		return bean;
	}
	
	//TransactionManager bean
	@Bean(name = "firstDbPlatformTransactionManager")
	@Primary
	public PlatformTransactionManager platformTransactionManagerBean() {
		JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
		jpaTransactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());
		return jpaTransactionManager;
	}

}

ProductDbConfig.java

Java
package com.paulsofts.multipledbservices.product.config;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
		
		entityManagerFactoryRef = "secondDbEntityManagerFactoryBean",
		basePackages = {"com.paulsofts.multipledbservices.product.repository"},
		transactionManagerRef = "secondDbPlatformTransactionManager"
		
		)
public class ProductDbConfig {
	
	//this is used for reading configuration from properties file
		@Autowired
		private Environment environment;
		
		//DataSource bean
		@Bean(name = "secondDataSource")
		@Primary
		public DataSource dataSource() {
			//we will fetch and set database configurations from application.properties file
			DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
			driverManagerDataSource.setUrl(environment.getProperty("second.datasource.url"));
			driverManagerDataSource.setUsername(environment.getProperty("second.datasource.username"));
			driverManagerDataSource.setPassword(environment.getProperty("second.datasource.password"));
			driverManagerDataSource.setDriverClassName(environment.getProperty("second.datasource.driver-class-name"));
			
			return driverManagerDataSource;
		}
		
		//EntityManager bean
		@Bean(name = "secondDbEntityManagerFactoryBean")
		public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
			LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
			
			//setting data source
			bean.setDataSource(dataSource());
			
			//setting JPA adapter
			JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
			bean.setJpaVendorAdapter(jpaVendorAdapter);
			
			//setting hibernate properties
			Map<String, String> hibernate_properties = new HashMap<String, String>();
			hibernate_properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
			hibernate_properties.put("hibernate.show_sql", "true");
			hibernate_properties.put("hibernate.hbm2ddl.auto", "create");
			
			bean.setJpaPropertyMap(hibernate_properties);
			
			//setting entities(model class) package path
			bean.setPackagesToScan("com.paulsofts.multipledbservices.product.data");
			return bean;
		}
		
		//TransactionManager bean
		@Bean(name = "secondDbPlatformTransactionManager")
		@Primary
		public PlatformTransactionManager platformTransactionManagerBean() {
			JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
			jpaTransactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());
			return jpaTransactionManager;
		}

}

Step 8- Test application

Now, we will test our application.

Run application:

As we have used hibernate for the field mapping, as soon as we run our application, it must create two tables, user_information and product_information, in two different tables. i.e., user_db and product_db.

Fig 4- Tables created in two database

We can also check the MySQL database; two tables have been created in the database.

Fig 5- Database tables

Test Case:

We also write the test case for our application to insert data into the two different tables.

MultipleDbServicesApplicationTests.java

Java
package com.paulsofts.multipledbservices;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.paulsofts.multipledbservices.product.data.Product;
import com.paulsofts.multipledbservices.product.repository.ProductRepository;
import com.paulsofts.multipledbservices.user.data.User;
import com.paulsofts.multipledbservices.user.repository.UserRepository;

@SpringBootTest
class MultipleDbServicesApplicationTests {
	
	@Autowired
	private UserRepository userRepository;
	
	@Autowired
	private ProductRepository productRepository;

	@Test
	void dbTest() {
		
		//creating user
		User user = User.builder()
			.user_name("paulsofts")
			.build();
		//creating product
		Product product = Product.builder()
					.prd_name("HP Envy x 360")
					.prd_price(94460.0)
					.build();
		//inserting into user_db
		userRepository.save(user);
		//inserting into product_db
		productRepository.save(product);
	}

}

As we can see, our test case is executed successfully, and the data is inserted into the database tables.

Fig 6- Testcase executed

We can also check the database to see whether the data has been inserted into the tables.

Fig 7- user_information table
Fig 8- product_information

Download this example here!

Leave a Reply

Your email address will not be published. Required fields are marked *