Spring Boot, MyBatis Multiple Datasources Example

In this section, we will show you how we can use multiple data sources in Spring Boot, MyBatis application.


In this example we will create two data source configurations for User DB(PostgreSQL) and Student DB(MySQL). 

Technologies used:

  • Spring Boot 3.0.2
  • MyBatis 3
  • Java 17
  • Maven 
  • PostgreSQL driver and database
  • MySQL driver and database

Database setup

PostgreSQL - Create database and table.

Create a database in the PostgreSQL. 
CREATE DATABASE userdb;

Then, create table users. 
CREATE TABLE IF NOT EXISTS public.users
(
    id           serial primary key,
    email        VARCHAR(40) not null,
    first_name   VARCHAR(40) not null,
    last_name    VARCHAR(40) not null
);


MySQL - Create database and table.

Create a database in the MySQL. 
CREATE DATABASE studentdb;

Then, create table students. 
CREATE TABLE IF NOT EXISTS students(
 id INT PRIMARY KEY AUTO_INCREMENT,
 name VARCHAR (200) NOT NULL,
 email VARCHAR (200) NOT NULL,
 school_name VARCHAR (200) NOT NULL
);

Creating a simple spring boot web application:

First, open the Spring initializr https://start.spring.io/ 

Then, Provide the Group and Artifact name. We have provided Group name com.knf.dev.demo and Artifact spring-boot-mybatis-multidatasource-mysql-postgresql. Here I selected the Maven project - language Java 17 - Spring Boot 3.0.2 and add Spring web dependencyMyBatis Framework, MySQL Driver, and PostgreSQL Driver.

Then, click on the Generate button. When we click on the Generate button, it starts packing the project in a .zip(spring-boot-mybatis-multidatasource-mysql-postgresql) file and downloads the project. Then, Extract the Zip file. 
Then, import the project on your favourite IDE.

Final Project directory:


pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
    https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.knf.dev.demo</groupId>
    <artifactId>spring-boot-mybatis-multidatasource-mysql-postgresql</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-mybatis-multidatasource-mysql-postgresql</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>


application.properties

#MySQL configuration properties
spring.datasource.mysql.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.mysql.jdbc-url=jdbc:mysql://localhost:3306/studentdb
spring.datasource.mysql.username=knfuser
spring.datasource.mysql.password=root

#PostgreSQL configuration properties
spring.datasource.postgresql.driver-class-name=org.postgresql.Driver
spring.datasource.postgresql.jdbc-url=jdbc:postgresql://localhost:5432/userdb
spring.datasource.postgresql.username=postgres
spring.datasource.postgresql.password=root


Create PostgreSQLConnMapper.java

package com.knf.dev.demo.config.postgresql;

import org.springframework.stereotype.Component;
import java.lang.annotation.*;

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface PostgreSQLConnMapper {

    String value() default "";
}


Create PostgreSQLConfig.java

package com.knf.dev.demo.config.postgresql;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;

@MapperScan(value = "com.knf.dev.demo",
        annotationClass= PostgreSQLConnMapper.class,
        sqlSessionFactoryRef="PostgreSQLSessionFactory")
@Configuration
public class PostgreSQLConfig {

    @Bean(name = "PostgreSQLDataSource")
    @ConfigurationProperties(prefix="spring.datasource.postgresql")
    public DataSource SecondDataSource() {

        return DataSourceBuilder.create().build();
    }

    @Bean(name = "PostgreSQLSessionFactory")
    public SqlSessionFactory oracleSqlSessionFactory(
            @Qualifier("PostgreSQLDataSource")
             DataSource oracleDataSource) throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(oracleDataSource);
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().
                        getResources("mapper/postgresql/*.xml"));
        sqlSessionFactoryBean.setTypeAliasesPackage("com.knf.dev.demo.model.postgresql");
        return sqlSessionFactoryBean.getObject();
    }

    @Bean(name = "PostgreSQLSessionTemplate")
    public SqlSessionTemplate oracleSqlSessionTemplate(
            @Qualifier("PostgreSQLSessionFactory")
             SqlSessionFactory oracleSessionTemplate) {

        return new SqlSessionTemplate(oracleSessionTemplate);
    }


    @Bean(name = "PostgreSQLTransactionManager")
    public DataSourceTransactionManager PrimaryTransactionManager(
            @Qualifier("PostgreSQLDataSource")
              DataSource oracleDataSource) {

        return new DataSourceTransactionManager(oracleDataSource);
    }
}


Create MySQLConnMapper.java

package com.knf.dev.demo.config.mysql;

import org.springframework.stereotype.Component;
import java.lang.annotation.*;

@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MySQLConnMapper {
    String value() default "";
}


Create MySQLConfig.java

package com.knf.dev.demo.config.mysql;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;


/*
When using multiDB, set the basePackages for mapper class
file scan separately for each DB, and now it doesn't
matter because you make separate annotations.
 */
@MapperScan(value = "com.knf.dev.demo",
        annotationClass= MySQLConnMapper.class,
         sqlSessionFactoryRef="MySQLSessionFactory")
@Configuration
public class MySQLConfig {

    //When there are multiple beans of the same type,
    // give those beans a higher priority
    @Primary
    @Bean(name = "MySQLDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.mysql")
    public DataSource mysqlDataSource() {
        return DataSourceBuilder
                .create()
                .build();
    }

    @Primary
    @Bean(name = "MySQLSessionFactory")
    public SqlSessionFactory mySqlSessionFactory
            (@Qualifier("MySQLDataSource") DataSource mysqlDataSource,
               ApplicationContext applicationContex) throws Exception {

        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(mysqlDataSource);
        sqlSessionFactoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().
                        getResources("mapper/mysql/*.xml"));
        sqlSessionFactoryBean.setTypeAliasesPackage("com.knf.dev.demo.model.mysql");

        return sqlSessionFactoryBean.getObject();
    }

    @Primary
    @Bean(name = "MySQLSessionTemplate")
    public SqlSessionTemplate mySqlSessionTemplate
            (@Qualifier("MySQLSessionFactory")
              SqlSessionFactory mySqlSessionFactory) {

        return new SqlSessionTemplate(mySqlSessionFactory);
    }


    @Bean(name = "MysqlTransactionManager")
    @Primary
    public DataSourceTransactionManager PrimaryTransactionManager
            (@Qualifier("MySQLDataSource") DataSource mysqlDataSource) {

        return new DataSourceTransactionManager(mysqlDataSource);
    }
}


Create User Bean

package com.knf.dev.demo.model.postgresql;

public class User {

    private Integer id;
    private String firstName;
    private String lastName;
    private String email;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}


Create Student Bean

package com.knf.dev.demo.model.mysql;

public class Student {

    private Integer id;
    private String name;
    private String schoolName;
    private String email;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}


Create UserMapper.java

package com.knf.dev.demo.mapper.postgresql;

import com.knf.dev.demo.config.postgresql.PostgreSQLConnMapper;
import com.knf.dev.demo.model.postgresql.User;
import java.util.List;
import java.util.Optional;

@PostgreSQLConnMapper("PostgreSQLUserMapper")
public interface UserMapper {

     List<User> findAllUser();

     Optional<User> findUserById(Integer id);

     int deleteUserById(Integer id);

     int insertUser(User user);

     int updateUser(User user);
}


Create StudentMapper.java

package com.knf.dev.demo.mapper.mysql;

import com.knf.dev.demo.config.mysql.MySQLConnMapper;
import com.knf.dev.demo.model.mysql.Student;
import java.util.List;
import java.util.Optional;

@MySQLConnMapper("MysqlStudentRepository")
public interface StudentMapper {

    List<Student> findAllStudent();

    Optional<Student> findStudentById(Integer id);

    int deleteStudentById(Integer id);

    int insertStudent(Student student);

    int updateStudent(Student student);
}


Create UserMapper.xml

Mapper XML is an important file in MyBatis, which contains a set of statements to configure various SQL statements such as select, insert, update, and delete.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.knf.dev.demo.mapper.postgresql.UserMapper">
    <resultMap type="user" id="result">
        <id property="id" column="id" />
        <result property="firstName" column="first_name" />
        <result property="lastName" column="last_name" />
        <result property="email" column="email" />
    </resultMap>

    <select id="findAllUser" resultType="user" resultMap="result">
        SELECT * FROM users
    </select>

    <insert id="insertUser" parameterType="user"
            keyProperty="id" useGeneratedKeys="true">
        INSERT INTO users(first_name, last_name, email)
        VALUES(#{firstName}, #{lastName}, #{email})
    </insert>

    <update id="updateUser" parameterType="user">
        UPDATE users SET first_name = #{firstName},
        email = #{email}, last_name = #{lastName}
        WHERE id = #{id}
    </update>

    <delete id="deleteUserById" parameterType="int">
        DELETE FROM users WHERE id = #{id}
    </delete>

    <select id="findUserById" parameterType="int"
            resultType="user" resultMap="result">
        SELECT * FROM users WHERE id = #{id}
    </select>
</mapper>


Create StudentMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.knf.dev.demo.mapper.mysql.StudentMapper">
    <resultMap type="student" id="result">
        <id property="id" column="id" />
        <result property="name" column="name" />
        <result property="schoolName" column="school_name" />
        <result property="email" column="email" />
    </resultMap>

    <select id="findAllStudent" resultType="student" resultMap="result">
        SELECT * FROM students
    </select>

    <insert id="insertStudent" parameterType="student"
            keyProperty="id" useGeneratedKeys="true">
        INSERT INTO students(name, school_name, email)
        VALUES(#{name}, #{schoolName}, #{email})
    </insert>

    <update id="updateStudent" parameterType="student">
        UPDATE students SET name = #{name},
        email = #{email}, school_name = #{schoolName}
        WHERE id = #{id}
    </update>

    <delete id="deleteStudentById" parameterType="int">
        DELETE FROM students WHERE id = #{id}
    </delete>

    <select id="findStudentById" parameterType="int"
            resultType="student" resultMap="result">
        SELECT * FROM students WHERE id = #{id}
    </select>
</mapper>


-Exception handling

Create ResourceNotFoundException

package com.knf.dev.demo.exception;

public class ResourceNotFoundException extends RuntimeException{
    private static final long serialVersionUID = 1L;

    public ResourceNotFoundException(String message) {
        super(message);
    }
}


Create GlobalExceptionHandler

Spring supports exception handling by a global Exception Handler (@ExceptionHandler) with Controller Advice (@ControllerAdvice). This enables a mechanism that makes ResponseEntity work with the type safety and flexibility of @ExceptionHandler.
package com.knf.dev.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import java.time.LocalDateTime;

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<CustomErrorResponse>
        globalExceptionHandler(Exception ex, WebRequest request) {
        CustomErrorResponse errors = new CustomErrorResponse();
        errors.setTimestamp(LocalDateTime.now());
        errors.setError(ex.getMessage());
        errors.setStatus(HttpStatus.NOT_FOUND.value());
        return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND);
    }
}

Create CustomErrorResponse

package com.knf.dev.demo.exception;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;

public class CustomErrorResponse {

    @JsonFormat(shape = JsonFormat.Shape.STRING,
                       pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime timestamp;
    private int status;
    private String error;
    public LocalDateTime getTimestamp()
    {
        return timestamp;
    }
    public void setTimestamp(LocalDateTime timestamp)
    {
        this.timestamp = timestamp;
    }
    public int getStatus()
    {
        return status;
    }
    public void setStatus(int status)
    {
        this.status = status;
    }
    public String getError()
    {
        return error;
    }
    public void setError(String error)
    {
        this.error = error;
    }
}


Create User Controller

package com.knf.dev.demo.controller.postgresql;

import com.knf.dev.demo.exception.ResourceNotFoundException;
import com.knf.dev.demo.mapper.postgresql.UserMapper;
import com.knf.dev.demo.model.postgresql.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/v1")
public class UserController {
    @Autowired
    private UserMapper userMapper;

    // get all users
    @GetMapping("/users")
    public List<User> getAllUsers()
    {
        return userMapper.findAllUser();
    }

    // create user rest API
    @PostMapping("/users")
    public Map<String, Boolean> createUser(@RequestBody User user)  {

        Map<String, Boolean> response = new HashMap<>();

        Boolean bool = userMapper.insertUser(user) > 0 ?
                response.put("created", Boolean.TRUE) :
                response.put("created", Boolean.FALSE);

        return response;

    }

    // get user by id rest api
    @GetMapping("/users/{id}")
    public User findUserById(@PathVariable Integer id) {

        User user = userMapper.findUserById(id).
                orElseThrow(() -> new ResourceNotFoundException
                ("User not exist with id :" + id));
        return user;
    }

   // update user rest api
   @PutMapping("/users/{id}")
   public Map<String, Boolean> updateUser(@PathVariable Integer id,
                                          @RequestBody User userDetails) {

      User user = userMapper.findUserById(id)
              .orElseThrow(() -> new ResourceNotFoundException
               ("User not exist with id :" + id));
       userDetails.setId(id);
       Map<String, Boolean> response = new HashMap<>();

       Boolean bool = userMapper.updateUser(userDetails) > 0 ?
               response.put("updated", Boolean.TRUE) :
               response.put("updated", Boolean.FALSE);

      return response;
    }

    // delete user rest api
    @DeleteMapping("/users/{id}")
    public Map<String, Boolean> deleteUser
               (@PathVariable Integer id) {

        User user = userMapper.findUserById(id)
                .orElseThrow(() -> new ResourceNotFoundException
                        ("User not exist with id :" + id));

        Map<String, Boolean> response = new HashMap<>();

        Boolean bool = userMapper.deleteUserById(user.getId()) > 0 ?
                response.put("deleted", Boolean.TRUE) :
                response.put("deleted", Boolean.FALSE);
        return response;
    }
}


Create Student Controller

package com.knf.dev.demo.controller.mysql;

import com.knf.dev.demo.exception.ResourceNotFoundException;
import com.knf.dev.demo.mapper.mysql.StudentMapper;
import com.knf.dev.demo.model.mysql.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

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

@RestController
@RequestMapping("/api/v1")
public class StudentController {

    @Autowired
    private StudentMapper studentMapper;

    // get all student
    @GetMapping("/students")
    public List<Student> getAllStudent()
    {
        return studentMapper.findAllStudent();
    }
    // create student rest API
    @PostMapping("/students")
    public Map<String, Boolean> createStudent(@RequestBody Student student)  {

        Map<String, Boolean> response = new HashMap<>();

        Boolean bool = studentMapper.insertStudent(student) > 0 ?
                response.put("created", Boolean.TRUE) :
                response.put("created", Boolean.FALSE);

        return response;

    }

    // get student by id rest api
    @GetMapping("/students/{id}")
    public Student findStudentById(@PathVariable Integer id) {

        Student student = studentMapper.findStudentById(id).
                orElseThrow(() -> new ResourceNotFoundException
                        ("Student not exist with id :" + id));
        return student;
    }

    // update student rest api
    @PutMapping("/students/{id}")
    public Map<String, Boolean> updateStudent(@PathVariable Integer id,
                                           @RequestBody Student studentDetails) {

        Student student = studentMapper.findStudentById(id)
                .orElseThrow(() -> new ResourceNotFoundException
                        ("Student not exist with id :" + id));
        studentDetails.setId(id);
        Map<String, Boolean> response = new HashMap<>();

        Boolean bool = studentMapper.updateStudent(studentDetails) > 0 ?
                response.put("updated", Boolean.TRUE) :
                response.put("updated", Boolean.FALSE);

        return response;
    }

    // delete student rest api
    @DeleteMapping("/students/{id}")
    public Map<String, Boolean> deleteStudent
         (@PathVariable Integer id) {

        Student student = studentMapper.findStudentById(id)
                .orElseThrow(() -> new ResourceNotFoundException
                        ("Student not exist with id :" + id));

        Map<String, Boolean> response = new HashMap<>();

        Boolean bool = studentMapper.deleteStudentById(student.getId()) > 0 ?
                response.put("deleted", Boolean.TRUE) :
                response.put("deleted", Boolean.FALSE);
        return response;
    }
}


Run the application - Application.java

package com.knf.dev.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}
Application is the entry point that sets up the Spring Boot application. The @SpringBootApplication annotation enables auto-configuration and component scanning. 

Step1: Download or clone the source code from GitHub to a local machine - Click here


Step 2: mvn clean install


Step 3: Run the Spring Boot application - mvn spring-boot:run


OR 


Run this Spring boot application from 

  • IntelliJ IDEA IDE by right click - Run 'Application.main()' 
  • Eclipse/STS - You can right click the project or the Application.java file and run as java application or Spring boot application.


Verify User APIs

Add User:


Update User:


Fetch all User:

Get User by Id:

Delete User by Id:

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Spring Boot + Mockito simple application with 100% code coverage

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete