本文目录导读:

- 核心思路
- 方案一:基于 Spring Boot + MyBatis 的静态多数据源
- 方案二:动态数据源路由 (ThreadLocal + AbstractRoutingDataSource)
- 方案三:使用 ShardingSphere-JDBC (推荐生产环境)
- 总结与建议
在Java企业级开发(尤其是Spring Boot项目中)配置多数据源是一个常见需求,用于读写分离、连接不同业务数据库或实现数据迁移。
下面我将介绍两种最主流的配置方式:静态多数据源(基于Spring Boot + JPA/MyBatis)和动态数据源路由(基于AbstractRoutingDataSource)。
核心思路
无论采用哪种方式,都离不开以下两个步骤:
- 定义数据源:在配置文件中声明多个
DataSourceBean。 - 管理数据源:使用
@Primary指定主数据源,并通过AbstractRoutingDataSource实现动态切换,或者在Mapper/Repository层显式指定。
基于 Spring Boot + MyBatis 的静态多数据源
这种方式最常用,通过包扫描路径或注解来区分操作哪个数据库。
配置文件 (application.yml)
spring:
datasource:
# 主数据源 (master)
master:
url: jdbc:mysql://localhost:3306/db_master?useSSL=false
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
# 从数据源 (slave)
slave:
url: jdbc:mysql://localhost:3306/db_slave?useSSL=false
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
数据源配置类
我们需要创建两个DataSource Bean,并指定@Primary。
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 javax.sql.DataSource;
@Configuration
public class DataSourceConfig {
// 主数据源
@Primary // 标记为主数据源
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
// 从数据源
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
}
配置 MyBatis 的 SqlSessionFactory
为每个数据源创建独立的SqlSessionFactory,并为它们指定不同的包扫描路径。
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
@Configuration
public class MyBatisConfig {
// 主数据源的 SqlSessionFactory
@Bean(name = "masterSqlSessionFactory")
@Qualifier("masterSqlSessionFactory")
public SqlSessionFactory masterSqlSessionFactory(
@Qualifier("masterDataSource") DataSource masterDataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(masterDataSource);
// 指定主库的 Mapper XML 文件路径 (不同路径)
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/master/*.xml"));
return bean.getObject();
}
// 从数据源的 SqlSessionFactory
@Bean(name = "slaveSqlSessionFactory")
public SqlSessionFactory slaveSqlSessionFactory(
@Qualifier("slaveDataSource") DataSource slaveDataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(slaveDataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/slave/*.xml"));
return bean.getObject();
}
}
为 Mapper 扫描指定数据源
// 主库 Mapper 扫描
@MapperScan(
basePackages = "com.example.mapper.master",
sqlSessionFactoryRef = "masterSqlSessionFactory"
)
public class MasterMapperConfig {
}
// 从库 Mapper 扫描
@MapperScan(
basePackages = "com.example.mapper.slave",
sqlSessionFactoryRef = "slaveSqlSessionFactory"
)
public class SlaveMapperConfig {
}
注意:这种方式下,你的代码结构会自然地分为
mapper/master和mapper/slave两个包,调用者只需要调用对应包的 Mapper 接口,即可自动使用对应的数据源。
动态数据源路由 (ThreadLocal + AbstractRoutingDataSource)
当需要在同一次请求内、根据业务逻辑动态切换数据源时(根据用户ID路由到不同的分库),方案一就不够灵活了,此时可以使用AbstractRoutingDataSource。
创建数据源路由上下文
public class DataSourceContextHolder {
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
// 设置当前数据源
public static void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource);
}
// 获取当前数据源
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
// 清除当前数据源
public static void clear() {
CONTEXT_HOLDER.remove();
}
}
实现 AbstractRoutingDataSource
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 返回当前线程使用的数据源名称
return DataSourceContextHolder.getDataSource();
}
}
配置动态数据源
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 javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DynamicDataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary // 必须标记为主 Bean
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 配置所有数据源
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slave", slaveDataSource());
// 设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
dynamicDataSource.setTargetDataSources(targetDataSources);
return dynamicDataSource;
}
}
使用 AOP + 注解 实现无缝切换
为了方便,我们可以定义一个注解@DataSource,通过AOP自动设置上下文。
@DataSource 注解
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "master"; // 默认主库
}
AOP切面实现
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
@Order(1) // 确保在事务之前执行
public class DataSourceAspect {
@Pointcut("@annotation(com.example.annotation.DataSource)")
public void dataSourcePointcut() {}
@Around("dataSourcePointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
if (dataSource == null) {
dataSource = point.getTarget().getClass().getAnnotation(DataSource.class);
}
// 设置数据源
if (dataSource != null) {
DataSourceContextHolder.setDataSource(dataSource.value());
}
try {
return point.proceed();
} finally {
// 清除数据源,避免内存泄漏
DataSourceContextHolder.clear();
}
}
}
使用示例
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@DataSource("master") // 强制走主库
public User getUserById(Long id) {
return userMapper.selectById(id);
}
@DataSource("slave") // 强制走从库
public List<User> listUsers() {
return userMapper.selectAll();
}
}
使用 ShardingSphere-JDBC (推荐生产环境)
如果项目是多数据源 + 分库分表 + 读写分离的复杂场景,不建议自己实现AbstractRoutingDataSource,而是直接使用成熟的第三方框架。
ShardingSphere-JDBC 配置示例:
spring:
shardingsphere:
datasource:
names: master,slave
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db_master
username: root
password: root123
slave:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/db_slave
username: root
password: root123
rules:
readwrite-splitting:
data-sources:
myds:
type: Static
props:
write-data-source-name: master
read-data-source-names: slave
这种方式完全屏蔽了底层细节,开发者只需像操作单数据源一样编写SQL,ShardingSphere会自动完成路由。
总结与建议
| 特性 | 静态多数据源 | 动态数据源路由 | ShardingSphere |
|---|---|---|---|
| 实现复杂度 | 低 (适合结构固定) | 中 (适合简单动态切换) | 高 (但功能强大) |
| 灵活性 | 差 (需分包) | 高 (任意方法切换) | 极高 |
| 事务一致性 | 不支持跨库事务 | 不支持跨库事务 | 支持分布式事务 (需配置) |
| 推荐场景 | 代码隔离清晰、结构固定 | 需要方法级别动态切换 | 复杂分库、读写分离、分表 |
我的建议:
- 如果你的团队希望代码结构清晰、数据源之间业务独立,首选方案一。
- 如果你需要根据参数动态选择数据库(比如租户隔离),且不想引入第三方框架,使用方案二。
- 如果你项目本身就涉及到分库分表、数据加密、读写分离等复杂需求,直接上 ShardingSphere,避免重复造轮子。