随着项目变大,单数据扛不住压力很常见的事,这时候一般会先做读写分离 :主库、从库读,减轻单库压力。而实现读写分离的第一,就是先学会在 SringBoot 里配置多数据源。
一、适用场景
-
• 主库写入、从库读取(读写分离基础版)
-
• 一个项目连接多个不同业务库
-
• 微服务内部多库访问
-
• 简单分库、不分表的场景
二、整体思路
-
- 关闭 SpringBoot 默认数据源自动配置
-
- 手动构建两个(或多个)
DataSource
- 手动构建两个(或多个)
-
- 为每个数据源配置独立的
SqlSessionFactory、SqlSessionTemplate
- 为每个数据源配置独立的
-
- 按包或按注解切换数据源
-
- 事务注意:多数据源下跨库事务需要额外处理
三、引入依赖
和单数据源 MyBatis 一致:
go
<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 配置双数据源
这里配置一个主库(write)、一个从库(read):
go
spring:
# 关闭自动配置数据源
autoconfigure:
exclude: org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
# 主库(写)
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:
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 通用配置
mybatis:
configuration:
map-underscore-to-camel-case: true
五、手动配置主数据源(Master)
新建配置类:DataSourceMasterConfig
go
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
// 主库 Mapper 接口包路径
@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);
// XML 路径
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);
}
}
六、手动配置从数据源(Slave)
新建配置类:DataSourceSlaveConfig
go
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
// 从库 Mapper 接口包路径
@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(sqlSessionTemplate);
}
}
七、项目结构划分(按包区分数据源)
go
com.demo
├── mapper
│ ├── master # 主库:写操作
│ │ └── UserMasterMapper.java
│ └── slave # 从库:读操作
│ └── UserSlaveMapper.java
├── service
│ ├── MasterService
│ └── SlaveService
└── config
├── DataSourceMasterConfig.java
└── DataSourceSlaveConfig.java
resources/mapper 下也要对应:
go
resources/mapper/master/*.xml
resources/mapper/slave/*.xml
这样不同包下的 Mapper 会自动走不同数据源,实现写走主库、读走从库。
八、Service 层使用示例
go
@Service
public class UserService {
@Autowired
private UserMasterMapper userMasterMapper; // 主库:写
@Autowired
private UserSlaveMapper userSlaveMapper; // 从库:读
// 写操作:主库
@Transactional
public int addUser(User user) {
return userMasterMapper.insert(user);
}
// 读操作:从库
public User getUserById(Long id) {
return userSlaveMapper.selectById(id);
}
}
九、多数据源事务说明
-
• 单个数据源内事务正常使用
@Transactional -
• 跨多个数据源的事务不保证原子性
-
• 真正生产级读写分离一般用:
-
-
• Sharding-JDBC
-
• MyCat
-
• dynamic-datasource-spring-boot-starter
-
本文这种手动配置方式,适合学习原理、简单多库场景,真正高可用建议用成熟组件。
十、注意事项
-
- 没关闭
DataSourceAutoConfiguration→ 启动报错
- 没关闭
-
@Primary忘记加 → 找不到首选数据源
-
- Mapper 包路径、XML 路径写错 → 绑定失败
-
- 多个库表结构不一致 → 查询报错
-
- 误以为自动支持分布式事务 → 踩大坑
十一、总结
SpringBoot 多数据源核心就三步:
-
- 关闭自动配置
-
- 手动注册多个 DataSource
-
- 按包/按注解区分 SqlSessionFactory
掌握这一套,你就理解了读写分离的底层原理,再去用 Sharding-JDBC、dynamic-datasource 这类框架会非常轻松。