How to Configure Multiple Data Sources in a Spring Boot Application

Welcome, in this tutorial, we will see how to configure multiple data sources in a single boot application.

Here we will use a single source of truth (meaning a datatype of a single type but fetch the data from two different databases).

1. Introduction

Before going further in this tutorial, we will look at the common terminology such as introduction to Spring Boot and Lombok.

1.1 Spring Boot

1.2 Lombok

1.2.1 Lombok features

Feature Details
val Local variables are declared as final
var Mutable local variables
@Slf4J Creates an SLF4J logger
@Cleanup Will call close() on the resource in the finally block
@Getter Creates getter methods for all properties
@Setter Creates setter for all non-final properties
@EqualsAndHashCode
  • Generates implementations of equals(Object other) and hashCode()
  • By default will use all non-static, non-transient properties
  • Can optionally exclude specific properties
@ToString
  • Generates String of class name, and each field separated by commas
  • Optional parameter to include field names
  • Optional parameter to include a call to the super toString method
@NoArgsConstructor
  • Generates no-args constructor
  • Will cause compiler error if there are final fields
  • Can optionally force, which will initialize final fields with 0/false/null var – mutable local variables
@RequiredArgsContructor
  • Generates a constructor for all fields that are final or marked @NonNull
  • The constructor will throw a NullPointerException if any @NonNull fields are null val – local variables are declared final
@AllArgsConstructor
  • Generates a constructor for all properties of the class
  • Any @NotNull properties will have null checks
@Data
  • Generates typical boilerplate code for POJOs
  • Combines – @Getter, @Setter, @ToString, @EqualsAndHashCode, @RequiredArgsConstructor
  • No constructor is generated if constructors have been explicitly declared
@Builder
  • Implements the Builder pattern for object creation
@Value
  • The immutable variant of @Data
  • All fields are made private and final by default

Let us go ahead with the tutorial implementation but before going any further I’m assuming that you’re aware of the Spring boot basics.

2. How to Configure Multiple Data Sources in a Spring Boot Application

2.1 Tools Used for Spring boot application and Project Structure

We are using Eclipse Kepler SR2, JDK 8, and Maven. In case you’re confused about where you should create the corresponding files or folder, let us review the project structure of the spring boot application.

Fig. 1: Project structure

Let us start building the application!

3. Creating a Spring Boot application

Below are the steps involved in developing the application.

3.1 Maven Dependency

Here, we specify the dependency for the Spring Boot (Web, JPA, and Configuration processor), H2 database, Lombok, and Java faker. Maven will automatically resolve the other dependencies. The updated file will have the following code.

pom.xml

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?xml version="1.0" encoding="UTF-8"?>
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>com.springboot.multiple.datasources</groupId>
    <artifactId>SpringbootMultipleDatasources</artifactId>
    <version>0.0.1-SNAPSHOT</version>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.0</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
 
    <properties>
        <java.version>1.8</java.version>
    </properties>
 
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- required for the proper working of @ConfigurationProperties -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- embedded database (h2) dependency. -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- lombok dependency. -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- faker dependency to generate some random data. -->
        <dependency>
            <groupId>com.github.javafaker</groupId>
            <artifactId>javafaker</artifactId>
            <version>1.0.2</version>
        </dependency>
    </dependencies>
 
    <build>
        <!-- to make the application as fat jar so that spring boot libraries are
            included -->
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

3.2 Application Properties

Create a new properties file at the location: SpringbootMultipleDatasources/src/main/resources/ and add the following code to it. Here we will define the two data sources for a single h2 database along with database and application properties.

application.properties

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
## application properties
server.port=9091
server.servlet.context-path=/jcg
spring.application.name=springboot-multiple-datasources
## h2 database settings
## database1 name - beerdb
spring.db1.datasource.username=sa
spring.db1.datasource.password=
spring.db1.datasource.jdbc-url=jdbc:h2:mem:beerdb
spring.db1.datasource.driver-class-name=org.h2.Driver
## database2 name - customerdb
spring.db2.datasource.username=sa
spring.db2.datasource.password=
spring.db2.datasource.jdbc-url=jdbc:h2:mem:customerdb
spring.db2.datasource.driver-class-name=org.h2.Driver
 
spring.jpa.show-sql=true
## url for h2 console - http://localhost:9091/jcg/h2-console
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

3.3 Java Classes

Let us write all the java class(es) involved in this application. Remember will only configure the important classes here to build a concrete understanding for this tutorial.

3.3.1 Implementation/Main class

Add the following code to the main class to bootstrap the application from the main method. Always remember, the entry point of the spring boot application is the class containing @SpringBootApplication annotation and the static main method.

Runner.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package com.springboot.multiple.datasources;
 
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
// causes Lombok to generate a logger field.
@Slf4j
// serves two purposes i.e. configuration and bootstrapping.
@SpringBootApplication
public class Runner {
 
    public static void main(String[] args) {
        SpringApplication.run(Runner.class, args);
        log.info("Application started successfully.");
    }
}

3.3.2 Beer database Configuration Class

Add the following code to the beer configuration class where we will configure the properties so that the spring boot application could connect to the beer database on the startup and perform the table schema creation using the Beer.java model class. This class will define the Spring bean for the data source, Entity Manager Factory, and Transaction Manager.

Also, the use of @Primary annotation in this class tells Spring to treat the bean instances defined in this class as the primary beans.

BeerConfig.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.springboot.multiple.datasources.config;
 
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
 
@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "db1EntityMgrFactory",
        transactionManagerRef = "db1TransactionMgr",
        basePackages = {
                "com.springboot.multiple.datasources.beer.repo"
        })
@EnableTransactionManagement
public class BeerConfig {
 
    @Bean(name = "datasource1")
    @ConfigurationProperties(prefix = "spring.db1.datasource")
    @Primary
    // setting up the data source for the beer database.
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }
 
    @Bean(name = "db1EntityMgrFactory")
    @Primary
    public LocalContainerEntityManagerFactoryBean db1EntityMgrFactory(
            final EntityManagerFactoryBuilder builder,
            @Qualifier("datasource1") final DataSource dataSource) {
        // dynamically setting up the hibernate properties for each of the datasource.
        final Map<String, String> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto", "create-drop");
        // in springboot2 the dialect can be automatically detected.
        // we are setting up here just to avoid any incident.
        properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        return builder
                .dataSource(dataSource)
                .properties(properties)
                .packages("com.springboot.multiple.datasources.model")
                .persistenceUnit("beer")
                .build();
    }
 
    @Bean(name = "db1TransactionMgr")
    @Primary
    public PlatformTransactionManager db1TransactionMgr(
            @Qualifier("db1EntityMgrFactory") final EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

3.3.3 Customer database Configuration Class

Add the following code, to the customer configuration class where we will configure the properties so that the spring boot application could connect to the beer database on the startup and perform the table schema creation using the Customer.java model class. This class will define the Spring bean for the data source, Entity Manager Factory, and Transaction Manager.

CustomerConfiguration.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.springboot.multiple.datasources.config;
 
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
 
@Configuration
@EnableJpaRepositories(
        entityManagerFactoryRef = "db2EntityMgrFactory",
        transactionManagerRef = "db2TransactionMgr",
        basePackages = {
                "com.springboot.multiple.datasources.customer.repo"
        })
@EnableTransactionManagement
public class CustomerConfig {
 
    @Bean(name = "datasource2")
    @ConfigurationProperties(prefix = "spring.db2.datasource")
    // setting up the data source for the customer database.
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }
 
    @Bean(name = "db2EntityMgrFactory")
    public LocalContainerEntityManagerFactoryBean db2EntityMgrFactory(
            final EntityManagerFactoryBuilder builder,
            @Qualifier("datasource2") final DataSource dataSource) {
        // dynamically setting up the hibernate properties for each of the datasource.
        final Map<String, String> properties = new HashMap<>();
        properties.put("hibernate.hbm2ddl.auto", "create-drop");
        // in springboot2 the dialect can be automatically detected.
        // we are setting up here just to avoid any incident.
        properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
        return builder
                .dataSource(dataSource)
                .properties(properties)
                .packages("com.springboot.multiple.datasources.model")
                .persistenceUnit("customer")
                .build();
    }
 
    @Bean(name = "db2TransactionMgr")
    public PlatformTransactionManager db2EntityMgrFactory(
            @Qualifier("db2EntityMgrFactory") final EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}

3.3.4 Beer Model Class

Add the following code to the beer model class which will be used to create a table schema on the application startup. The table schema for this model class will be created in the beerdb.

Beer.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.springboot.multiple.datasources.model;
 
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
 
import javax.persistence.*;
 
@Entity
@Table(name = "beer")
// causes Lombok to generate toString(), equals(), hashCode(), getter() & setter(), and Required arguments constructor in one go.
@Data
// causes Lombok to implement the Builder design pattern for the Pojo class.
// usage can be seen in DefaultBeersLoader.java -> createNewBeer() method.
@Builder
// causes Lombok to generate a constructor with no parameters.
@NoArgsConstructor
// causes Lombok to generate a constructor with 1 parameter for each field in your class.
@AllArgsConstructor
@Component
public class Beer {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    int id;
    @Column(name = "beer_name", nullable = false)
    String name;
    @Column(name = "beer_style", nullable = false)
    String style;
    String malt;
}

3.3.5 Customer Model Class

Add the following code to the customer model class which will be used to create a table schema on the application startup. The table schema for this model class will be created in the customerdb.

Customer.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.springboot.multiple.datasources.model;
 
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
 
import javax.persistence.*;
 
@Entity
@Table(name = "customer")
// causes Lombok to generate toString(), equals(), hashCode(), getter() & setter(), and Required arguments constructor in one go.
@Data
// causes Lombok to implement the Builder design pattern for the Pojo class.
// usage can be seen in DefaultCustomersLoader.java -> createNewCustomer() method.
@Builder
// causes Lombok to generate a constructor with no parameters.
@NoArgsConstructor
// causes Lombok to generate a constructor with 1 parameter for each field in your class.
@AllArgsConstructor
@Component
public class Customer {
 
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    int id;
    @Column(name = "full_name", nullable = false)
    String fullName;
    @Column(name = "age", nullable = false)
    int age;
    @Column(name = "gender", nullable = false)
    String gender;
    @Column(name = "phone_number", unique = true)
    String phoneNumber;
}

3.3.6 Common Controller Class

Add the following code to the controller class where we have defined the HTTP GET methods to fetch the data from the two different databases (i.e. beerdb and customerdb). The method will call the service methods which in turn will interact with the DAO interface methods to fetch the data from the data source. Here the DAO interface is a simple interface that extends the JPA interface to perform the database operations.

CommonCtrl.java

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.springboot.multiple.datasources.controller;
 
import com.springboot.multiple.datasources.model.Beer;
import com.springboot.multiple.datasources.model.Customer;
import com.springboot.multiple.datasources.service.BeerService;
import com.springboot.multiple.datasources.service.CustomerService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.List;
 
@RestController
@RequestMapping(value = "/api")
@Slf4j
public class CommonCtrl {
 
    @Autowired
    private BeerService service;
    @Autowired
    private CustomerService service2;
 
    @GetMapping(value = "/beers")
    public ResponseEntity<List<Beer>> findAllBeers() {
        log.info("Fetch all beers info from the database.");
        // will fetch the beers data from the 'beer' table present in the 'beerdb'
        final List<Beer> beers = service.finalAll();
        return new ResponseEntity<>(beers, HttpStatus.OK);
    }
 
    @GetMapping(value = "/customers")
    public ResponseEntity<List<Customer>> findAllCustomer() {
        log.info("Fetch all customers info from the database.");
        // will fetch the customers data from the 'customer' table present in the 'customerdb'
        final List<Customer> customers = service2.findAll();
        return new ResponseEntity<>(customers, HttpStatus.OK);
    }
}

4. Run the Application

To execute the application, right-click on the Runner.java class, Run As -> Java Application.

Fig. 2: Run the Application

5. Project Demo

Open the Postman tool and hit the following URL to list the actuator endpoints exposed over HTTP.

1
2
3
4
5
6
7
// get all beers
// http get
 
// get all customers
// http get

On successfully calling these API’s the response will be returned from the different databases (i.e. the beer’s information will be returned from the beerdb and the customer’s information will be returned from the customerdb). That is all for this tutorial and I hope the article served you whatever you were looking for. Happy Learning and do not forget to share!

6. Summary

In this section, you learned,

You can download the sample application as an Eclipse project in the Downloads section.

7. Download the Eclipse Project

This was an example of actuators in the spring boot application.

Download
You can download the full source code of this example here: How to Configure Multiple Data Sources in a Spring Boot Application
Exit mobile version