ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot多数据源配置实战:静态与动态方案详解

SpringBoot多数据源配置实战:静态与动态方案详解 1. 项目概述为什么我们需要多数据源在实际的企业级应用开发中尤其是面对微服务架构、数据中台或遗留系统整合的场景一个SpringBoot应用只连接一个数据库的情况越来越少见。我遇到过不少项目业务数据存在MySQL日志和监控数据要写入Elasticsearch用户行为数据又要同步到ClickHouse做实时分析甚至还需要连接一个老旧的Oracle系统来读取历史订单。这时候单一数据源DataSource的配置就完全不够用了。“SpringBoot实现多数据源的两种方式”这个标题背后直指的就是这个核心痛点如何在同一个SpringBoot应用中优雅、清晰且可维护地管理多个数据库连接。这不仅仅是配置两个DataSourceBean那么简单它涉及到事务管理、MyBatis或JPA的会话工厂绑定、动态路由、以及如何避免在复杂的业务代码中把数据源切换逻辑写得一团糟。根据我的经验主流的实现方式可以归纳为两种核心思路一种是静态多数据源在应用启动时就明确配置好所有数据源通过不同的DAO层或Service层来隔离使用另一种是更灵活的动态多数据源可以根据运行时条件如请求头、方法注解、分库分表键动态切换到目标数据源。前者结构清晰适合数据源相对固定、业务模块划分明确的场景后者灵活性高是应对读写分离、多租户等复杂架构的利器。接下来我将结合具体代码和踩坑经验为你详细拆解这两种方式的实现路径、核心细节以及如何避开那些新手最容易掉进去的“坑”。2. 核心思路与方案选型静态配置 vs. 动态路由在动手写代码之前我们必须先想清楚业务场景这直接决定了该选择哪种方案。选型错误后期重构的成本会非常高。2.1 静态多数据源模块化隔离的经典模式静态多数据源我更喜欢称之为“物理隔离”模式。它的核心思想是为每一个数据源创建一套独立的、完整的持久层基础设施。这包括独立的DataSource、TransactionManager、SqlSessionFactory如果使用MyBatis或EntityManagerFactory如果使用JPA。然后通过Spring的包扫描机制将不同的Mapper接口或Repository接口划分到不同的包路径下分别绑定到对应的持久层设施上。这种模式的优势非常明显清晰直观代码结构上就能看出哪个模块用哪个库可读性强。事务管理简单每个数据源有自己的事务管理器事务边界清晰不易出错。适合垂直拆分当你的应用由几个相对独立的业务模块组成每个模块使用独立的数据库时这种模式是天然匹配的。但它也有局限性灵活性差数据源在启动时固定运行时无法根据条件切换。配置繁琐每增加一个数据源就需要完整复制一套配置。不适合水平拆分比如读写分离、按租户分库这种模式就力不从心了。2.2 动态多数据源运行时切换的敏捷之道动态多数据源的核心在于“路由”。我们通常会定义一个AbstractRoutingDataSource的子类它本身并不直接管理数据库连接而是作为一个“路由中介”根据当前线程上下文ThreadLocal中设定的一个“查找键”lookup key动态地将数据操作请求委派给背后真正的、预先配置好的多个目标DataSource之一。这种模式的优势在于其强大的灵活性运行时决策可以在Service方法中甚至通过AOP切面根据业务逻辑动态决定使用哪个数据源。实现读写分离通过注解如DS(“slave”)轻松标记哪些方法读从库哪些方法写主库。支持多租户根据当前登录用户的租户ID路由到其专属的数据库实例。配置相对集中所有数据源定义在一个地方由路由数据源统一管理。当然它的复杂度也更高事务管理是难点跨数据源的事务即分布式事务需要引入额外的解决方案如Seata而AbstractRoutingDataSource本身不处理这个。在同一事务方法内切换数据源如果不做特殊处理可能会导致连接错乱或事务失效。上下文管理必须精心设计和管理用于存储“查找键”的线程上下文确保在异步、多线程环境下不会出现数据源污染。对框架理解要求更深需要深入理解Spring事务管理和数据源抽象的机制。如何选择如果你的业务是模块A用库1模块B用库2彼此泾渭分明几乎没有交叉访问选静态多数据源。如果你的业务需要同一个Service方法在不同场景下访问不同库如同一个查询根据用户类型查不同库或者要实现读写分离、多租户那么动态多数据源是更合适的选择。3. 静态多数据源配置实战与避坑指南我们先从相对简单的静态模式开始。假设我们有两个MySQL数据库primary_db主业务库和report_db报表库。3.1 依赖准备与基础配置首先确保你的pom.xml包含了必要的依赖。这里以MyBatis Plus为例它简化了很多配置。dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.7/version !-- 请使用最新稳定版 -- /dependency /dependencies接下来在application.yml中配置两个数据源的连接信息。这里有一个关键点Spring Boot默认的自动配置spring.datasource.*只会帮我们创建一个数据源。为了配置多个我们需要禁用默认的数据源自动配置或者使用自定义的前缀。spring: autoconfigure: exclude: - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration # 可选手动控制配置 # 数据源一主库 datasource: primary: jdbc-url: jdbc:mysql://localhost:3306/primary_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver # 数据源二报表库 report: jdbc-url: jdbc:mysql://localhost:3307/report_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: report_user password: report_pass driver-class-name: com.mysql.cj.jdbc.Driver注意在Spring Boot 2.x及以上版本url属性已更名为jdbc-url。如果你在配置时遇到经典的Failed to configure a DataSource: ‘url‘ attribute is not specified and no embedded datasource could be configured.错误请检查属性名是否正确或者确认是否错误地引入了spring-boot-starter-data-jpa等依赖导致自动配置冲突。3.2 主数据源Primary配置类我们需要为每个数据源编写一个Java配置类。首先配置主数据源。Configuration // 指定Mapper接口的扫描路径并关联到对应的SqlSessionFactory MapperScan(basePackages com.example.mapper.primary, sqlSessionFactoryRef primarySqlSessionFactory) public class PrimaryDataSourceConfig { Primary // 这个注解至关重要指定默认的数据源 Bean(name primaryDataSource) ConfigurationProperties(prefix spring.datasource.primary) public DataSource primaryDataSource() { // 使用HikariCP连接池Spring Boot默认集成 return DataSourceBuilder.create().type(HikariDataSource.class).build(); } Primary Bean(name primaryTransactionManager) public DataSourceTransactionManager primaryTransactionManager(Qualifier(primaryDataSource) DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } Primary Bean(name primarySqlSessionFactory) public SqlSessionFactory primarySqlSessionFactory(Qualifier(primaryDataSource) DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean sessionFactory new MybatisSqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); // 如果有自定义的MyBatis配置或Mapper XML位置在这里设置 // sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(classpath:mapper/primary/*.xml)); // sessionFactory.setConfigLocation(new ClassPathResource(mybatis-config.xml)); return sessionFactory.getObject(); } }关键点解析Primary注解当Spring容器中存在多个同类型Bean这里是DataSource,TransactionManager时Primary标记的Bean会被优先注入。我们的主业务库通常就是默认数据源所以必须加上。MapperScan注解sqlSessionFactoryRef属性明确指定了这个扫描包下的所有Mapper接口都使用primarySqlSessionFactory这个Bean来创建代理对象。这是实现Mapper与数据源绑定的核心。ConfigurationProperties优雅地将application.yml中spring.datasource.primary下的属性映射到DataSource对象上。明确指定Bean名称在Bean注解中定义好名字在通过Qualifier注入时就不会混淆。3.3 报表数据源Report配置类报表数据源的配置类结构与主数据源类似但绝对不能使用Primary注解。Configuration MapperScan(basePackages com.example.mapper.report, sqlSessionFactoryRef reportSqlSessionFactory) public class ReportDataSourceConfig { Bean(name reportDataSource) ConfigurationProperties(prefix spring.datasource.report) public DataSource reportDataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); } Bean(name reportTransactionManager) public DataSourceTransactionManager reportTransactionManager(Qualifier(reportDataSource) DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } Bean(name reportSqlSessionFactory) public SqlSessionFactory reportSqlSessionFactory(Qualifier(reportDataSource) DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean sessionFactory new MybatisSqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); // 可以单独设置报表库的Mapper XML路径 // sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(classpath:mapper/report/*.xml)); return sessionFactory.getObject(); } }3.4 业务层使用与事务控制配置完成后使用就非常直观了。你只需要将不同模块的Mapper接口放到对应的包下即可。// 主业务Mapper位于 com.example.mapper.primary 包下 Repository public interface UserMapper { User selectById(Long id); } // 报表业务Mapper位于 com.example.mapper.report 包下 Repository public interface SalesReportMapper { ListSalesData getDailyReport(Date date); }在Service层你可以注入对应的Mapper。Spring会根据Mapper接口所在的包自动使用正确的SqlSessionFactory和DataSource。Service public class BusinessService { Autowired private UserMapper userMapper; // 自动使用primaryDataSource Autowired private SalesReportMapper salesReportMapper; // 自动使用reportDataSource Transactional(transactionManager primaryTransactionManager) // 明确指定事务管理器 public void updateUserAndLog(User user) { userMapper.updateById(user); // 这里如果调用 reportDataSource 的事务方法需要新开一个事务或使用分布式事务 } }实操心得事务管理器指定在静态多数据源模式下使用Transactional注解时最好通过transactionManager属性显式指定使用哪个事务管理器。虽然Spring在只有一个TransactionManager时会自动使用它但在多数据源环境下不指定可能会导致事务无法正确回滚。这是一个非常隐蔽的坑。4. 动态多数据源配置实战基于AbstractRoutingDataSource当业务需要动态切换时静态配置就显得捉襟见肘了。下面我们实现一个基于AbstractRoutingDataSource和自定义注解的动态数据源方案。4.1 定义数据源上下文与路由键首先我们需要一个工具类来管理当前线程应该使用哪个数据源。这里使用ThreadLocal来保证线程安全。public class DynamicDataSourceContextHolder { /** * 使用ThreadLocal维护变量每个线程拥有独立的变量副本。 */ private static final ThreadLocalString CONTEXT_HOLDER new ThreadLocal(); /** * 设置数据源路由键 */ public static void setDataSourceKey(String key) { CONTEXT_HOLDER.set(key); } /** * 获取当前数据源路由键 */ public static String getDataSourceKey() { return CONTEXT_HOLDER.get(); } /** * 清空数据源路由键 */ public static void clearDataSourceKey() { CONTEXT_HOLDER.remove(); } }4.2 实现动态数据源路由类核心路由逻辑继承自AbstractRoutingDataSource。public class DynamicRoutingDataSource extends AbstractRoutingDataSource { Override protected Object determineCurrentLookupKey() { // 这个方法会在每次数据库操作前被调用用于决定使用哪个数据源 return DynamicDataSourceContextHolder.getDataSourceKey(); } }4.3 定义切换数据源的注解为了方便在方法上声明使用哪个数据源我们定义一个注解。Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) Documented public interface DS { /** * 数据源名称对应Spring容器中DataSource Bean的名称 */ String value() default primary; }4.4 全局配置类组装动态数据源这是最关键的配置类负责将多个物理数据源组装成一个动态路由数据源。Configuration Slf4j public class DynamicDataSourceConfig { Bean(name primaryDataSource) ConfigurationProperties(prefix spring.datasource.primary) public DataSource primaryDataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); } Bean(name slaveDataSource) ConfigurationProperties(prefix spring.datasource.slave) public DataSource slaveDataSource() { return DataSourceBuilder.create().type(HikariDataSource.class).build(); } /** * 核心动态数据源 */ Primary // 动态数据源作为主数据源注入 Bean(name dynamicDataSource) public DataSource dynamicDataSource( Qualifier(primaryDataSource) DataSource primaryDataSource, Qualifier(slaveDataSource) DataSource slaveDataSource) { MapObject, Object targetDataSources new HashMap(2); targetDataSources.put(primary, primaryDataSource); targetDataSources.put(slave, slaveDataSource); DynamicRoutingDataSource routingDataSource new DynamicRoutingDataSource(); // 设置所有备选数据源Map routingDataSource.setTargetDataSources(targetDataSources); // 设置默认数据源 routingDataSource.setDefaultTargetDataSource(primaryDataSource); // 初始化 routingDataSource.afterPropertiesSet(); log.info(动态数据源加载完成可用数据源: {}, targetDataSources.keySet()); return routingDataSource; } /** * 配置事务管理器统一使用动态数据源 */ Bean public PlatformTransactionManager transactionManager(Qualifier(dynamicDataSource) DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } /** * 配置MyBatis SqlSessionFactory绑定动态数据源 */ Bean public SqlSessionFactory sqlSessionFactory(Qualifier(dynamicDataSource) DataSource dataSource) throws Exception { MybatisSqlSessionFactoryBean sessionFactory new MybatisSqlSessionFactoryBean(); sessionFactory.setDataSource(dataSource); // 其他MyBatis配置... return sessionFactory.getObject(); } }配置解析我们依然定义了两个物理数据源BeanprimaryDataSource和slaveDataSource。dynamicDataSourceBean是核心。它创建了一个DynamicRoutingDataSource实例并将所有物理数据源以Map的形式注入进去Key就是后面用于切换的字符串如”primary”。将dynamicDataSource标记为Primary这样Spring容器中主要的DataSource类型Bean就是它。事务管理器(TransactionManager)和SqlSessionFactory都绑定到这个dynamicDataSource上。这是与静态配置最大的不同整个应用只有一套持久化核心设施它们内部通过路由机制来切换连接。4.5 实现切面基于注解自动切换我们需要一个AOP切面在方法执行前解析DS注解并设置相应的数据源路由键。Aspect Component Slf4j Order(-1) // 确保在事务切面之前执行 public class DynamicDataSourceAspect { Around(annotation(ds)) public Object around(ProceedingJoinPoint point, DS ds) throws Throwable { String dsKey ds.value(); // 判断当前指定的数据源是否在已配置的数据源集合中 boolean containsKey DynamicDataSourceContextHolder.containsDataSource(dsKey); if (!containsKey) { log.warn(数据源[{}]不存在将使用默认数据源, dsKey); } else { log.debug(切换至数据源: {}, dsKey); DynamicDataSourceContextHolder.setDataSourceKey(dsKey); } try { return point.proceed(); } finally { // 方法执行完毕后清空当前线程的数据源键恢复为默认数据源 DynamicDataSourceContextHolder.clearDataSourceKey(); log.debug(清空数据源路由键); } } }注意事项切面执行顺序务必通过Order注解确保数据源切换切面在Spring的事务管理切面Transactional之前执行。因为事务的开启需要获取数据库连接而获取连接时就会调用determineCurrentLookupKey()方法。如果切换发生在事务开启之后则切换无效可能导致事务内的所有操作都跑在默认数据源上。这是一个极其重要的细节。4.6 在业务中使用动态切换配置完成后使用就变得非常优雅。Service public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; // 所有Mapper都使用同一个SqlSessionFactory Override DS(primary) // 写操作走主库 Transactional // 事务管理器统一使用 dynamicDataSource public void createUser(User user) { userMapper.insert(user); } Override DS(slave) // 读操作走从库 public User getUserById(Long id) { return userMapper.selectById(id); } Override DS(slave) public PageUser queryUserByPage(PageQuery query) { PageUser page new Page(query.getPageNum(), query.getPageSize()); return userMapper.selectPage(page, query.toQueryWrapper()); } }5. 深度避坑与高级技巧实录无论是静态还是动态方案在实际生产中都可能遇到一些棘手问题。下面是我总结的几个典型场景和解决方案。5.1 事务与数据源切换的“幽灵”问题问题描述在动态数据源模式下一个Service方法被Transactional注解方法内部调用了多个带有不同DS注解的方法。你期望每个DAO操作都能正确切换数据源但实际发现所有操作都跑在了第一个DS注解指定的数据源或者默认数据源上。根因分析Spring的事务管理是基于AOP代理实现的。当调用一个Transactional方法时Spring会先开启事务。开启事务的过程就会从数据源获取一个数据库连接并将其绑定到当前线程TransactionSynchronizationManager。这个获取连接的动作触发了determineCurrentLookupKey()。此时如果数据源切换切面DS还没有执行那么就会使用默认数据源或当前上下文中的数据源键来获取连接。之后在整个事务执行期间Spring会重用这个已经绑定的连接即使你后续在方法内切换了数据源键实际使用的连接也不会改变。解决方案方案A推荐将数据源切换提升到事务层面。避免在同一个Transactional方法内切换数据源。将不同数据源的操作拆分成不同的Service方法每个方法都有自己的Transactional和DS注解。然后在上层通过非事务或新事务的方式调用它们。Service public class CombinedService { Autowired private UserService userService; Autowired private LogService logService; // 不开启事务或者开启一个新事务REQUIRES_NEW // Transactional(propagation Propagation.NOT_SUPPORTED) public void businessOperation() { // 每个操作在自己的事务和数据源中执行 userService.updateUser(); // 内部有 Transactional DS(primary) logService.insertLog(); // 内部有 Transactional DS(log) } }方案B使用TransactionTemplate编程式事务。在需要切换数据源的地方手动用TransactionTemplate开启一个新事务这样可以精确控制事务边界和连接获取时机。方案C高级继承DataSourceTransactionManager。自定义事务管理器在doBegin()方法中根据当前最新的数据源键来获取连接。但这需要深入理解Spring事务源码实现复杂不推荐新手尝试。5.2 多数据源下的MyBatis Mapper XML扫描冲突问题描述在静态多数据源配置中如果两个SqlSessionFactory都扫描了同一个Mapper XML文件路径可能会导致映射语句被重复加载或覆盖引发不可预知的行为。解决方案严格隔离Mapper接口和XML文件。目录隔离将不同数据源对应的Mapper XML文件放在不同的目录下如resources/mapper/primary/和resources/mapper/report/。配置隔离在每个数据源的配置类中通过sessionFactory.setMapperLocations()明确指定其专属的扫描路径。// 在 PrimaryDataSourceConfig 中 sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources(classpath:mapper/primary/*.xml)); // 在 ReportDataSourceConfig 中 sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources(classpath:mapper/report/*.xml));包名隔离确保MapperScan扫描的接口包路径也完全分开。5.3 异步任务与线程池中的数据源污染问题描述在使用了Async或线程池的任务中子线程会从父线程继承ThreadLocal值吗如果不继承那么我们在主线程设置的数据源键在异步任务中就会丢失导致使用默认数据源。解决方案实现线程上下文传递。手动传递在提交异步任务前将数据源键作为参数传递给异步方法。Async public FutureVoid asyncProcess(String dsKey, SomeData data) { DynamicDataSourceContextHolder.setDataSourceKey(dsKey); try { // ... 业务逻辑 } finally { DynamicDataSourceContextHolder.clearDataSourceKey(); } }使用TaskDecorator推荐Spring TaskExecutor允许设置一个TaskDecorator可以在任务执行前后进行包装。我们可以在这里实现上下文传递。Configuration public class AsyncConfig implements AsyncConfigurer { Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); // ... 配置线程池参数 executor.setTaskDecorator(new ContextCopyingTaskDecorator()); executor.initialize(); return executor; } static class ContextCopyingTaskDecorator implements TaskDecorator { Override public Runnable decorate(Runnable runnable) { // 捕获调用方的上下文 String dsKey DynamicDataSourceContextHolder.getDataSourceKey(); return () - { try { // 在子线程中恢复上下文 DynamicDataSourceContextHolder.setDataSourceKey(dsKey); runnable.run(); } finally { DynamicDataSourceContextHolder.clearDataSourceKey(); } }; } } }使用TransmittableThreadLocalTTL阿里开源的TransmittableThreadLocal可以自动解决线程池中的上下文传递问题功能更强大适合复杂场景。5.4 整合第三方Starter时的自动配置冲突问题描述当你引入spring-boot-starter-data-redis、spring-boot-starter-data-elasticsearch或其他数据相关的Starter时它们可能也会自动配置DataSource可能与你的多数据源配置冲突。解决方案排除特定自动配置在主应用类或配置类上使用SpringBootApplication(exclude {DataSourceAutoConfiguration.class})。但要注意这可能会禁用所有数据源的默认配置你需要完全手动管理。使用ConditionalOnMissingBean这是更优雅的方式。确保你的自定义DataSourceBean定义上带有Primary这样当其他自动配置类尝试创建DataSource时会因为容器中已存在同类型的主Bean而跳过。同时检查第三方Starter的自动配置类看它们是否对DataSource有强依赖必要时也可以通过exclude排除它们。仔细阅读官方文档许多第三方Starter提供了关闭自动数据源创建的配置属性。6. 进阶基于Dynamic-Datasource开源组件的快速实现手动实现动态数据源虽然有助于理解原理但在生产环境中我们更推荐使用成熟的轮子。国内最流行的当属dynamic-datasource-spring-boot-starter。它功能完善文档清晰极大地简化了配置。6.1 快速入门配置引入依赖dependency groupIdcom.baomidou/groupId artifactIddynamic-datasource-spring-boot-starter/artifactId version4.3.0/version !-- 请使用最新版本 -- /dependency配置文件spring: datasource: dynamic: primary: master # 设置默认数据源 strict: false # 是否严格匹配数据源未匹配到则使用默认数据源 datasource: master: url: jdbc:mysql://localhost:3306/master_db username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver slave_1: url: jdbc:mysql://localhost:3307/slave_1_db username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver slave_2: url: jdbc:mysql://localhost:3308/slave_2_db username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver使用注解该组件提供了和前面示例类似的DS注解。Service public class UserServiceImpl implements UserService { DS(master) public void insert(User user) { // 使用master数据源 } DS(slave_1) public User selectById(Long id) { // 使用slave_1数据源 } }6.2 组件核心优势与注意事项开箱即用无需自己编写AbstractRoutingDataSource、切面、上下文管理类。功能丰富支持读写分离、多租户、SpEL表达式动态解析数据源名、全局/本地数据源切换顺序配置等。无缝集成与MyBatis Plus、Spring Transaction、Spring Boot Actuator等集成良好。事务支持组件内部对事务场景下的数据源切换做了优化比我们手动实现的更健壮。使用注意事项事务问题依然存在该组件通过DataSourceAnnotationAdvisor和DynamicDataSourceTransactionAutoConfiguration在一定程度上优化了事务内的切换但其官方文档仍明确指出DS注解可以注解在方法上或类上但存在同时注解时方法注解优先于类注解。对于同一方法内的多个不同数据源操作依然建议拆分方法。版本兼容性注意其与Spring Boot、MyBatis Plus版本的兼容性建议参考官方GitHub仓库的说明。监控与管理生产环境使用建议结合Spring Boot Actuator的/health和/metrics端点监控各个数据源连接池的健康状态。我个人在经历了从手动造轮子到使用成熟组件的转变后强烈建议在正式项目中直接使用dynamic-datasource-spring-boot-starter。它社区活跃经历了大量生产验证能帮你规避很多底层细节的坑让你更专注于业务逻辑的实现。当然理解其底层原理对于排查复杂问题依然是必不可少的。
返回列表