1. 项目概述
在Java企业级应用开发中,多数据源连接是常见需求。当我们需要同时操作MySQL和SQL Server数据库时,Spring Boot配合MyBatisPlus提供了优雅的解决方案。我最近在一个电商后台管理系统中实际应用了这种架构,系统需要同时访问商品MySQL数据库和订单SQL Server数据库。
这种架构最大的优势在于保持了Spring Boot的简洁特性,同时通过合理配置实现了对不同类型数据库的无缝操作。下面我将分享从零开始构建多数据源系统的完整过程,包含我踩过的坑和优化经验。
2. 环境准备与基础配置
2.1 依赖引入
首先在pom.xml中添加必要依赖。除了常规的Spring Boot Starter外,关键需要这些依赖:
xml复制<!-- MyBatisPlus核心依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- MySQL连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- SQL Server连接驱动 -->
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 数据源连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
注意:SQL Server驱动建议使用最新版本,旧版本可能存在兼容性问题。我在实际项目中曾遇到TLS连接问题,升级驱动后解决。
2.2 配置文件设置
application.yml中配置双数据源。关键是要区分不同数据源的配置前缀:
yaml复制spring:
datasource:
druid:
mysql:
url: jdbc:mysql://localhost:3306/db1?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
sqlserver:
url: jdbc:sqlserver://localhost:1433;databaseName=db2
username: sa
password: yourStrongPassword
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
3. 多数据源核心实现
3.1 数据源配置类
创建两个独立的数据源配置类,分别对应MySQL和SQL Server:
java复制@Configuration
@MapperScan(basePackages = "com.example.mapper.mysql", sqlSessionTemplateRef = "mysqlSqlSessionTemplate")
public class MysqlDataSourceConfig {
@Bean(name = "mysqlDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.mysql")
public DataSource mysqlDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "mysqlSqlSessionFactory")
public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
// 配置其他MyBatis设置
return factoryBean.getObject();
}
@Bean(name = "mysqlSqlSessionTemplate")
public SqlSessionTemplate mysqlSqlSessionTemplate(
@Qualifier("mysqlSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
SQL Server配置类结构类似,主要区别在于:
- 使用不同的@MapperScan路径(如com.example.mapper.sqlserver)
- Bean名称前缀改为sqlserver
- 指向不同的配置前缀
3.2 MyBatisPlus多数据源支持
MyBatisPlus对多数据源有良好支持。关键是在Service层通过注解指定数据源:
java复制@Service
@DS("mysql") // 默认使用mysql数据源
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@DS("sqlserver") // 这个方法使用sqlserver数据源
public List<Order> getOrdersByUser(Long userId) {
return orderMapper.selectList(new LambdaQueryWrapper<Order>()
.eq(Order::getUserId, userId));
}
}
实际经验:@DS注解可以放在类或方法上,方法级注解会覆盖类级注解。我在项目中将90%的方法放在默认数据源上,只有少数跨库操作才特别标注。
4. 事务管理方案
4.1 单数据源事务
对于单个数据源的操作,直接使用@Transactional即可:
java复制@Transactional(rollbackFor = Exception.class)
public void updateUserInfo(User user) {
userMapper.updateById(user);
// 其他MySQL操作
}
4.2 跨数据源事务
真正的跨数据源分布式事务需要引入JTA等方案,但在大多数场景下,我们可以采用最终一致性方案:
java复制public void syncUserToOrderSystem(User user) {
// 先操作MySQL
userMapper.insert(user);
try {
// 再操作SQL Server
orderUserMapper.insert(convertToOrderUser(user));
} catch (Exception e) {
// 补偿机制:记录日志或发送消息到MQ
log.error("同步用户到订单系统失败", e);
throw new BusinessException("同步失败");
}
}
我在实际项目中配合消息队列实现了更健壮的最终一致性方案,核心是:
- 本地事务保证主操作完成
- 发送消息通知其他系统
- 消费端实现幂等处理
5. 性能优化实践
5.1 连接池配置优化
Druid连接池需要针对不同数据库单独配置:
yaml复制spring:
datasource:
druid:
mysql:
initial-size: 5
max-active: 20
min-idle: 5
max-wait: 60000
validation-query: SELECT 1
test-while-idle: true
sqlserver:
initial-size: 3
max-active: 15
min-idle: 3
max-wait: 30000
validation-query: SELECT 1
test-on-borrow: true
经验之谈:SQL Server的连接建立成本通常比MySQL高,max-wait可以适当设置长些。我在压力测试中发现SQL Server在并发高时容易出现连接获取超时。
5.2 MyBatisPlus二级缓存
在多数据源环境下,二级缓存需要特别处理:
java复制@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 针对MySQL的乐观锁
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
// SQL Server的配置需要单独实例化一个Interceptor
@Bean
public MybatisPlusInterceptor sqlServerMybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.SQL_SERVER));
return interceptor;
}
6. 常见问题排查
6.1 数据源切换失效
症状:@DS注解不生效,始终使用默认数据源
排查步骤:
- 检查是否在启动类加了@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
- 确认没有在方法内直接调用this.方法(会导致AOP失效)
- 检查包扫描路径是否正确
6.2 SQL Server连接问题
典型错误:驱动程序无法通过安全套接字层(SSL)加密与SQL Server建立安全连接
解决方案:
- 在连接字符串添加encrypt=true;trustServerCertificate=true
- 升级JDBC驱动到最新版
- 检查SQL Server是否启用了SSL
6.3 MyBatisPlus分页差异
MySQL和SQL Server的分页语法不同,需要特别注意:
- MySQL使用LIMIT
- SQL Server使用OFFSET-FETCH
解决方案:
- 为不同数据源配置不同的PaginationInnerInterceptor
- 在XML中编写数据库特定的分页SQL
7. 扩展思考
7.1 动态数据源方案
对于更复杂的多租户场景,可以考虑动态数据源:
java复制public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
// 使用示例
public void processTenantData(String tenantId) {
DataSourceContextHolder.setDataSourceType(tenantId);
try {
// 业务操作
} finally {
DataSourceContextHolder.clearDataSourceType();
}
}
7.2 多数据源监控
集成Druid监控界面查看各数据源状态:
java复制@Bean
public ServletRegistrationBean<StatViewServlet> druidServlet() {
ServletRegistrationBean<StatViewServlet> servletRegistrationBean =
new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
// 添加监控配置
return servletRegistrationBean;
}
访问http://localhost:8080/druid可以查看:
- 每个数据源的连接池状态
- SQL执行统计
- 慢SQL记录
在实际项目中,这种多数据源架构已经稳定运行了半年多,日均处理百万级数据库操作。最大的收获是认识到清晰的代码分层和合理的事务边界设计比技术选型更重要。对于刚接触多数据源的开发者,我的建议是从简单场景开始,逐步扩展复杂度,同时一定要做好监控和日志记录。
