SpringBoot Multiple Data Source Configuration for Basic Read‑Write Separation

This guide explains how to configure multiple data sources in SpringBoot to achieve basic read‑write separation, covering applicable scenarios, step‑by‑step setup of master and slave DataSources, SqlSessionFactory and SqlSessionTemplate beans, package‑based routing, transaction considerations, common pitfalls, and best‑practice recommendations.

Java Tech Workshop
Java Tech Workshop
Java Tech Workshop
SpringBoot Multiple Data Source Configuration for Basic Read‑Write Separation

Applicable Scenarios

Master‑write, slave‑read (basic read/write separation)

Projects that need to connect to multiple business databases

Micro‑service internal multi‑database access

Simple sharding without table splitting

Overall Approach

Disable SpringBoot's default DataSource auto‑configuration.

Manually create one or more DataSource beans.

For each DataSource, configure an independent SqlSessionFactory and SqlSessionTemplate.

Switch DataSource by package or annotation.

Note: Cross‑DataSource transactions are not atomic and require extra handling.

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>2.3.2</version>
</dependency>
<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <optional>true</optional>
</dependency>

application.yml – Dual DataSource Configuration

spring:
  # Disable auto‑configuration of the default DataSource
  autoconfigure:
    exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

  # Master (write) datasource
  datasource:
    master:
      url: jdbc:mysql://localhost:3306/testdb_master?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver

    # Slave (read) datasource
    slave:
      url: jdbc:mysql://localhost:3306/testdb_slave?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456
      driver-class-name: com.mysql.cj.jdbc.Driver

  mybatis:
    configuration:
      map-underscore-to-camel-case: true

Manual Configuration of Master DataSource

package com.demo.config;

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.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.demo.mapper.master", sqlSessionTemplateRef = "masterSqlSessionTemplate")
public class DataSourceMasterConfig {

    @Bean
    @ConfigurationProperties(prefix = "datasource.master")
    @Primary
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @Primary
    public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/master/*.xml"));
        return bean.getObject();
    }

    @Bean
    @Primary
    public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

Manual Configuration of Slave DataSource

package com.demo.config;

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 javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "com.demo.mapper.slave", sqlSessionTemplateRef = "slaveSqlSessionTemplate")
public class DataSourceSlaveConfig {

    @Bean
    @ConfigurationProperties(prefix = "datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                .getResources("classpath:mapper/slave/*.xml"));
        return bean.getObject();
    }

    @Bean
    public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

Project Structure (Package‑Based DataSource Separation)

com.demo
├── mapper
│   ├── master        # Master DB: write operations
│   │   └── UserMasterMapper.java
│   └── slave         # Slave DB: read operations
│       └── UserSlaveMapper.java
├── service
│   ├── MasterService
│   └── SlaveService
└── config
    ├── DataSourceMasterConfig.java
    └── DataSourceSlaveConfig.java

Corresponding XML mapper files should be placed under resources/mapper/master/*.xml and resources/mapper/slave/*.xml so that each package’s mappers automatically use the correct DataSource.

Service Layer Usage Example

@Service
public class UserService {

    @Autowired
    private UserMasterMapper userMasterMapper; // write (master)

    @Autowired
    private UserSlaveMapper userSlaveMapper;   // read (slave)

    // Write operation – uses master
    @Transactional
    public int addUser(User user) {
        return userMasterMapper.insert(user);
    }

    // Read operation – uses slave
    public User getUserById(Long id) {
        return userSlaveMapper.selectById(id);
    }
}

Transaction Notes

Transactions work normally within a single DataSource using @Transactional.

Cross‑DataSource transactions are not guaranteed to be atomic; additional solutions are required.

Production‑grade read/write separation typically relies on frameworks such as Sharding‑JDBC, MyCat, or dynamic-datasource-spring-boot-starter.

Common Pitfalls

Failing to exclude DataSourceAutoConfiguration leads to startup errors.

Omitting @Primary on the master bean causes "no primary DataSource" errors.

Incorrect mapper package or XML path results in binding failures.

Inconsistent table structures across databases cause query exceptions.

Assuming automatic support for distributed transactions leads to serious bugs.

Summary

SpringBoot multiple DataSource configuration boils down to three essential steps:

Disable the default auto‑configuration.

Manually register each DataSource.

Separate SqlSessionFactory (and optionally SqlSessionTemplate) by package or annotation.

Mastering this setup clarifies the underlying principle of read/write separation, making the adoption of advanced frameworks like Sharding‑JDBC or dynamic-datasource much easier.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaMyBatisRead Write SeparationSpringBootTransaction ManagementMultiple DataSource
Java Tech Workshop
Written by

Java Tech Workshop

Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.