news 2026/10/10 14:34:29

Spring DataSource原理剖析:连接池、自动配置与多数据源实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring DataSource原理剖析:连接池、自动配置与多数据源实战

1. 全局视角:为什么弄懂 DataSource 才算真正理解 Spring 的数据库原理

直接说吧,Spring 的数据库原理这座大厦里,DataSource 就是地基中的地基。不管是 JdbcTemplate、MyBatis 还是 JPA,底层全部要跟数据库建立连接,而连接的来源统一指向一个对象——DataSource。你去看任何一个 Spring 项目的配置文件,几乎都绕不开spring.datasource.url、username、password这三件套;面试的时候,面试官只要往深处问一句“Spring Boot 到底怎么创建连接池的”,很多人的回答就垮掉了。原因很简单:大多数人只会配,不知其所以然。

我最初做 Java 开发的时候也是这个状态,复制粘贴了一段spring.datasource配置,项目能跑就完事了。直到后来接手一个线上系统,连接池疯狂告警,超时、泄漏、慢 SQL 全冒出来,才痛下决心把 DataSource 这条链路从头到尾啃了一遍。啃完之后你会发现,以前看到的很多“玄学问题”其实全是原理层面的必然结果。这篇文章我不想写成一本书,就按我自己的理解,把 Spring 中 DataSource 的创建过程、连接池原理、自动配置机制、多数据源切换、事务协同这些核心点拆开讲透,再附上大量实测案例和排查经验。无论是刚学 Spring Boot 的入门者,还是被线上数据库问题折腾过的老手,应该都能从这里找到对自己有用的那块拼图。

顺便提一句,现在 Spring AI 这类新项目越来越火,很多人开始把智能体框架接入 Spring Cloud Alibaba 微服务体系,但不管是传统业务还是 AI 应用,数据访问这层依然是绕不开的 DataSource。把基础夯实,后面接什么花样都能稳住。

2. 核心源码与装配链路:从接口设计到连接池创建的全过程

2.1 DataSource 接口的设计意图:连接工厂这件事被抽象到了极致

先看 JDK 自带的javax.sql.DataSource接口,它其实只有三个方法:getConnection()、getConnection(String username, String password),加上一个unwrap相关的默认方法。就这点方法,撑起了整个 Java 数据库连接生态。它的本质就是一个“连接工厂”,谁需要连接,就问它要;它自己内部是直接DriverManager.getConnection()每次新建,还是从一个池子里取,使用者完全不用关心。

这种抽象设计的精妙之处在于:业务代码面对的是一个稳定不变的接口,而底层实现可以千变万化。你本地开发用 H2 内存库,测试环境用 MySQL,生产环境换成 PostgreSQL,只要换一个 DataSource 实现类、改一下连接参数,业务代码一行都不用动。这种“面向接口编程”的思想,比任何口头强调都更有说服力——它直接体现在 JDK 最基础的数据库 API 里。

Spring 对 DataSource 的态度也是一脉相承。org.springframework.jdbc.datasource包下面提供了大量装饰器类,比如LazyConnectionDataSourceProxy、TransactionAwareDataSourceProxy、UserCredentialsDataSourceAdapter,它们全部实现了同一个 DataSource 接口,然后在getConnection()方法里做各种增强。所以你在排查问题的时候,经常会遇到 DataSource 被一层层包起来的情况,表面上是一个对象,内部其实是洋葱结构。理解了接口这一层,再去看 Spring 的装配逻辑,才能看明白它到底怎么把一个普通的连接池包装成“带事务能力”“带懒加载能力”的复杂对象。

2.2 Spring Boot 自动配置:DataSourceAutoConfiguration的加载时机与条件装配

Spring Boot 之所以能做到“零配置就能连数据库”,核心功臣是DataSourceAutoConfiguration这个自动配置类。它通过@ConditionalOnClass判断类路径下有没有javax.sql.DataSource和EmbeddedDatabaseType,通过@ConditionalOnMissingBean判断容器里还没有用户自定义的 DataSource,只有这两个条件同时满足,才会自动创建一个默认的 DataSource。

这个设计背后有一个非常重要的原则:用户自定义优先。Spring Boot 给你兜底,但绝不抢你的控制权。你只要在容器里手动声明了一个DataSource类型的 Bean,自动配置立刻退避三舍,绝不重复创建。这也是很多新手容易踩坑的地方——明明自己定义了一个数据源 Bean,却发现系统里还有另一个连接池在运行,多半就是没有理解条件装配的优先级。

从加载顺序上看,DataSourceAutoConfiguration依赖于DataSourceConfiguration内部类做具体创建。Spring Boot 2.x 时代默认选择 HikariCP,判断逻辑是:先看类路径里有没有 HikariCP 的HikariDataSource类,如果不在看 Tomcat JDBC Pool,再看 Commons DBCP2。这个优先级顺序是硬编码在DataSourceConfiguration里的。也就是说,只要你的pom.xml里引入了 HikariCP 依赖,Spring Boot 闭着眼睛都会用它。想换成 Druid 也简单,排除默认的依赖,再把 Druid 的spring-boot-starter引进来,Druid 的自动配置类会接管。

2.3 属性绑定的黑魔法:@ConfigurationProperties如何把 yml 变成连接池参数

配置生效的最后一公里,靠的是@ConfigurationProperties绑定机制。Spring Boot 会把spring.datasource前缀下的所有配置项,按照“松绑定”规则映射到 DataSource 的属性上。所谓松绑定,就是url、URL、Url都能正确匹配到setUrl()方法,极大的容错性让配置文件可以保持人类友好的书写习惯。

这里要特别留意一个细节:DataSourceProperties类其实是 Spring Boot 官方提供的一个“参数中转站”。它先把spring.datasource下的基础配置读进来,然后通过initializeDataSourceBuilder()方法,把这个中转站里的参数复制给真正的连接池对象。所以你在排查问题时,如果发现 yml 里的某个连接池专属参数没有生效,第一步不是怀疑连接池,而是先确认spring.datasource.druid或spring.datasource.hikari这类子前缀是否写对了位置。参数没绑定上,连接池只会默默使用自己的默认值,表现出来就是“配置了等于没配”。

从实际编码角度,我更推荐直接用原生配置前缀,而不是依赖 Spring Boot 的自动绑定。比如用 Druid 时,写成spring.datasource.druid.xxx往往会有一堆坑,因为你得自己去匹配 Druid 的@ConfigurationProperties和 Spring Boot 的绑定逻辑。简单粗暴的做法是:自己在配置类里写一个@Bean加@ConfigurationProperties(prefix = "my.ds"),把你自定义配置前缀绑定到 DruidDataSource 上。这个方法我用了好几年,少踩很多莫名其妙的绑定冲突。

3. 连接池选型与配置实战:HikariCP、Druid 与多数据源方案

3.1 连接池为什么必不可少:一次数据库连接的开销远超你想象

没有连接池的时代,每次数据库操作都要经历 TCP 握手、MySQL 认证、权限校验、创建会话这一整套流程。我曾经在自己笔记本上做过一个粗略测试:直连 MySQL 建立一次连接大约需要 20 到 50 毫秒,而真正执行一条简单 SQL 只需要 1 到 2 毫秒。这意味着,在高并发场景下如果每来一个请求就新建连接,光是连接建立的时间就会把数据库吞吐量拖垮,更不用说频繁建连还会导致数据库端出现大量 TIME_WAIT 状态的连接,最终触及max_connections上限。

连接池的核心作用就是复用。提前创建一批物理连接放在池子里,业务请求来了直接取现成的,用完归还而不是关闭。真正连接数据库的物理连接始终只有池子里那十几个或几十个,数据库压力骤降,业务响应时间也稳定下来。理解了这一点,你就明白为什么连接池的参数调优这么重要——池子里放多少连接,直接决定了系统能扛住多少并发,以及数据库会不会被压垮。

3.2 HikariCP 关键参数与调优思路:从默认值开始一步步逼近最优

HikariCP 之所以成为 Spring Boot 默认连接池,核心原因是快。它做了大量字节码级优化,比如用FastStatementList替代ArrayList,用自定义的并发集合类减少锁竞争。但“快”不代表不用调参,我见过太多项目直接躺平使用默认配置,结果高峰期被打穿连接池。

下面几个参数是调优的重中之重:

  • maximum-pool-size:池中最大连接数,默认 10。这个值不是越大越好,它要考虑数据库自身的连接上限、应用所在机器的文件描述符限制、以及每个连接占用的内存。一个常见经验公式是((core_count * 2) + effective_spindle_count),但对大多数业务系统来说,设置为 20 到 50 通常够用。如果压测发现实际并发只有 30,却把最大池设成 200,只会白白浪费数据库资源。
  • minimum-idle:空闲时保持的最小连接数,默认等于 maximum-pool-size。如果你的系统有低峰期,建议把 minimum-idle 调小,比如 5 到 10,避免闲时也占着一堆数据库连接。
  • connection-timeout:客户端从池中获取连接的超时时间,默认 30000 毫秒。线上出现Connection is not available, request timed out after 30000ms这类报错,就是这个参数触发的。
  • max-lifetime:物理连接最大存活时间,默认 1800000 毫秒(30 分钟),建议比数据库的wait_timeout小 5 到 10 秒,防止数据库主动断开空闲连接后,连接池还持有早已失效的物理连接。
  • idle-timeout:空闲连接的超时时间,默认 600000 毫秒(10 分钟),只有当minimum-idle小于maximum-pool-size时才会生效。

调优的完整思路应该是:先设置合理的maximum-pool-size,再根据低峰需求设置minimum-idle,然后用压测验证connection-timeout是否合理,最后确保max-lifetime小于数据库服务端超时。不要一上来就把所有参数堆满,每一步改动都要有监控数据支撑。

3.3 Druid 场景实战:监控面板、慢 SQL 与 removeAbandoned 防泄漏

Druid 在国内企业中使用率极高,很大原因是它的监控能力太强了。StatFilter会自动统计 SQL 执行次数、执行时间、并发数等指标;WallFilter能做 SQL 白名单校验,防 SQL 注入;StatViewServlet提供一个 Web 界面,实时展示连接池状态。

很多人在配置 Druid 时抄了一段网上的配置,但不知道每个参数的含义。这里挑几个容易配错或者实际影响很大的核心项来说:

  • initial-size:初始化时创建多少个物理连接,默认 0。建议设置成 5,让应用启动后立刻有可用连接,避免第一个请求还要现建连接。
  • min-idle与max-active:最小空闲连接数与最大活跃连接数。注意 Druid 的语义和 HikariCP 不同,max-active对应 HikariCP 的maximum-pool-size。
  • remove-abandoned:是否开启超时连接回收机制,默认 false。当连接被业务代码借出后长期不归还,Druid 会通过后台线程强制回收该连接。这在修复连接泄漏问题上非常有效,但要小心误杀——如果某些长事务确实需要运行超过remove-abandoned-timeout,会被 Druid 强行回收,导致事务回滚。开启前一定要评估业务中是否存在合法长事务。
  • remove-abandoned-timeout:超过这个秒数未归还的连接会被回收,默认 300 秒。
  • log-abandoned:回收连接时打印日志,建议开启,方便追踪到是哪段代码泄漏了连接。这个日志会记录到当时的堆栈信息,排查问题的时候价值极大。

我自己的经验是:在开发环境把remove-abandoned打开、remove-abandoned-timeout设小一点(比如 60 秒),一旦代码里有人忘了关闭连接,几分钟内就会在日志里看到明确的告警,效率远高于在测试环境大海捞针。线上环境再根据实际长事务情况放宽阈值。

3.4 多数据源与动态切换的正确姿势:绕过 Spring Boot 的默认单数据源假设

Spring Boot 默认只支持一个 DataSource,但真实系统里经常要连多个库:主库、从库、报表库、缓存库。实现多数据源的方案有很多种,最常见的是用AbstractRoutingDataSource,它像是一个路由器,根据determineCurrentLookupKey()的返回值,从目标数据源 Map 中选一个真正的 DataSource 返回。

配套地,一般会用一个ThreadLocal保存当前线程要使用的数据源标识,再通过 AOP 切面在 Service 方法执行前设置标识、执行后清理。这里要注意一个非常容易踩的坑:ThreadLocal没有清理的话,线程池复用时会导致后续请求用错数据源。所以 AOP 的通知里必须有finally { DynamicDataSourceContextHolder.clearDataSourceKey(); }。

另外,动态切换数据源和事务之间的相互作用必须想清楚。Spring 事务是基于 DataSource 连接做的,如果你在事务开启之后再切换数据源,事务管理器可能已经拿到了旧数据源的连接,切换根本不会生效,而且会造成“多个数据源连接混在同一个事务里”的严重混乱。正确做法是:数据源切换必须发生在事务开启之前,也就是在进入@Transactional方法之前,通过 AOP 把数据源标识设置好。如果使用了@Transactional传播行为嵌套,那更是要格外小心,内层方法即使切换了数据源,也仍然沿用外层事务的连接,这是事务传播机制决定的,不是你的路由代码没执行。

4. 事务与 DataSource 的协同机制:连接、事务管理器与传播行为

4.1 事务管理器如何从 DataSource 获取连接并绑定到线程

Spring 的事务模型,本质上是把“数据库连接”和“当前线程”绑定在一起。DataSourceTransactionManager在事务开始时调用DataSourceUtils.getConnection(dataSource)获取一个连接,然后把它存放在TransactionSynchronizationManager的ThreadLocal里。之后,同一个线程里的所有 DAO 操作,通过DataSourceUtils.getConnection()拿到的都是同一个物理连接,从而保证大家在同一个数据库事务里。

这个机制解释了很多奇怪的现象。比如你在同一个线程里既用了 JdbcTemplate 又用了 MyBatis,只要它们指向同一个 DataSource,事务就能统一管理,因为连接绑定在线程上,与具体框架无关。反过来,如果两个 DAO 使用了不同的 DataSource,它们拿到的连接就不是同一个,事务自然就管不住另一边的操作。

DataSourceUtils的getConnection还会做一层额外的检查:当传入的 DataSource 是ConnectionHandle或者被事务感知代理包装过的对象时,它会调用TransactionAwareDataSourceProxy之类的装饰器,确保从这里获取的连接能够识别当前线程是否已经有事务绑定。这也是为什么 Spring 官方推荐在需要显式操作 JDBC 的场景里使用TransactionAwareDataSourceProxy包装数据源——它能保证你手动拿连接的时候也走事务逻辑,而不是绕过事务另外开一个连接。

4.2 事务失效的六个常见元凶:从连接角度看真相

“事务不生效”是数据库开发里被问烂了的问题,但从 DataSource 原理视角可以给出非常清晰的解释。最常见的六个原因,全部能在连接管理层面找到答案:

第一,没有配置事务管理器。Spring Boot 的DataSourceTransactionManagerAutoConfiguration会自动创建一个事务管理器,但如果你自定义了 DataSource,却忘记重新声明事务管理器,Spring 会用默认机制尝试找容器中的唯一 DataSource,匹配失败后事务自然失效。

第二,@Transactional加在了非 public 方法上。Spring 的声明式事务默认通过 AOP 代理实现,而基于 JDK 动态代理和 CGLIB 的实现都只能拦截公开方法调用。私有方法被内部调用时,不会经过代理对象,事务完全失效。

第三,同类内部调用绕过了代理。这就好比你直接new了一个对象调用方法,当然不会走 Spring 容器里的代理逻辑。解决办法是注入自身代理对象,或者把需要事务的方法拆到另一个 Service 里。

第四,异常被吞没。Spring 默认只在 RuntimeException 时回滚。如果你捕获了异常并正常返回,事务会提交而不是回滚。这个不算连接层面的问题,但却是最容易被新手踩中的。

第五,传播行为设置错误。比如Propagation.REQUIRES_NEW会挂起当前事务并新建一个连接开启新事务。如果你以为加了注解会沿用外部事务,实际却是新开了连接、新开了事务,一旦内层抛异常回滚,外层的数据已经提交了一部分,数据一致性就出问题了。

第六,多线程调用。子线程使用Runnable或CompletableFuture时拿不到主线程的ThreadLocal连接,即使方法上有@Transactional,对子线程来说也是无事务的。从 DataSource 角度看,子线程重新获取了一个全新的连接,之前主线程绑定的连接它根本接触不到。

每遇到一个事务失效问题,我都会先自己问一句:这个线程里,TransactionSynchronizationManager.getResource(dataSource)拿到的连接还活着吗?问题往往就出在这里。

4.3 多数据源下的事务边界:分布式事务的启蒙与避坑

当系统拆分出多个数据源之后,单靠DataSourceTransactionManager只能保证一个数据源内的事务,跨库操作就面临分布式事务问题。很多人的第一反应是引入 Seata、Atomikos 这类组件,但我的态度是:能用业务手段规避的,绝不上中间件。比如把需要跨库的操作改造成“先写主库,再异步同步到从库”,或者通过消息队列实现最终一致性,这样系统复杂度可控得多。

如果业务确实需要强一致,那再考虑分布式事务方案。以 Seata 为例,它的 AT 模式会在业务操作前记录数据快照、操作后生成 undo log,通过全局事务协调器统一提交或回滚。这背后依然是每个分支事务对应独立的 DataSource,但协调器负责跨 DataSource 的提交决策。理解这一点,你就明白为什么 Seata 要求每个分支事务的数据源都被 Seata 代理包装——它需要拦截你的连接和 SQL 执行。

在这里我特别想提醒一句:不要为了一个很小的跨库读操作引入分布式事务。它带来的性能损耗、运维复杂度、排障难度,往往远超你的想象。先画清楚业务的数据一致性要求,能降级就降级,这才是架构师思维。

5. 常见问题与排查技巧实录:连接池、慢 SQL 与配置绑定踩坑

5.1 “Connection is not available” 不是配置问题,是容量问题

线上最经典的异常是HikariPool-1 - Connection is not available, request timed out after 30000ms。新手一看,以为是连接池配置错了,实际上这个报错只说明一件事:在 30 秒内,池里始终没有可用连接,而且新连接也建不出来。原因有两种:要么是池的maximum-pool-size设置太小,并发请求超过池容量,连接不够用;要么是慢 SQL 把连接全部占满了,每条连接都被长时间持有,池里所有连接都在执行没结束的 SQL。

排查步骤我有自己固定的顺序:先看监控面板里的活跃连接数,是稳定等于最大值还是频繁触顶。如果触顶后连接数降不下来,基本就是慢 SQL 或连接泄漏。接着通过show processlist查看数据库侧的执行状态,看大量Query状态是否长期存在,定位具体 SQL。最后用 Arthas 之类的工具查看线程栈,找到到底哪个业务方法一直占着连接不放。

connection-timeout本身也可以辅助判断。如果你把超时缩短到 5 秒,系统仍然频繁报错,说明池容量和 SQL 性能的缺口不是偶发性的,而是结构性的容量问题,这时候单纯调参是治标不治本。

5.2 连接泄漏的三种定位手段:监控阈值、堆栈日志、借用计数

连接泄漏是比容量不足更隐蔽的问题。它最典型的表现是:系统运行几个小时之后,活跃连接数缓慢上涨,最终所有连接被耗尽,期间数据库的 CPU 和 SQL 执行效率看起来一切正常。因为泄漏的业务请求虽然把连接借走了,但并没有真正执行 SQL,数据库侧根本看不到压力。

定位手段有三板斧。第一板斧是连接池监控:Druid 的监控页面直接展示activeCount、poolingCount、逻辑连接打开次数和关闭次数,差值为正则说明有连接打开了没关闭。第二板斧是泄漏堆栈:开启 Druid 的log-abandoned,回收连接时打印堆栈,一眼看清是哪段代码借了连接没归还。第三板斧是代码审查:重点查try-with-resources没有关闭的旧写法,尤其是用了finally却忘记close()的情况。

这里有一个容易被忽略的细节:用 JdbcTemplate 操作数据库时,连接是由 Spring 管理的,不需要你手动关闭,但如果混用了原生 JDBC 代码,括号里漏了Connection,就会导致 Spring 封装的连接也无法归还。混合编码方式是连接泄漏的重灾区,我见过好几个团队都栽在这上面。

5.3 yml 配置不生效的排查:先看前缀,再看类路径,最后看绑定

配置不生效这个问题的排查思路,其实比大多数人想的要直接。第一步,检查配置前缀是否正确。HikariCP 的专属参数在 Spring Boot 下应该写在spring.datasource.hikari下面,Druid 的写在spring.datasource.druid下面,写错层级,绑定直接失败,连接池用默认值。第二步,确认依赖是否在类路径里。如果你的项目用 HikariCP,却写了 Druid 的initial-size和max-active,那当然不会生效,因为 HikariCP 根本没有这些属性。第三步,如果是自定义数据源 Bean,检查@ConfigurationProperties绑定前,是否把 Bean 声明成了静态方法。Spring Boot 的配置绑定在 BeanPostProcessor 阶段执行,如果你在@Bean方法里先new了一个对象再手动 set 属性,同时又在类级别加了@ConfigurationProperties,两者相互覆盖,效果会非常混乱。

最后再分享一个经验:使用 IDE 的spring-configuration-metadata.json校验功能,能大幅减少这类低级错误。把 Spring Boot 相关的依赖引进来之后,编写 yml 的时候 IDE 会自动提示可用的配置项,前缀写错当场就能发现。

5.4 动态数据源切换失效的排查:事务边界与连接缓存顺序

动态数据源切换失效,最常见的场景是:同一个 Service 里,方法 A 切到了读库,方法 B 却仍然访问主库。排查时先检查 AOP 切面和事务注解的执行顺序。如果@Transactional先被 AOP 处理,事务已经启动,连接已经获取,这个时候再去切换数据源标识,事务管理器缓存了已经拿到的主库连接,路由根本不会改变默认数据源,因为AbstractRoutingDataSource的determineCurrentLookupKey()返回值变了,但连接本身已经被事务管理器锁定在当前 DataSource 上。

文档里常常提到一个策略升级:把路由 DataSource 作为目标数据源,同时配置一个LazyConnectionDataSourceProxy包装它。这样DataSourceUtils.getConnection()第一次真正获取连接时才会触发路由,哪怕事务开启得早,只要第一次使用连接发生在切库之后,路由就是正确的。这个方案能解决一部分“事务先启动、切库后无效”的问题,但对使用DataSourceUtils之前就已经获取连接的框架,仍然不能完全解决。所以最稳妥的方案依然是:在进入事务方法之前完成数据源切换。

5.5 常见问题速查表

现象直接原因排查手段解决方案
连接获取超时池容量不足或连接被占满查看活跃连接数、数据库 processlist、线程栈调大 maximum-pool-size,优化慢 SQL,修复泄漏
连接泄漏连接被借出未归还监控面板打开/关闭计数、开启 remove-abandoned 日志统一使用 try-with-resources,审查混合 JDBC 编码
事务不生效事务管理器未创建、异常被捕获、代理未生效检查 Bean 定义、异常路径、代理类补齐配置、改用 RuntimeException、注入代理对象
多数据源切换失败切换发生在事务开启之后检查 AOP 顺序、事务传播行为切库逻辑移到事务方法之前,使用 LazyConnectionDataSourceProxy
配置不生效前缀错误或类路径依赖缺失IDE 校验配置项、查看依赖树修正前缀、补充依赖

6. 延伸与进阶:从监控出发的容量规划,以及 Spring AI 场景下的新触点

6.1 用连接池监控倒推容量规划:别等报警再扩容

连接池的监控数据不止是排障用的,它还是容量规划的重要输入。我一般会定期收集三个指标:峰值活跃连接数、连接获取平均耗时、连接创建次数。如果峰值活跃连接数稳定在maximum-pool-size的 70% 以上,说明池容量偏紧,需要考虑调大或者优化 SQL 性能;如果连接获取平均耗时在几毫秒内,说明池容量充足,强行调大反而会增加数据库负担;如果连接创建次数频繁,说明max-lifetime太短或者连接利用率低,调整生命周期参数比加池子数量更有效。

这套数据结合 CPU、磁盘 IO 和慢 SQL 日志,能综合判断系统瓶颈到底在应用侧还是数据库侧。很多时候,数据库负载高不是连接池问题,而是业务 SQL 写得差;反过来说,应用响应慢也可能根本不是数据库的问题,而是连接获取卡住了。不要陷入单一指标,多个指标交叉验证才是正路。

6.2 Spring AI 时代:DataSource 如何服务智能体与向量检索

Spring AI 的出现让 DataSource 的边界又扩展了一层。现在很多团队在做智能体应用,需要把业务数据存储和向量检索结合起来。Spring AI 官方提供了VectorStore抽象,而实现这个抽象的一种方式,就是像JdbcVectorStore一样直接基于 DataSource 操作数据库表来存储向量和元数据。这种方案的好处是不需要额外引入 Elasticsearch 或专用向量数据库,直接复用现有数据库连接池和管理体系。

更复杂的场景是把 Spring AI 接入 Spring Cloud Alibaba 微服务体系内部,让智能体通过工具调用方式访问业务微服务。这时,每个被调用的微服务依然有自己的 DataSource 和连接池,问题从单机转向了分布式链路。工具调用可能横跨多个服务、多个库,事务一致性又回到前面聊过的分布式事务范畴。好消息是,最基础的连接复用、连接池调优、路由切换能力在 AI 场景里完全一样,原理不会变。

所以我的建议是:学 Spring AI 之前,先把 DataSource 这块啃透。AI 应用再花哨,落到数据访问还是那一套连接管理机制。基础不牢,后面接大模型、接智能体,迟早还要回来补课。

说实话,我这些年看过的系统里,90% 的数据库相关故障都能在 DataSource 这条链路上找到根因。有时候是连接池参数没调,有时候是事务和路由的顺序问题,有时候只是配置前缀写错了。这些东西在官方文档里都有,但分散在几个不同的章节里,很难串成一条线。今天这篇文章把我自己的理解完整记录下来,也是希望能帮你在遇到类似问题的时候少走一些弯路。我最后再分享一个实践习惯:给项目加一个启动时的DataSource健康检查,在ApplicationRunner里尝试获取一次连接并执行SELECT 1,如果失败就让应用快速失败,不要等流量上来了才被超时拖垮。这个习惯我持续了好几年,救过我好几次。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 14:30:54

C++ Qt词法分析器课设:NFA/DFA状态图可视化与完整实现

简介:一套面向编译原理课程与期末课设场景的C/Qt词法分析器工程包,适合正在学习词法分析、自动机理论与GUI开发的学生参考。工具将源代码拆分为标记(token),覆盖关键字、标识符、数字、运算符等常见规则,并…

作者头像 李华
网站建设 2026/10/10 14:30:14

Go并发面试题详解:两个goroutine交替打印1到100的三种解法

1. 题目拆解:面试官到底在考什么“两个 goroutine 轮流打印 1 到 100,一个打印奇数,一个打印偶数”——这道题在 Go 面试中出现的频率,高到几乎可以跟“反转链表”并列。我第一次在面试中被问到的时候,脑子里全是 chan…

作者头像 李华
网站建设 2026/10/10 14:29:39

ArkWeb开发手记02|权限、网络白名单与页面缓存控制

搞定了 ArkWeb 基础页面搭建,把 Web 组件生命周期做了绑定,解决最头疼的内存泄漏问题。但很多同学照着代码跑通之后,马上遇到新麻烦:H5 页面调用摄像头直接拒绝、上传图片无响应;更换 H5 资源之后 APP 里面还是旧页面&…

作者头像 李华
网站建设 2026/10/10 14:29:22

[鼎捷 ERP] BOM批量导入、工单和采集序列号相关问题排错合集(二)

目录 E10-P-MFG-013 BOM导入主件品号或工厂错误E10-P-MFG-014 BOM变更与导入报错处理E10-P-MFG-015 工单ICD信息字段无法选择E10-P-MFG-016 库存改制入库单审核提示未采集序列号E10-P-MFG-017 生产入库单提示须存在且未审核E10-P-MFG-018 生产入库单序列号采集未完成E10-P-MFG-…

作者头像 李华
网站建设 2026/10/10 14:28:29

租赁门店押金自动原路退回系统设计与实现:以汉服礼服为例

1. 汉服礼服租赁,为什么押金原路退回是个“硬需求”前阵子帮一个做汉服体验馆的朋友梳理订单流程,聊到押金这块,他给我看后台的退款记录,基本上每周都有几笔退款纠纷。有的是顾客说“押金怎么还没到账”,有的是“我当时…

作者头像 李华
网站建设 2026/10/10 14:28:20

英语六级翻译纲领

目录 : 🌈 “主” 和 “顺” 原则 🌈 局部倒译法 🌈 With/As 来相助 🌈 句子衔接 第一讲 “主” 和 “顺” 原则 一. 主语的选择决定了译文的语态 ( 主动还是被动 ) 比如 : 人们经常夸方世玉很帅 主动语态 : People often com…

作者头像 李华