Spring Boot + 多数据源 + Druid:监控页面 + 控制台 SQL 日志的完整实践
大家好,我是服务端技术精选的作者。今天咱们聊聊一个在企业级开发中非常常见的需求:多数据源管理。
多数据源的挑战
在我们的日常开发工作中,经常会遇到这样的场景:
- 需要连接多个数据库,可能是不同的业务系统
- 要实现读写分离,提升数据库性能
- 需要连接不同类型的数据库(MySQL、Oracle、PostgreSQL等)
- 对数据库连接进行统一监控和管理
传统的单数据源配置显然无法满足这些复杂需求。今天我们就来聊聊如何用Spring Boot + Druid构建一个功能完善的多数据源管理系统。
解决方案思路
今天我们要解决的,就是如何构建一个多数据源管理平台,包含监控页面和SQL日志功能。
核心思路是:
- 多数据源配置:动态切换不同数据源
- Druid监控:提供数据库连接池监控
- SQL日志输出:记录详细的SQL执行信息
- 统一管理界面:集中查看和管理
多数据源配置实现
1. 数据源配置
首先,我们需要配置多个数据源:
spring:
datasource:
# 主数据源
primary:
url: jdbc:mysql://localhost:3306/primary_db?useUnicode=true&characterEncoding=utf8
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
# 从数据源
secondary:
url: jdbc:mysql://localhost:3306/secondary_db?useUnicode=true&characterEncoding=utf8
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
# Druid连接池配置
druid:
initial-size: 5
min-idle: 5
max-active: 20
max-wait: 60000
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
filters: stat,wall,log4j
2. 动态数据源路由
实现数据源的动态切换:
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<>();
@Override
protected Object determineCurrentLookupKey() {
return dataSourceHolder.get();
}
public static void setDataSource(String dataSource) {
dataSourceHolder.set(dataSource);
}
public static void clearDataSource() {
dataSourceHolder.remove();
}
}
3. 数据源配置类
@Configuration
@Primary
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("primary", primaryDataSource());
dataSourceMap.put("secondary", secondaryDataSource());
dynamicDataSource.setTargetDataSources(dataSourceMap);
dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());
return dynamicDataSource;
}
}
Druid监控配置
1. 监控配置
@Configuration
public class DruidConfig {
@Bean
public ServletRegistrationBean<StatViewServlet> druidStatViewServlet() {
ServletRegistrationBean<StatViewServlet> registrationBean =
new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
// IP白名单
registrationBean.addInitParameter("allow", "127.0.0.1");
// IP黑名单
registrationBean.addInitParameter("deny", "");
// 登录用户名密码
registrationBean.addInitParameter("loginUsername", "admin");
registrationBean.addInitParameter("loginPassword", "password");
// 是否可以重置数据
registrationBean.addInitParameter("resetEnable", "false");
return registrationBean;
}
@Bean
public FilterRegistrationBean<WebStatFilter> druidStatFilter() {
FilterRegistrationBean<WebStatFilter> filterRegistrationBean =
new FilterRegistrationBean<>(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
2. SQL拦截器配置
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dynamicDataSource());
// 添加SQL拦截器
Interceptor[] interceptors = new Interceptor[]{
new PerformanceInterceptor(),
new PaginationInnerInterceptor(DbType.MYSQL)
};
factoryBean.setPlugins(interceptors);
return factoryBean.getObject();
}
}
SQL日志输出
1. 配置SQL日志
logging:
level:
# 显示SQL
com.baomidou.mybatisplus.core: debug
# 显示SQL参数
com.alibaba.druid: debug
# 显示具体Mapper的SQL
com.example.mapper: debug
mybatis-plus:
configuration:
# 开启SQL日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 打印参数
log-params: true
2. 自定义SQL拦截器
@Component
@Intercepts({
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
public class SqlLogInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(SqlLogInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = MetaObject.forObject(statementHandler,
SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
String sql = statementHandler.getBoundSql().getSql();
Object parameterObject = statementHandler.getBoundSql().getParameterObject();
long startTime = System.currentTimeMillis();
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
logger.info("SQL执行耗时: {} ms, SQL: {}, 参数: {}",
endTime - startTime, sql.trim(), parameterObject);
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {}
}
数据源切换注解
1. 自定义注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {
String value() default "primary";
}
2. AOP切面实现
@Aspect
@Component
public class DataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
@Around("@annotation(targetDataSource)")
public Object around(ProceedingJoinPoint point, TargetDataSource targetDataSource) throws Throwable {
String dataSource = targetDataSource.value();
logger.info("切换到数据源: {}", dataSource);
DynamicDataSource.setDataSource(dataSource);
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
logger.info("数据源已还原");
}
}
}
实际应用示例
1. Service层使用
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@TargetDataSource("primary")
public List<User> getUsersFromPrimary() {
return userMapper.selectAll();
}
@TargetDataSource("secondary")
public List<User> getUsersFromSecondary() {
return userMapper.selectAll();
}
}
2. 监控页面访问
启动应用后,可以通过以下地址访问Druid监控页面:
- http://localhost:8080/druid/index.html
- 可以查看SQL执行统计、慢查询、连接池状态等信息
性能优化建议
1. 连接池参数调优
druid:
# 初始化连接数
initial-size: 10
# 最小空闲连接数
min-idle: 10
# 最大连接数
max-active: 50
# 获取连接等待超时时间
max-wait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接
time-between-eviction-runs-millis: 60000
# 配置一个连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
# 验证连接有效性
validation-query: SELECT 1
test-while-idle: true
test-on-borrow: false
test-on-return: false
2. SQL性能监控
通过Druid的SQL监控功能,可以:
- 识别慢查询SQL
- 分析SQL执行频率
- 监控数据库连接使用情况
- 发现潜在的性能问题
注意事项
在使用多数据源时,需要注意以下几点:
- 事务管理:跨数据源事务需要使用分布式事务
- 连接泄漏:确保及时释放数据库连接
- 配置管理:敏感信息如密码应加密存储
- 监控告警:设置数据库连接池的告警阈值
总结
通过Spring Boot + 多数据源 + Druid的组合,我们可以构建一个功能完善、易于监控的数据库访问系统。这种架构不仅解决了多数据源访问的问题,还提供了强大的监控和日志功能,大大提升了开发和运维效率。
希望这篇文章对你有所帮助!如果你觉得有用,欢迎关注【服务端技术精选】公众号,获取更多后端技术干货。
原文首发于 www.jiangyi.space
转载请注明出处
标题:Spring Boot + 多数据源 + Druid:监控页面 + 控制台 SQL 日志的完整实践
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/01/17/1768638410238.html
0 评论