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.
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: trueManual 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.javaCorresponding 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
