从JDBC到MyBatis:详解Spring Boot连接池与事务隔离的实战要点
搞 Java 的同学几乎没人绕得开 JDBC 这个名字。不管你现在用的是 Spring Boot MyBatis还是更偏底层的自研框架只要连的是关系型数据库JDBC 就是那条绕不过去的必经之路。这篇文章我不打算念 API 文档而是把我自己从“只会调框架”到“能徒手写好一个数据访问层”的过程拆开讲清楚原生 JDBC 该怎么写、Spring Boot 集成后连接池和事务是怎么回事、再往上怎么平滑过渡到 MyBatis 这类 ORM 框架最后把工作中真正踩过的连接泄露、时区报错、慢 SQL 这些坑也一并整理出来。适合谁看如果你是刚入门 Java、准备面试或者正在做毕设的同学这篇文章能帮你把 JDBC 和 Spring Boot 之间的那层窗户纸捅破如果你已经写了一阵子业务代码但遇到数据库连接异常就发怵也能在里面找到排查思路。核心就一句话知道底层怎么运作上层框架用起来才不慌。1. 为什么要从 JDBC 原生 API 开始而不是直接上框架1.1 JDBC 到底是什么它帮你干了哪些事JDBC 的全称是 Java Database Connectivity翻译过来就是 Java 数据库连接。你可以把它理解成一套“插座标准”Sun 公司定义好接口MySQL、PostgreSQL、Oracle 这些数据库厂商各自提供对应的“插头”也就是驱动Driver。有了这套标准你的 Java 代码写一次换个数据库厂商只要换驱动和连接串就行业务代码基本不用动。它真正帮你做的事笼统说有三件建立连接、执行 SQL、处理结果。打开一个 JDBC 连接本质上是在客户端和数据库服务器之间建立一条网络通道驱动负责把这条通道上的字节流转换成数据库能识别的协议。执行 SQL 时驱动把你要执行的语句发给数据库数据库解析、优化、执行然后把结果集ResultSet返回给 Java 端。整个过程如果让你用 Socket 自己写要处理协议、加密、分帧基本不可能所以 JDBC 这层封装才这么重要。好多人一上来就接触 MyBatis觉得 JDBC 是个旧东西没必要学。这个观点我很不认同。MyBatis 内部的核心依然是 JDBC它只不过帮你把 Connection 的获取和释放、Statement 的创建、ResultSet 到对象的映射这些样板代码隐藏了。你要是连 JDBC 最基本的执行流程都不清楚将来排查一条 SQL 很奇怪的问题时会连日志都看不懂。1.2 直接上 Spring Boot MyBatis 前最好先手写一遍原生 JDBC我在带新人时有个习惯入职第一周不让他碰 MyBatis先让他用原生 JDBC 写一个完整的增删改查。有人可能觉得这是浪费时间但实际效果很好。为什么因为手写一遍之后很多概念会瞬间变得具体。比如“连接池”这个词。你第一次用原生 JDBC每次请求都 DriverManager.getConnection()一旦并发上来数据库连接数瞬间被打满你才开始明白连接为什么要复用。再比如“事务”这件事你用 MyBatis 时只要在 Service 方法上加 Transactional一切看起来很简单但其实底层是 Spring 帮你拿到了同一个 Connection关闭了自动提交。你不理解这一层遇到事务失效的坑时往往不知道往哪找其实大概率就是数据源配错了、方法被 private 修饰了或者异常被 try-catch 吃掉了。手写 JDBC 也能让你在面试时更有底气。Java 面试题里常考的“PreparedStatement 和 Statement 区别”“JDBC 操作数据库步骤”“如何防止 SQL 注入”本质上都是 JDBC 的底层知识。你要是只会背八股文面试官随便追问一个细节就露馅但你真的动手写过答出来的是自己的体会。1.3 前置条件驱动、连接串、依赖选择开始写代码之前先把环境准备好。我用的是 MySQL 8.0对应驱动必须是com.mysql:mysql-connector-j8.x。驱动下载地址在各 Maven 仓库都能找到注意别再用老版本的mysql-connector-java坐标虽然兼容但类名已经变了。连接串是我最常被问到的点。一个标准的 MySQL JDBC 连接串长这样String url jdbc:mysql://localhost:3306/demo_db ?useSSLfalse serverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue characterEncodingutf8;useSSLfalse本地开发不需要 TLS 加密不加这个 MySQL 8 默认会尝试 SSL 握手容易报奇怪的警告。serverTimezoneAsia/Shanghai不设置的话驱动可能拿不到数据库时区报错提示也会让你加上。allowPublicKeyRetrievaltrueMySQL 8 使用 caching_sha2_password 认证非 SSL 连接下需要这个参数。characterEncodingutf8避免中文乱码。如果你用的是 Spring Boot依赖直接写spring-boot-starter-jdbc就行它会帮你把 HikariCP 连接池和数据源自动配置都带进来。手动写原生 JDBC 测试时只需要在 pom.xml 里加一个驱动依赖就够了。2. 原生 JDBC 实战从零写一个可用的数据访问层2.1 五步走加载驱动、建立连接、执行、处理结果、释放资源原生 JDBC 的套路五步我建议直接背下来写代码时照着做就不会漏加载驱动获取连接创建 Statement / PreparedStatement执行 SQL 并处理 ResultSet关闭资源一个最简单查询用户的方法长这样public User findById(Long id) { String url jdbc:mysql://localhost:3306/demo_db?useSSLfalseserverTimezoneAsia/Shanghai; String user root; String password 123456; String sql SELECT id, name, email FROM user WHERE id ?; User result null; try (Connection conn DriverManager.getConnection(url, user, password); PreparedStatement ps conn.prepareStatement(sql)) { ps.setLong(1, id); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { result new User(); result.setId(rs.getLong(id)); result.setName(rs.getString(name)); result.setEmail(rs.getString(email)); } } } catch (SQLException e) { // 实际项目中要记录日志 e.printStackTrace(); } return result; }注意Class.forName(com.mysql.cj.jdbc.Driver)这行在新版驱动里可以不写因为依赖包里有 SPI 配置文件DriverManager 会自动加载。但手写时我还是习惯写上一方面兼容老版本另一方面让阅读代码的人知道这里加载了驱动逻辑更清晰。Cursor和ResultSet的处理有个小技巧如果你不确定 SQL 返回多少条建议用rs.next()配合循环遍历而不是直接rs.getString避免拿不到数据时抛异常。处理完一行就封装成业务对象这是我推荐的写法因为 ResultSet 和 Connection 是绑定的一旦连接关闭结果集也就废了。2.2 参数怎么传PreparedStatement 为什么是必须的新手最容易犯的错误是用字符串拼接 SQLString sql SELECT * FROM user WHERE name name ; Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql);一旦name里传入 or 11SQL 就变成了SELECT * FROM user WHERE name or 11这就是经典的 SQL 注入。所以项目中必须用PreparedStatement它有两个核心价值一是预编译。SQL 骨架先发给数据库解析后面的参数只作为字面量传递根本不会被当成 SQL 执行注入自然失效。二是类型安全。比如ps.setDate(1, date)只接受java.sql.Date你传LocalDateTime会直接编译报错强迫你先做转换。我自己的习惯是统一用LocalDateTime做业务时间类型写 JDBC 时再转成java.sql.Timestampps.setTimestamp(1, Timestamp.valueOf(localDateTime));查询结果也是一样rs.getTimestamp(create_time).toLocalDateTime()转回来。这里特别提醒一下千万别图省事用rs.getString(create_time)再转字符串格式化容易乱。2.3 事务控制和批量操作别让代码崩在最后一刻JDBC 默认情况下每一条 SQL 执行完会自动提交也就是说conn.setAutoCommit(true)。如果一次业务操作要执行多条 SQL比如转账场景里一个人扣款、另一个人加款就必须手动控制事务Connection conn null; try { conn DriverManager.getConnection(url, user, password); conn.setAutoCommit(false); // 执行扣款 try (PreparedStatement ps1 conn.prepareStatement(UPDATE account SET balance balance - ? WHERE id ?)) { ps1.setBigDecimal(1, amount); ps1.setLong(2, fromId); ps1.executeUpdate(); } // 执行加款 try (PreparedStatement ps2 conn.prepareStatement(UPDATE account SET balance balance ? WHERE id ?)) { ps2.setBigDecimal(1, amount); ps2.setLong(2, toId); ps2.executeUpdate(); } conn.commit(); } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { // 回滚失败要重点记录 } } throw e; } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException ignored) { } } }这段代码虽然啰嗦但意义重大。注意事务里的多条 SQL 必须用同一个 Connection因为事务是绑定在连接上的不同的连接各走各的谈不上原子性。finally 里把setAutoCommit(true)恢复默认值也是一个容易被忽略的细节连接归还给连接池后如果不复位下个使用者可能“莫名其妙”被套进一个没提交的事务里。批量操作也是 JDBC 里特别实用的能力。批量插入时用addBatch()executeBatch()能明显减少网络往返try (PreparedStatement ps conn.prepareStatement(INSERT INTO user(name) VALUES (?))) { for (int i 0; i 10000; i) { ps.setString(1, user i); ps.addBatch(); if (i % 1000 0) { ps.executeBatch(); } } ps.executeBatch(); }MySQL 连接串加上rewriteBatchedStatementstrue后驱动会把多条 INSERT 重写成一条多 VALUES 的语句性能提升非常明显我实测过批量插入从几十秒降到几百毫秒。2.4 资源释放finally 与 try-with-resources 的正确姿势资源释放是 JDBC 最容易踩坑的地方。ResultSet、Statement、Connection 这三个都是资源如果不关闭数据库连接会被一直持有连接池很快就满了。Java 7 之后最优雅的写法是 try-with-resources方法执行完自动调用 close()try (Connection conn DriverManager.getConnection(url, user, password); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { // 处理 } }这里有个顺序问题关闭顺序应该先 ResultSet再 Statement最后 Connection。try-with-resources 里资源的关闭顺序是逆序正好符合要求所以放心用。如果你在一个方法里同时写多个 SQL不要共用一个 Statement每段操作独立开一个 try-with-resources。过去很多人喜欢一个 Statement 反复 executeQuery一方面不直观另一方面 ResultSet 会互相覆盖数据错了都找不到原因。这些经验不是文档里能看到的属于实操后才能总结出来的教训。3. Spring Boot 集成 JDBC让连接管理变得省心3.1 引入 spring-boot-starter-jdbc 之后连接从哪来原生 JDBC 的一个痛点就是连接管理每次手动DriverManager.getConnection太奢侈生产环境基本没人这么干。Spring Boot 引入spring-boot-starter-jdbc后会自动配置一个DataSource默认是 HikariCP 连接池这也解释了为什么你的项目里什么都没配就能直接注入JdbcTemplate。连接池的含义好比一个“租车行”。你不再需要自己买车而是每次要用时去租用完还回去。HikariCP 的默认配置里maximum-pool-size是 10也就是说最多同时租出 10 个连接。如果 10 个都被占满第 11 个请求就会等待直到有连接归还或超时。在 application.properties 里最常用的配置是这些spring.datasource.urljdbc:mysql://localhost:3306/demo_db?useSSLfalseserverTimezoneAsia/Shanghai spring.datasource.usernameroot spring.datasource.password123456 spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver spring.datasource.hikari.maximum-pool-size10 spring.datasource.hikari.minimum-idle2 spring.datasource.hikari.connection-timeout30000 spring.datasource.hikari.idle-timeout600000minimum-idle和maximum-pool-size建议不要设成一样。我见过有人为了省事把两个都设成 10结果空闲连接一直占着内存正常情况下让连接数随流量动态伸缩就好。connection-timeout是获取连接的最大等待时间设太长会让请求卡死设太短在高并发下会频繁报获取不到连接我一般从 30 秒起步再根据监控调整。为什么 Spring Boot 默认选择 HikariCP因为它在 Benchmarks 里长期排第一字节码级别优化做得很极致。你不需要自己写连接池但至少要知道它是一个池子连接的创建和销毁都很昂贵池化是必须的。3.2 JdbcTemplate我最常用的三个方法和一个坑Spring Boot 里集成 JDBC官方推荐直接使用JdbcTemplate。这个类把原生 JDBC 的样板代码处理掉了同时保留了 SQL 的完全可控性。我日常最常用的三个方法queryForObject查询单条记录配合 RowMapper 映射对象。query查询列表。update增删改操作。一个标准示例Service public class UserService { private final JdbcTemplate jdbcTemplate; public UserService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } public User findById(Long id) { ListUser list jdbcTemplate.query( SELECT id, name FROM user WHERE id ?, (rs, rowNum) - { User user new User(); user.setId(rs.getLong(id)); user.setName(rs.getString(name)); return user; }, id); if (list.isEmpty()) { return null; } return list.get(0); } }这里有个特别典型的坑queryForObject在查询不到记录时会抛EmptyResultDataAccessException而不是返回 null。很多新人第一次遇到都会一脸懵。解决办法是像我上面这样先查 List或者用queryForObject包一层 try-catch。从 Spring 5 开始可以用Optional但最稳妥的还是 List 判断。JdbcTemplate还有个兄弟叫NamedParameterJdbcTemplate它允许在 SQL 里用:name这种命名参数SQL 可读性好很多参数多了也不容易对错位置。如果项目中既有简单查询又有复杂动态 SQL建议优先用 NamedParameterJdbcTemplate维护起来更舒服。3.3 多数据源怎么办DynamicDataSource 的取舍单体应用也可能遇到多数据源读写分离、多个业务库。Spring Boot 默认只配一个 DataSource多数据源需要自己动手。最简单的方案是基于AbstractRoutingDataSource做动态数据源路由。核心思路是用一个 Map 管理所有真实的数据源再通过一个ThreadLocal上下文变量决定当前线程走哪个数据源。AOP 切面里根据方法名或注解切换到目标数据源。切到只读数据源时把事务设置为只读还能减少数据库压力。多数据源最大的坑是“跨数据源事务”。你要是一个事务里同时操作库 A 和库 B单机本地事务根本管不了。解决方案分三个层次业务允许拆分就拆成不同事务代码里串行调用保证最终一致。必须强一致引入分布式事务框架但成本明显变高。尽量把同一次业务的所有 SQL 收敛到同一个数据源这是最简单也最容易被忽视的办法。如果你用 MyBatis-Plus多数据源可以直接用DS(slave)注解它本质也是切面 动态路由。但我的建议是能不用多数据源就不用一个库能解决的事别拆成两个库数据库迁移、备份、事务都会变成多个维度的复杂度。4. 进阶从 JDBC 到 MyBatis/MyBatis-Plus 的平滑过渡4.1 原生 JDBC 写多了最应该怀念和抛弃的东西手写 JDBC 一段时间后你会对它又爱又恨。爱的是透明SQL 完全掌握在自己手里出了任何问题都可以一步一步跟进去看。恨的是样板代码一个简单的 CRUD 要写五六遍 try-with-resources整个类几百行维护成本很高。MyBatis 的出现本质上就是把结果集映射和 SQL 执行流程封装起来但你依然在 XML 或注解里写 SQL。对比一下MyBatis 的 Mapper 接口比 JdbcTemplate 更进一步它把查询结果自动映射到 POJO列名和属性名可以通过 map-underscore-to-camel-case 自动转换。曾经 JDBC 里最烦人的那坨 ResultSet 映射代码在这里消失了。不过使用 ORM 时必须保留一个清醒的认知ORM 只是帮你生成了一部分 SQL复杂的动态查询、关联查询、分库分表还是要自己写 SQL。MyBatis 动态 SQL 里的if、foreach是业务里用得最多的标签比如列表查询根据条件拼 WHERE 子句。很多人把这个当成“自动拼 SQL”其实它只是帮你用 XML 表达逻辑背后还是纯粹的 SQL。4.2 MyBatis-Plus 根据实体类生成建表 SQL 的原理网上有个很常见的需求根据 Java 实体类自动生成创建表的 SQL 语句。MyBatis-Plus 虽然以 CRUD 增强出名但它本身不直接提供“实体类生成建表 SQL”的功能。不过这个工具类写起来并不难原理也不复杂。核心思路是通过反射读取实体类的字段信息结合注解生成 CREATE TABLE 语句。先定义一个实体类TableName(user) public class User { TableId(type IdType.AUTO) private Long id; TableField(value user_name, comment 用户名) private String userName; TableField(value age, comment 年龄) private Integer age; }然后写一个生成器步骤是用反射拿到实体的所有Field过滤掉static和transient。读取TableName确定表名没有注解就用类名转下划线。读取TableField获得列名和注释TableId标识主键。把 Java 类型映射成 MySQL 类型String - varcharInteger - intLong - bigintLocalDateTime - datetimeBigDecimal - decimal。拼出 CREATE TABLE 语句。拼接逻辑大致这样StringBuilder ddl new StringBuilder(CREATE TABLE IF NOT EXISTS ).append(tableName).append( (\n); for (Field field : fields) { ddl.append( ).append(columnName).append( ) .append(mysqlType).append( ) .append(field.isPrimary ? PRIMARY KEY AUTO_INCREMENT : ) .append( COMMENT ).append(comment).append(,\n); }这里最容易被忽略的是字段长度。String 类型你得指定 varchar(255) 还是 text否则默认长度可能不够用。写工具时建议允许通过注解自定义长度比如TableField(value remark, length 500)比统一定死 255 灵活得多。这个功能在什么场景下适用快速搭建项目的初始化 DDL或者需要在测试环境同步表结构时。生产环境我不会用它表结构变更仍然要走专业的 Flyway 或 Liquibase 做版本管理靠实体类反向生成表结构对线上运维来说是灾难。4.3 跨商户商城项目里数据访问层的分层经验很多开源项目比如基于 Spring Boot MyBatis 的多商户跨境商城数据访问层如果设计得不好业务一扩展就崩。我这里想聊聊通用的分层经验。第一Controller、Service、Mapper 三层各自职责要清晰。Controller 只做参数校验和返回封装Service 做业务逻辑和事务控制Mapper 只做 SQL 和映射。如果 Controller 里直接注入 Mapper短期写起来爽但将来要做权限控制、日志记录、事务处理时你会发现没法插手因为入口太碎了。第二多商户项目的所有查询都必须按租户隔离。最简单的做法是表上带merchant_id字段然后每一条 SQL 都手动加WHERE merchant_id ?。但手写很容易漏更好的办法是用 MyBatis 拦截器在 SQL 执行前自动改写 WHERE 条件把当前租户 ID 拼进去。这样能保证“漏一条”的概率小很多但拦截器写不好也会变成大坑建议先在测试用例里覆盖各种 SQL 形态。第三避免 N1 查询。商城列表页如果循环查每个商品的店铺信息一条列表请求会变成几十条 SQL数据库很容易被打爆。用连表查询把关联数据一次性查出或者用 MyBatis 的collection映射嵌套结果。N1 是教科书级别的反面案例但在真实项目里反复出现尤其是新人接手时最容易犯。事务方面我坚持一个 Service 方法就是一个事务边界。方法内部会调用多个 Mapper 操作如果有一步失败整个方法回滚。注意事务不要跨越远程调用比如 HTTP 调另一个服务不然本地事务完全管不住别人只能靠最终一致性方案兜底。这也是许多“看上去很简单落地很难”的架构问题来源。5. 常见问题与排查技巧实录5.1 连接不上 MySQL时区、SSL、驱动版本三座大山我帮人排查过不少数据库连接问题90% 都集中在连接串。典型报错是SQLNonTransientConnectionException: Public Key Retrieval is not allowed原因就是 MySQL 8 默认插件caching_sha2_password在非 SSL 情况下需要先拿公钥。连接串加上allowPublicKeyRetrievaltrue并且useSSLfalse就能解决。另一个高频报错是The server time zone value xxx is unrecognized。MySQL 8 驱动要求明确时区要么在 MySQL 里执行SET GLOBAL time_zone 08:00要么在连接串里写serverTimezoneAsia/Shanghai。我推荐后者因为改数据库全局配置影响面太大连接串参数的作用范围更可控。还有一类问题是“在客户端工具里能连上Java 程序连不上”。优先排查服务器防火墙是否放行了 3306 端口。MySQL 用户是否只允许localhost登录需要改成%或指定 IP。驱动版本是否与 MySQL 服务端版本兼容比如 MySQL 5.7 用 8.x 驱动一般没问题但反过来经常失败。driver-class-name是否写错MySQL 8 是com.mysql.cj.jdbc.Driver老写法com.mysql.jdbc.Driver在新版本驱动里已经移除了。排查这类问题我习惯先在本机跑一个最小 demo确认连接串没问题再检查部署环境和网络能省很多弯路。5.2 连接泄露代码里最常见的隐形杀手连接泄露和内存泄漏一样都是“过程很安静结果很致命”的问题。表现是系统跑着跑着突然大量请求报错HikariPool-1 - Connection is not available, request timed out after 30000ms这个报错说明连接池里的所有连接都被借走了而且一直没有归还。原因大概率是某段代码拿到 Connection 后异常退出资源没有关闭。排查思路看日志找到哪个 SQL 长期持有连接。在数据源配置里开启leakDetectionThreshold当连接借用超过指定时间时HikariCP 会打印堆栈spring.datasource.hikari.leak-detection-threshold60000去 MySQL 里执行SHOW PROCESSLIST;看哪些连接Sleep时间很长基本就是“僵尸连接”。修复方式很简单所有使用Connection/PreparedStatement/ResultSet的地方全部用 try-with-resources不要在 finally 里手动关一半留一半。如果是 Spring 的JdbcTemplate/ MyBatis框架会帮忙释放但自己写原生 JDBC 片段时最容易漏尤其要检查有没有在 catch 分支里只打了日志而忘了关资源。5.3 慢 SQL 与连接池监控Spring Boot 里怎么做数据库访问层扛不住通常是慢 SQL 拖垮连接池。一个 SQL 跑 5 秒连接被占 5 秒连接池只有 10 个同一时刻超过 10 个这样的请求整个应用就瘫痪了。所以监控慢 SQL 和连接池状态比监控接口响应时间更重要。基础手段是开启 MySQL 慢查询日志SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1;这样记录超过 1 秒的 SQL定位到具体语句后用EXPLAIN看执行计划。type如果出现ALL说明是全表扫描大概率少了索引。最常见的问题是明明在 WHERE 条件里写了索引字段但因为对列做了函数运算比如WHERE YEAR(create_time) 2025导致索引失效。改成create_time 2025-01-01 AND create_time 2026-01-01就能走索引了。应用侧可以用 Spring Boot Actuator 暴露健康检查和指标再接入 Spring Boot Admin 做图形化展示。数据源的健康检查已经内置/actuator/health里能看到数据库是否可用。如果你用 Prometheus Grafana还能把 HikariCP 的指标拉出来比如hikaricp_connections_active活跃连接数hikaricp_connections_pending等待获取连接的请求数hikaricp_connections_timeout_total获取连接超时总数活跃连接长期逼近maximum-pool-size说明连接池太小或者 SQL 太慢必须先优化 SQL而不是一味调大连接池。连接池调大了数据库的负载并不会变小只是把压力延后且放大了。5.4 给第三方提供接口时的数据访问层设计思考经常有人问Spring Boot 对外提供的接口给第三方应该放在哪里是单独服务还是放在对应的业务服务里我的建议是分情况。如果只是内部系统之间调用放在同一个服务里没问题直接暴露一个 Controller 接口Service 层复用现有逻辑数据访问层基本不用动。如果这个接口是给外部第三方用的比如开放平台、跨境商城对接物流/支付我强烈建议单独拆一个服务或者至少在同一个项目里单独建一个模块。原因有三点接口版本管理更清晰。外部接口不能随便改字段单独服务可以按版本共存老版本继续跑新版本逐步升级。安全隔离更好做。对外开放的接口需要限流、验签、IP 白名单如果和内部接口混在一起越权风险很高Controller 层的防护策略也很难统一。数据访问层能独立伸缩。外部接口流量往往不可控万一某个第三方疯狂调接口打爆数据库不能让它牵连内部核心服务。独立服务的数据访问层也应该设计成“面向接口输出 DTO而不是直接暴露实体”。你可以用 MapStruct 把实体转成 DTO这样即使数据库表结构调整对外字段也不变。Controller 层防爬虫、防刷这块我用过的有效手段是加次数限制和签名校验每个第三方一个 appId 密钥请求统一带上签名服务端验签后处理再配合 Redis 计数器做 QPS 限流。数据库访问层面再设置一个独立的连接池和内部业务的数据源分开谁也别拖累谁。最后再分享一个小技巧无论服务怎么拆数据库访问层一定要保证所有 SQL 可追踪。我习惯在业务表里加trace_id字段或者至少在日志里打印 SQL 和参数这样第三方说“为什么我的订单状态没更新”时你能快速定位到具体请求。排查问题的时间往往比你写代码的时间更值钱。写到这里我自己的感受挺深。这些年见过太多同学把 MyBatis、JdbcTemplate 用得飞起但一遇到连接池耗尽、时区报错、事务失效就束手无策。技术债不是一天积累的而你对 JDBC 的理解深度决定了你在这些故障面前是慌乱还是从容。别嫌 JDBC 老它依然是整个 Java 数据库生态的基石值得你花一个下午亲手写一遍。

相关新闻

华为荣耀笔记本演示机解锁:恢复F10智能还原与系统重装指南

华为荣耀笔记本演示机解锁:恢复F10智能还原与系统重装指南

用过荣耀和华为笔记本的朋友应该都清楚,正经零售机和门店演示样机完全是两码事。演示机样机在出厂时会刷入一套专门的演示系统,桌面自动播广告、登录不了个人账号、很多设置项是灰色的,甚至部分机器连F10智能还原都被改得进不去。这些年我帮亲…

2026/10/8 19:50:18 阅读更多 →
OpenClaw本地部署保姆级指南:环境准备、模型对接与技能排雷

OpenClaw本地部署保姆级指南:环境准备、模型对接与技能排雷

最近OpenClaw在AI代理圈的热度高得离谱,群里天天有人问:这玩意儿到底怎么装?为什么照着教程一步步来,还是各种报错?作为把OpenClaw在Windows、Linux、还有手机上各折腾过一遍的人,我可以很负责地说&#xf…

2026/10/8 19:50:18 阅读更多 →
计算机系统原理自考备考:真题三轮复盘法+考点详解避坑指南

计算机系统原理自考备考:真题三轮复盘法+考点详解避坑指南

简介:2025年10月自考13015《计算机系统原理》真题答案详解考点,是一份面向自考考生和计算机初学者的复习资料,该课程属于计算机科学与技术自考本科的核心科目,覆盖从基础概念到综合计算的完整复习链条。内容包含选择题、填空题、名…

2026/10/8 19:50:18 阅读更多 →

最新新闻

智能体技能插件ponytail实战:从安装调优到排障的完整记录

智能体技能插件ponytail实战:从安装调优到排障的完整记录

第一次看到 ponytail 这个名字的时候,我下意识以为是讲马尾辫造型的Demo项目。直到把技能插件装进智能体运行目录、跑通第一轮真实任务之后,才意识到这个命名确实讲究:马尾辫的核心是把散乱发丝聚成一束,扎得结实但随时能解开——…

2026/10/8 20:19:30 阅读更多 →
超延时摄影核心hyperframes:从拍摄原理到后期稳定实操

超延时摄影核心hyperframes:从拍摄原理到后期稳定实操

如果你拍过延时摄影,又觉得固定机位拍出来的画面不够带感,那么你大概率迟早会撞上这个词:hyperframes。它不是什么高深莫测的新技术,其实是超延时摄影(Hyperlapse)里最核心的一环——那些沿着运动轨迹逐帧拍…

2026/10/8 20:19:30 阅读更多 →
企业搜索范式迁移:从关键词检索到RAG增强检索实战

企业搜索范式迁移:从关键词检索到RAG增强检索实战

这两年要是有人跟我聊企业搜索,我第一反应已经不太是“你们 Elasticsearch 里挂了几个索引”这种话了。我通常先反问一句:你们要的是把相关文档列出来,还是让系统直接把回答摆到桌面上?这个差别看着不大,实际是两种搜索…

2026/10/8 20:19:30 阅读更多 →
从圆周率到AI编程代理:pi coding agent实操与PI整定经验迁移

从圆周率到AI编程代理:pi coding agent实操与PI整定经验迁移

部门群里有人发了个消息,只有两个字:“pi”。硬件组的同事秒回“你说的是SI里的PI,电源完整性吧”;做控制的同事说“pi不是比例积分控制器吗”;一个数学专业出身的在角落默默念“3.1415926...”。我没参与讨论&#xf…

2026/10/8 20:19:30 阅读更多 →
OpenAI DevDay深度拆解:GPT-6.1 Sol、Codex与Agents API实战指南

OpenAI DevDay深度拆解:GPT-6.1 Sol、Codex与Agents API实战指南

1. 从DevDay的"梭哈"说起:这次发布会到底在赌什么OpenAI的DevDay历来是开发者圈子的"春晚",但今年这场发布会的气氛有点微妙。官方一口气甩出了一堆更新——GPT-6.1 Sol、Codex的全面升级、Agents API的正式开放,还有一堆…

2026/10/8 20:19:30 阅读更多 →
Spring Boot + MyBatis 学生信息管理系统实战:从数据库设计到登录鉴权与分页查询

Spring Boot + MyBatis 学生信息管理系统实战:从数据库设计到登录鉴权与分页查询

简介:本资源是一套基于Java与MySQL开发的学生信息管理系统课程设计项目,面向计算机相关专业学生、Java Web初学者及需要完成课程设计或毕业设计的开发者。项目在IntelliJ IDEA 2020.3.3环境下开发,采用JavaBean、Servlet与DAO分层架构&#x…

2026/10/8 20:18:29 阅读更多 →

日新闻

抖音用户主页视频数据爬虫详解:点赞、收藏、分享字段抓取与 TaoToken 统一 Key 配置

抖音用户主页视频数据爬虫详解:点赞、收藏、分享字段抓取与 TaoToken 统一 Key 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 0:00:03 阅读更多 →
AI 编程 Trae 国内版与国际版一篇讲透:TaoToken 统一 Key 接入实测

AI 编程 Trae 国内版与国际版一篇讲透:TaoToken 统一 Key 接入实测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 0:00:06 阅读更多 →
Claude Desktop 配置第三方推理接口教程:用 TaoToken 统一 Key 打通 API 调用

Claude Desktop 配置第三方推理接口教程:用 TaoToken 统一 Key 打通 API 调用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 0:00:07 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 10:10:36 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 14:34:12 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/7 13:34:55 阅读更多 →