SpringBoot JDBC连MySQL实战:配置、连接池与避坑

发布时间:2026/10/11 20:33:38

SpringBoot JDBC连MySQL实战:配置、连接池与避坑 简介面向准备使用SpringBoot连接MySQL的Java开发者这套资料提供了从零搭建JDBC数据访问层的完整方案。压缩包内包含可直接运行的SpringBoot演示项目工程中通过控制器、实体类与启动类配合建表脚本和多种配置文件完整演示了驱动加载、连接获取以及针对数据库的增删改查操作便于直观理解连接数据库的核心步骤。同时资源还整合了MySQL安装程序与运行依赖库例如安装引导文件、动态链接库以及必要的环境组件省去自行寻找安装包的繁琐步骤也能避免因驱动与版本不匹配而引发的连接报错。整份资源以RAR压缩包形式发布共包含136个文件大小约637.42MB文件构成以动态库、编译类、源码、数据库脚本、配置文件及安装工具为主覆盖了从开发调试到部署运行的关键部分目录中保留了工程源码、数据库脚本、配置样例与配套工具的分层结构便于逐项对照学习或抽取复用。已有598人学习下载适合初学Java后端的学生、课程设计者以及需要快速搭建数据库连接样例的开发者。1. SpringBoot 用 JDBC 连 MySQL为什么绕不开这一层很多团队一上来就上 MyBatis 或 JPA反而把最基础的 JDBC 忽略了。实际上 SpringBoot 里的 spring-boot-starter-jdbc 就是官方对 JDBC 的封装JdbcTemplate 则是 Spring 提供的最轻量数据库操作工具。它不引入 ORM 映射、不搞 SQL 生成器你写的每一句 SQL 都是原样发给 MySQL 执行的。对 SQL 有绝对控制欲的开发者或者表结构复杂、查询语句需要手动调优的场景JDBC 这一层值得认真用。本文把这套方案从 MySQL 安装、驱动依赖、连接池参数到高频踩坑完整串一遍新手可以照步骤复现老手可以重点看第 5 章的参数匹配和第 6 章的封装技巧。标题里说的源码和安装包本质上就是本文这套代码结构加 MySQL 安装程序按下面的章节拼起来就能跑通。2. 环境准备MySQL 安装、连接驱动与最小依赖配置2.1 安装 MySQL 并确认基础参数先用官方安装包把 MySQL 装好注意 8.x 和 5.7 的驱动配置有差异建议新项目直接用 8.0 以上版本。安装时记得勾选“Developer Default”或者自定义安装保证 mysqld 服务能开机自启。装完后命令行执行下面这条命令确认能进去mysql -uroot -p输入密码后如果看到mysql提示符就说明服务正常。接着执行两条关键 SQL确认字符集和认证插件状态SHOW VARIABLES LIKE character_set_server; SHOW VARIABLES LIKE default_authentication_plugin;第一条应该返回utf8mb4如果不是在 my.iniWindows或 my.cnfLinux里的[mysqld]段加上character-set-serverutf8mb4。第二条在 8.0 里通常返回caching_sha2_password这会影响 Java 驱动的连接参数后面会专门讲。这一步不用追求数据库端配置一次到位但字符集和认证插件这两个东西直接决定后续 JDBC URL 怎么写。2.2 Maven 依赖最少需要哪几个坐标SpringBoot 项目接入 JDBC 连 MySQLpom.xml 里加两个依赖就够了。第一个是 spring-boot-starter-jdbc它会把 JdbcTemplate、DataSource 基础类和 HikariCP 连接池一起带进来第二个是 MySQL 驱动8.0.31 之后版本号的 groupId 变成了com.mysql注意不要写错成mysql:mysql-connector-java那种老坐标。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency驱动依赖的 scope 写成 runtime 即可编译期不需要直接引用驱动类只有运行时才需要把驱动 jar 加载进连接池。spring-boot-starter-jdbc 里已经包含 HikariCP不需要额外引入连接池依赖。注意如果项目里同时有 mybatis-spring-boot-starter它会自动带上 spring-boot-starter-jdbc此时不要重复添加否则可能出现数据源初始化顺序冲突。2.3 application.yml 配置连接 URL 里藏着的关键参数配置写在 application.yml 里SpringBoot 会自动把它装配成 HikariDataSource。下面是最小可用配置每行参数后面我都标了作用spring: datasource: url: jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltruerewriteBatchedStatementstrue username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driverurl 里那串参数是关键。useUnicodetruecharacterEncodingutf8保证中文写入不乱码serverTimezoneAsia/Shanghai解决时区报错allowPublicKeyRetrievaltrue是 8.0 驱动连接 caching_sha2_password 用户时的必备参数不加就报 Public Key Retrieval is not allowedrewriteBatchedStatementstrue让批量插入真正走 MySQL 的批量执行协议性能提升明显后面章节详细展开。driver-class-name在 8.x 驱动里必须是com.mysql.cj.jdbc.Driver老款的com.mysql.jdbc.Driver已经移除了。2.4 验证连接写个最小示例查版本号配置完成之后最快的验证方式不是启动整个 Web 服务而是写一个命令行启动类用 JdbcTemplate 查数据库版本。这一步能快速区分“配置问题”和“业务代码问题”。Component public class ConnectionChecker implements CommandLineRunner { private final JdbcTemplate jdbcTemplate; public ConnectionChecker(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } Override public void run(String... args) { String version jdbcTemplate.queryForObject(SELECT VERSION(), String.class); System.out.println(MySQL 版本: version); } }SpringBoot 启动时 CommandLineRunner 会自动执行 run 方法。如果看到输出类似MySQL 版本: 8.0.36说明数据源、驱动、URL 参数全部打通了。这里有个细节JdbcTemplate 不需要手动 newSpringBoot 检测到 classpath 里存在org.springframework.jdbc.core.JdbcTemplate且配置了 DataSource 后会自动注册直接构造器注入即可。3. JdbcTemplate 核心操作增删改查、批处理与自增主键回填3.1 查queryForObject 和 query 的区别JdbcTemplate 的查询方法很多但核心就两类单值查询和列表查询。单值查询用queryForObject适合COUNT(*)、SELECT MAX(id)这类聚合或者按主键查单条实体列表查询用query传入 RowMapper 逐行映射。看下面这个完整的用户表操作public User findById(Long id) { String sql SELECT id, name, age, email FROM user WHERE id ?; return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(User.class), id); } public ListUser findByAgeGreaterThan(int age) { String sql SELECT id, name, age, email FROM user WHERE age ? ORDER BY id DESC; return jdbcTemplate.query(sql, new BeanPropertyRowMapper(User.class), age); }BeanPropertyRowMapper是 Spring 提供的默认行映射器前提是表字段用下划线命名且实体类用驼峰命名比如real_name对应realName它会自动做驼峰转换。如果不满足这个约定关系就得手写 RowMapper 的 lambda比如(rs, rowNum) - new User(rs.getLong(id), ...)。queryForObject 有个重要行为如果查不到数据会抛 EmptyResultDataAccessException而不是返回 null业务代码里要么用 try-catch 包一层要么改用query(...).stream().findFirst()避免异常噪音。3.2 增改删update 方法完成所有写操作写操作在 JdbcTemplate 里统一叫update无论 INSERT、UPDATE 还是 DELETE 都走这个方法返回受影响的行数。常见误用是用execute去执行 INSERTexecute其实适合 DDL 语句业务上不推荐。看下面的写入代码public int insertUser(String name, Integer age, String email) { String sql INSERT INTO user (name, age, email) VALUES (?, ?, ?); return jdbcTemplate.update(sql, name, age, email); }update的第二个参数是变长参数顺序必须和 SQL 里的?完全一致。注意如果某个字段是 null这里不要传 Java 的 null 直接进去可以用new Object[]{name, age, email}的写法配合ArgTypePreparedStatementSetter显式指定类型否则某些驱动可能无法识别 null 对应的 JDBC 类型。实际项目里遇到插入字段很多的情况建议把 SQL 拆成几段拼接避免一长串?对应关系搞混。update 返回 0 不等于执行失败只代表影响行数为 0比如更新一条不存在的数据。3.3 批处理batchUpdate 的正确打开方式批量插入是 JDBC 相对 ORM 框架最有优势的场景。JdbcTemplate 提供了batchUpdate方法配合BatchPreparedStatementSetter逐条绑定参数。看这个用户批量写入的例子public int[] batchInsertUsers(ListUser users) { String sql INSERT INTO user (name, age, email) VALUES (?, ?, ?); return jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { Override public void setValues(PreparedStatement ps, int i) throws SQLException { User u users.get(i); ps.setString(1, u.getName()); ps.setInt(2, u.getAge()); ps.setString(3, u.getEmail()); } Override public int getBatchSize() { return users.size(); } }); }注意两点。第一setValues里的索引从 1 开始对应 SQL 里的第一个?别习惯性地从 0 开始。第二JDBC URL 里如果没加rewriteBatchedStatementstrueMySQL 驱动会一条一条地发送 SQL性能跟循环单插没区别加了之后驱动会把多条 INSERT 合并成一条多 VALUES 的语句发送数据量越多收益越大。这个参数在 2.3 节已经从 URL 写进去了但很多人忽略它的存在导致 batchUpdate 没起到优化作用。3.4 自增主键回填KeyHolder 解决取值问题插入数据后拿主键是高频需求。很多人先 SELECT LAST_INSERT_ID() 再查一次在并发场景下可能拿到别的连接插入的主键。正确做法是用 Statement.RETURN_GENERATED_KEYS 机制配合 KeyHolder 实现public Long insertUserWithKey(User user) { String sql INSERT INTO user (name, age, email) VALUES (?, ?, ?); KeyHolder keyHolder new GeneratedKeyHolder(); jdbcTemplate.update(connection - { PreparedStatement ps connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, user.getName()); ps.setInt(2, user.getAge()); ps.setString(3, user.getEmail()); return ps; }, keyHolder); Number key keyHolder.getKey(); return key null ? null : key.longValue(); }这段代码用了 PreparedStatementCreator 匿名内部类和普通 update 的区别是创建 PreparedStatement 时传了Statement.RETURN_GENERATED_KEYS标志驱动会额外返回生成的自增主键。keyHolder.getKey()拿到的 Number 对象直接转 Long 即可。这里有个小坑如果表的自增列不是主键或者使用INSERT ... ON DUPLICATE KEY UPDATE语法getKey()可能返回 null需要结合具体 SQL 结果评估。4. 连接池与参数调优HikariCP 和 MySQL 端怎么配合4.1 HikariCP 为什么是默认选择SpringBoot 2.x 之后把 HikariCP 作为默认连接池它不是因为它“好像听说过”而是因为性能确实好。HikariCP 的字节码优化让它比老牌的 C3P0、DBCP 都要快而且配置项少、默认值合理。大部分项目根本不需要调参默认配置就能稳定跑很久。但如果你只配了spring.datasource.url那几行连接池用的全是内置默认值生产环境往往需要调整。连接池参数和 MySQL 服务端参数是配套的只调一边可能导致连接被服务端杀掉或者池内连接长期闲置这也是 4.3 节要讲的匹配问题。4.2 完整连接池配置与参数含义下面是一份生产可用的 HikariCP 配置放在 application.yml 的 datasource 节点下。每个核心参数的作用和推荐值我在表格里单独说明spring: datasource: url: jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 你的密码 hikari: pool-name: DemoHikariPool minimum-idle: 5 maximum-pool-size: 20 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 connection-test-query: SELECT 1参数推荐值说明minimum-idle5池中保持的最小空闲连接数低于此值会逐渐补充maximum-pool-size20池中最大连接数并不是设置越大越好MySQL 端最大连接数限制要先确认connection-timeout30000获取连接的最大等待毫秒数超过就抛 SQLTransientConnectionExceptionidle-timeout600000空闲连接存活时间10 分钟没有使用的连接会被回收max-lifetime1800000连接最大存活时间30 分钟强制更换连接必须小于 MySQL 的 wait_timeoutconnection-test-querySELECT 1获取连接时的活性检测语句HikariCP 默认会做显式配置更稳妥connection-timeout 太短会造成高峰期获取连接频繁失败报错信息通常是Connection is not available, request timed out after 30000ms。max-lifetime 是最容易被忽略的参数如果 MySQL 的 wait_timeout 是 8 小时而连接池 max-lifetime 默认是 30 分钟那没问题但如果有人把 max-lifetime 调到 8 小时以上就会间歇性报Communications link failure。连接池参数要奉行“池内连接寿命小于数据库端超时时间”的原则MySQL 的 interactive_timeout 和 wait_timeout 都要看一眼。4.3 MySQL 端参数wait_timeout 与连接池寿命的匹配登录 MySQL 执行这条命令SHOW VARIABLES LIKE wait_timeout;默认值是 28800 秒也就是 8 小时。连接池里 max-lifetime 30 分钟远小于这个值连接不会因为服务端超时被杀掉。但如果你的 DBA 把 wait_timeout 调成了 60 秒有些云数据库默认较短连接池里的空闲连接就会在 60 秒后被 MySQL 主动断开。连接池不知道自己持有的连接已经失效取到它时执行查询就会抛Communications link failure。解决方法是保证max-lifetime wait_timeout同时把idle-timeout也调低让在池子里睡太久的连接提前被回收。我一般会把 wait_timeout 所在的 MySQL 参数确认一遍再回来反推连接池数值顺序反了容易踩坑。另外一个配套参数是 MySQL 端的max_connections执行SHOW VARIABLES LIKE max_connections查看。如果数据库最大连接数是 200你的应用有 4 个实例连接池都配 50高峰期就超了。连接池配太大不仅浪费内存还会把数据库连接数打满导致其他应用连不上。生产环境建议连接池上限控制在max_connections / 应用实例数的 60% 左右留出运维和定时任务的余量。4.4 活跃连接数监控HikariCP 暴露的指标连接池是否健康不能靠猜HikariCP 在 SpringBoot Actuator 里暴露了现成指标。引入spring-boot-starter-actuator之后访问/actuator/metrics/hikaricp.connections.active可以看到当前活跃连接数hikaricp.connections.pending可以看到等待获取连接的线程数。如果 active 长期接近 maximum-pool-size 且 pending 不为 0说明连接池不够用或者 SQL 执行太慢占用连接太久。这时候先看慢 SQL再考虑加连接池上限否则加了池子也只是把问题往后推。5. JDBC 连 MySQL 避坑指南五个高频问题的现象与修复5.1 Public Key Retrieval is not allowed现象应用启动或第一次执行查询时报Public Key Retrieval is not allowed异常堆栈指向 MySQL 8.0 的 caching_sha2_password 认证。原因MySQL 8.0 默认认证插件是 caching_sha2_password客户端第一次连接时需要通过 RSA 公钥加密传输密码。驱动默认不允许从服务器动态获取公钥需要显式开启。解决在 JDBC URL 加allowPublicKeyRetrievaltrue。如果公司安全策略不允许 RSA 公钥动态获取可以在 MySQL 侧把用户的认证插件改成mysql_native_password执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码;但这需要 MySQL 端权限而且 8.0.34 之后的版本对 native_password 的兼容性在收紧建议还是用 allowPublicKeyRetrieval 方案。5.2 时区错误Server returns invalid timezone现象连接时报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized一堆乱码时区名。原因MySQL 的 time_zone 系统变量返回的是服务器本地时区描述JDBC 驱动无法识别特别是 Windows 下常见。解决URL 里加serverTimezoneAsia/Shanghai注意要加在 JDBC 连接参数的位置不要加在数据库字段上。也可以在 MySQL 执行SET GLOBAL time_zone 08:00但重启后可能失效最稳妥还是 URL 参数。顺带提醒serverTimezoneAsia/Shanghai里的斜杠在 yml 文件里不需要转义但如果是 properties 文件斜杠要按普通字符处理别多想。5.3 空闲连接被 MySQL 杀掉导致 Communications link failure现象应用长时间闲置后第一次访问报Communications link failure重试一次又正常了并且报错时间点符合固定周期。原因MySQL 端 wait_timeout 过期杀掉了该连接连接池不知道连接已死把失效连接交给了业务线程。解决把 HikariCP 的 max-lifetime 调整到低于 MySQL wait_timeout同时在连接池配置里加connection-test-query: SELECT 1。注意 HikariCP 对失效连接的检测默认是开启的但 max-lifetime 如果不匹配检测逻辑也救不了。这个问题的排查方法是同时查 MySQLSHOW VARIABLES LIKE wait_timeout和应用的连接池配置两边的数字要能对得上。5.4 批量插入慢到怀疑人生现象batchUpdate 插入一万条数据耗时几十秒单条插入反而更快。原因驱动没有批量执行逐条发送 SQL 到 MySQL。最常见原因有两个URL 没加rewriteBatchedStatementstrue或者代码里 batchUpdate 循环的次数太小批量合并没发挥威力。解决URL 加rewriteBatchedStatementstrue这条参数让驱动把多条 INSERT 重写成一条多 VALUES 语句。单批次的 size 不要太小建议单批 500 到 1000 条。如果表有多个索引插入速度还会受索引维护影响可以评估是否先删索引插完再建但这是另一个维度的优化不要和连接参数混淆。5.5 中文乱码数据库乱码和连接乱码要分开查现象插入的中文数据查询出来是问号?或者一堆???。原因乱码有三层可能性数据库表字符集不是 utf8mb4连接 URL 没指定字符集或者客户端本身编码不对。排查顺序是从存储端到传输端。解决先在 MySQL 里执行SHOW CREATE TABLE 表名看表的字符集必须是utf8mb4再确认 JDBC URL 里有characterEncodingutf8这个参数写成 utf8 实际映射到 utf8mb4不需要写utf8mb4最后检查代码文件的编译编码Maven 工程在 pom.xml 里加project.build.sourceEncodingUTF-8/project.build.sourceEncoding。有人为了省事在连接 URL 里写characterEncodingutf8mb4部分驱动版本会直接报错正确写法就是utf8。6. 再进一步把 JDBC 访问封装成可复用 DAO 的三个技巧到第 5 步为止这套方案已经能稳定跑业务但项目里表多了之后每个 DAO 都写一套 RowMapper 和 SQL 模板会很啰嗦。我常用三个技巧让 JDBC 代码更好用。技巧一是封装一个 BaseDao把 JdbcTemplate 注入和 common 的查询方法收敛起来子类只写 SQL 和实体映射技巧二是给 RowMapper 抽一个公共 lambda字段多的表可以配合 Record 或 MapStruct 减少手写 getter/setter技巧三是利用 JdbcTemplate 的queryForStream处理超大结果集避免一次性把所有数据加载进内存这个对报表导出场景特别有用。public abstract class BaseDaoT { protected final JdbcTemplate jdbcTemplate; protected BaseDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } protected T queryOne(String sql, RowMapperT mapper, Object... args) { return jdbcTemplate.query(sql, mapper, args) .stream().findFirst().orElse(null); } protected ListT queryList(String sql, RowMapperT mapper, Object... args) { return jdbcTemplate.query(sql, mapper, args); } }事务的坑我最后提醒一句BaseDao 里每个方法用Transactional是安全的但要注意它只对 Spring 代理对象生效。同一个类内部直接调用带事务的方法事务会失效因为走的是 this 调用而不是代理这个问题排查起来很隐蔽。另外事务只包装了 DAO 方法调用如果事务边界要覆盖 Controller 层的多个操作注解要放在 Service 层而不是 DAO 层。这个教训我自己踩过某次把事务注解写在 DAO 方法上上层两个写操作之间一旦抛异常第一条数据已经入库了回滚无效。从那以后我坚持所有事务注解一律放在 Service 层公共方法上DAO 层只做连接和 SQL 的执行。希望这个习惯能帮到你少走一次弯路。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/11 20:33:38

颈椎CT骨骼分割数据集实战:三轴2D切片与可视化代码解析

简介:本资源面向医学图像分割方向的算法工程师、研究生及深度学习爱好者,提供一套完整的人体颈椎CT骨骼分割数据集,可用于训练与验证2D分割模型,也适合作为医学影像入门练手项目。数据按横断面、冠状面、矢状面三个方向切分&#…

2026/10/11 20:33:38

基于Web的教师调停课系统:双部门审核与SQL Server建表实战

简介:这份毕业设计文档面向高校计算机相关专业学生与教学管理人员,围绕基于Web的教师调停课系统展开管理分析与设计,帮助读者理解如何用信息化手段替代传统人工调课流程,解决审批环节繁琐、信息传递滞后与调课冲突等问题。资源包内…

2026/10/11 20:28:38

TC2406部署中心实操:两层/四层客户端与BMIDE安装指南

简介:面向PLM系统实施与运维人员,提供一份围绕部署中心安装Teamcenter核心组件(含TC2406.2四层客户端与BMIDE)的完整操作指引。资料按真实部署顺序展开:先介绍主机名替换与两层客户端组件添加、脚本生成、部署和登录验…

2026/10/11 22:44:15

Codex驱动的Windows C盘空间审计方法论

1. 项目概述:这不是清理垃圾,而是一场系统级空间审计“我用 Codex,给 C 盘腾出 300 多 GB”——这句话在技术社区刷屏时,我第一反应不是惊讶,而是立刻打开任务管理器看了眼自己机器上那个常年卡在98%的C盘使用率。很多…

2026/10/11 22:44:15

MCP协议与Skills架构:Agent工程化落地实战指南

1. 项目概述:这不是又一个“AI概念课”,而是一份可执行的Agent工程实践路线图“MCPAgent Skills”这个组合词在2025年下半年突然密集出现在多个技术社区的讨论帖、GitHub star飙升的仓库名、以及某主流视频平台的算法推荐流里。它不是某个新发布的框架&a…

2026/10/11 22:44:15

LangChain 1.3实战:PDF字段提取与跨文档比对的生产级方案

1. 这不是“又一个LangChain教程”,而是一份能让你真正写出可用AI应用的实战手记 你点开这个标题,大概率是因为—— 刚在B站搜“LangChain 教程”,前五页全是“30分钟入门”“保姆级讲解”“从零开始”,结果点进去发现&#xff1…

2026/10/11 22:44:15

Cheat Engine 6.8.1 源码解析:内存扫描与调试器改造实战

简介:Cheat Engine 6.8.1 源码包面向游戏逆向、内存调试与安全分析方向的学习者与开发者,提供动态内存扫描、读写与指针追踪等核心机制的完整实现参考。压缩包共 1523 个文件,约 8.45MB,以 426 个 pas 与 181 个 h 文件构成 Pasca…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑