Hibernate Criteria API实战:告别HQL拼接,搞定动态查询与多表关联

发布时间:2026/10/11 4:32:40

Hibernate Criteria API实战:告别HQL拼接,搞定动态查询与多表关联 上周在重构某后台管理系统的查询模块时同事把原来一长串手工拼接的 HQL 丢给我让我帮忙看看怎么优化。那一段代码大概有七八个 if 判断每个判断都往 StringBuffer 里追加条件再加一个 and、加一个 or 看着就头疼。我接手后直接换成了 Hibernate 的 Criteria API把整个条件组装逻辑精简了一半以上。后来想想这个系列写到现在确实该系统性聊聊 Criteria API 了。这篇东西面向的是正在用 Hibernate 做 Java 持久层开发的开发者尤其是那些在项目里频繁遇到动态查询、多条件筛选、统计报表需求的同学。我会把 Criteria API 的基本用法、动态拼条件、排序分页、聚合统计、多表关联都过一遍再补上几个我实际踩过的坑希望对你有用。1. 为什么有了 HQL 我还会用 Criteria API1.1 动态查询的痛点字符串拼接的维护噩梦很多刚到项目的开发者会习惯性地用 HQL 做查询因为它在写法上跟 SQL 几乎一致找人教容易、上手成本低。但一旦查询条件变得复杂HQL 的劣势就非常明显。以我开头说的那个后台系统的用户列表查询为例页面上的筛选条件包括用户名、所属部门、状态、创建时间范围、角色类型、是否有订单记录等每个条件都可能选中也可能不选。用 HQL 去拼你八成会写出这样的代码StringBuilder hql new StringBuilder(from Customer c where 11 ); if (StringUtils.hasText(name)) { hql.append(and c.name like :name ); } if (status ! null) { hql.append(and c.status :status ); } if (startTime ! null) { hql.append(and c.createdAt :startTime ); } // ... 后面还有好几个 if Query query session.createQuery(hql.toString()); query.setParameter(name, % name %);这段代码本身能跑但问题在于条件越多字符串拼接的脑力负担越重。你需要时刻注意空格、and/or 的优先级、参数名冲突、字符串里有没有漏掉条件。更麻烦的是如果后来要支持状态等于 A 或 B你在字符串里插入一组括号稍微一个不小心整个 SQL 语义就错了。这种代码我见过无数次每次改需求都要提心吊胆。1.2 从拼字符串到描述查询对象Criteria API 的核心思想是它不让你去拼查询语句而是让你以往查询对象里添加约束条件的方式来构造查询。你可以把它想象成一张点单纸先创建一张空白纸张然后不断往上面追加要求比如用户名模糊匹配创建时间在某个范围内最后把这张纸交给厨师也就是调用 list() 或 uniqueResult()一次性取出结果集。因为查询条件是用对象和方法一层层加进去的所以天然不需要处理 and、or、括号这类字符串问题动态增删条件也特别顺手。用 Criteria API 重写上面的动态查询大概是这样的Criteria criteria session.createCriteria(Customer.class); if (StringUtils.hasText(name)) { criteria.add(Restrictions.like(name, name, MatchMode.ANYWHERE)); } if (status ! null) { criteria.add(Restrictions.eq(status, status)); } if (startTime ! null) { criteria.add(Restrictions.ge(createdAt, startTime)); } ListCustomer list criteria.list();你会发现每一行都是为了添加一个独立的约束条件不需要去考虑句子的整体拼接。条件之间的逻辑关系由 Criteria API 内部组合默认是 and 关系你要 or 就用 Disjunction 显式表达后面我会专门讲。这种可读性、可维护性的提升才是很多老手坚持用 Criteria API 的真正理由。2. 从 Session 到结果Criteria 查询的完整流水线2.1 核心接口之间的关系一批你每天都会用到的对象要想把 Criteria API 用顺先得弄清楚它涉及的几个核心接口。我按使用频率排一下接口/类作用常用入口Criteria查询对象本体代表一次针对某个实体类的查询session.createCriteria(...)Criterion单个查询条件是各种约束的统称Restrictions 工具类生成Restrictions条件工厂静态方法产出各种 Criterioneq/ne/gt/ge/lt/le/like/between/in/isNull/sizeEqOrder排序对象Order.asc() / Order.desc()Projections投影与聚合工厂产出 Projection 对象rowCount/avg/sum/max/min/projectionListProjection查询字段投影或聚合函数描述criteria.setProjection(...)DetachedCriteria可以在 Session 之外创建的 CriteriaDetachedCriteria.forClass(...)这些接口共同构成了整个查询引擎。你可以把 Criteria 当作一次查询的总容器Criterion 是容器里的每一条规则Restrictions 就是帮你快速生成这些规则的工具。在这个基础上Order 负责排序Projections 负责控制查哪些列、要不要统计。2.2 第一次用 Criteria最简单的等值查询与结果遍历假设我们有一个 Customer 实体对应 customers 表主键 id字段包含 name、email、status、age、createdAt。最简单的 Criteria 查询是把所有客户查出来Session session sessionFactory.openSession(); try { Criteria criteria session.createCriteria(Customer.class); ListCustomer customers criteria.list(); for (Customer customer : customers) { System.out.println(customer.getName()); } } finally { session.close(); }这段代码等价于 HQL 的from Customer等于 SQL 的select * from customer。别看它简单它体现了 Criteria 查询最基本的创建与执行模型先拿到 Session再通过 createCriteria 获得查询对象最终通过 list() 方法执行并返回所有匹配记录。如果你的查询预期只有一条结果可以用uniqueResult()但要注意如果实际返回多条uniqueResult() 会抛出异常所以它适合用在主键查询这种场景。这里稍微提醒一下Criteria 的 list() 返回的是该实体类的完整对象集合。也就是说在没使用投影的情况下Hibernate 会把每个字段都映射到 Customer 实例上这也就意味着查询相当于select *。如果你只需要其中两三个字段别着急后面投影那一节我会讲怎么裁剪字段。2.3 持久化管理list() 之后的对象在什么状态不少初学者会忽略一个细节通过 Criteria 查出来的对象是托管态对象。它们与当前 Session 关联如果你在 Session 内修改了对象的属性并提交事务修改会被同步到数据库。这个特性在某些批量更新场景下很方便但也带来了风险。比如你只是要做展示却误改了某个属性事务提交时数据就变了。我一般在展示型查询后立即结束 Session 生命周期或者把数据复制到 VO/DTO 中避免后续在视图层因为持久化上下文未关闭而产生难以追踪的副作用。这个习惯跟用 HQL 查询其实是一致的但因为 Criteria 更像对象操作很多人会对它到底是不是托管对象产生错觉所以在这里打个预防针。3. 条件筛选的常用手法与组合逻辑3.1 Restrictions 全家桶等值、模糊、范围、空值判断Restrictions 是 Criteria API 里最常用的一个工具类。我把它常用的方法整理成了一个表你在写代码时可以对照着查方法作用使用示例eq等于Restrictions.eq(status, ACTIVE)ne不等于Restrictions.ne(status, CLOSED)gt / ge大于 / 大于等于Restrictions.ge(age, 18)lt / le小于 / 小于等于Restrictions.le(createdAt, endTime)like模糊匹配Restrictions.like(name, 张, MatchMode.ANYWHERE)between范围匹配Restrictions.between(age, 18, 60)in集合匹配Restrictions.in(status, Arrays.asList(A,B))isNull / isNotNull空值判断Restrictions.isNull(email)isEmpty / isNotEmpty集合属性判空Restrictions.isEmpty(orders)sizeEq / sizeGt集合大小判断Restrictions.sizeEq(orders, 3)这里我想多聊一下 like 的 MatchMode它有 START、END、ANYWHERE 和 EXACT 四个选项分别对应 xx%、%xx、%xx%和精确匹配。在动态查询里我几乎总是用MatchMode.ANYWHERE因为它最符合前端输入框的直觉——用户输入什么片段我都去包含匹配。值得注意的是在早期版本的 Hibernate 里like 的匹配模式也可以直接在字符串里写通配符例如Restrictions.like(name, %张%)但用 MatchMode 会让意图更清晰也避免了特殊字符转义的麻烦。between 方法也是一个值得留意的点。它包含边界也就是大于等于 lower 且小于等于 upper。如果实际的业务边界是开区间你最好用 ge lt 组合来写否则容易查多或查少。比如查询这份报表只统计 2024 年 1 月整月的数据用 between 就行但要统计从起始日零点到当天最后一刻就要小心处理时间精度问题我习惯用ge(start) lt(nextDay)避免把第二天的数据算进来。3.2 条件之间的 and/or 组合Conjunction 与 Disjunction默认情况下criteria.add() 添加的所有条件都是以 and 关系组合的。但如果你的查询逻辑里有或者关系那就要用到 Conjunction 与 Disjunction也就是 and 块和 or 块。举个例子筛选条件是(年龄大于等于 18 且状态为 ACTIVE) 或者 (会员等级为 VIP)。写成代码可以是Criteria criteria session.createCriteria(Customer.class); Conjunction andBlock Restrictions.conjunction(); andBlock.add(Restrictions.ge(age, 18)); andBlock.add(Restrictions.eq(status, ACTIVE)); Disjunction orBlock Restrictions.disjunction(); orBlock.add(andBlock); orBlock.add(Restrictions.eq(vipLevel, VIP)); criteria.add(orBlock);这种写法非常直观相当于把条件块一层层嵌套。相比在 HQL 字符串里加括号它的优点是不需要担心括号匹配结构一目了然。嵌套多层时代码读起来是分层的而不是一长串堆叠的字符串。在动态拼装条件的场景中我还会配合一个习惯先把所有可能出现的 Criterion 放进一个ListCriterion等到所有 if 判断结束后统一把列表里的条件 add 到 criteria 中或者用一个 Conjunction 包起来。这样避免了在代码里东一锤子西一棒子地不断调用 criteria.add()方便统一管理与复用。ListCriterion conditions new ArrayList(); if (StringUtils.hasText(name)) { conditions.add(Restrictions.like(name, name, MatchMode.ANYWHERE)); } if (status ! null) { conditions.add(Restrictions.eq(status, status)); } if (startTime ! null) { conditions.add(Restrictions.ge(createdAt, startTime)); } if (!conditions.isEmpty()) { criteria.add(Restrictions.and(conditions.toArray(new Criterion[0]))); }这里的Restrictions.and(...)可以直接接收一个 Criterion 数组比逐个 add 更整齐条件集合构建也变得完全可测试。3.3 小心混合逻辑用嵌套来替代手工括号如果你对 and/or 混用没有概念最容易出错的场景是多个筛选条件里面还带一个或条件组。比如用户列表里有个搜索框用户可以搜姓名或邮箱同时其他条件也要生效。很多人会写成criteria.add(Restrictions.like(name, keyword, MatchMode.ANYWHERE)); criteria.add(Restrictions.like(email, keyword, MatchMode.ANYWHERE));这样其实是姓名包含关键字 且 邮箱包含关键字跟业务意图完全不同。正确做法是用一个 Disjunction 把两个 or 条件包起来再把这个整体作为一条 Criterion add 进去让它与外部条件形成 and 关系Disjunction keywordBlock Restrictions.disjunction(); keywordBlock.add(Restrictions.like(name, keyword, MatchMode.ANYWHERE)); keywordBlock.add(Restrictions.like(email, keyword, MatchMode.ANYWHERE)); criteria.add(keywordBlock);这个操作如果不做搜索结果就会诡异到让人怀疑人生。我见过某同事因为这个 bug 排查了半天最后发现是条件之间缺少分组。这个点几乎可以当成面试题问别人为什么不用字符串拼接而要用 Conjunction/Disjunction答案不就是为了结构化地表达逻辑树。4. 排序、分页和投影聚合超出查出来的部分4.1 排序与分页组合使用时的顺序把控Criteria 的排序用法跟 HQL 一样简单criteria.addOrder(Order.asc(age))或criteria.addOrder(Order.desc(createdAt))多个排序条件就多次调用 addOrder顺序代表优先级。分页则使用setFirstResult(int)和setMaxResults(int)两个方法注意 setMaxResults 传入的是最多返回多少条不是结束下标很多人第一次用会踩这个边界问题。Criteria criteria session.createCriteria(Customer.class); if (StringUtils.hasText(name)) { criteria.add(Restrictions.like(name, name, MatchMode.ANYWHERE)); } criteria.addOrder(Order.desc(createdAt)); criteria.setFirstResult(20); criteria.setMaxResults(10); ListCustomer customers criteria.list();这段代码表示从第 20 条开始取最多取 10 条。对应到数据库底层Hibernate 会针对不同方言生成不同 SQLMySQL 里就是limit 20, 10。还有一点值得注意setFirstResult 的起点是 0也就是说第一页应该是setFirstResult(0)第二页是setFirstResult(20)。假如你传入了负数Hibernate 会忽略或抛出异常取决于方言版本所以写分页工具时最好做一次参数校验。4.2 Projections 做投影和聚合统计总数、求平均值、查指定字段很多时候我们需要的不是一个完整的实体对象而是一个总数、一个平均值或者干脆只要几个字段。这时就要出动 Projections。最常用的统计是 rowCount也就是select count(*)Criteria criteria session.createCriteria(Customer.class); criteria.setProjection(Projections.rowCount()); Long total (Long) criteria.uniqueResult();注意 rowCount 返回的类型在不同数据库方言下可能不一样自动模式下 Hibernate 通常转换成 Long。但为了稳妥我在拿到 total 后一般直接用Number接收再longValue()避免 Integer 和 Long 之间的强转异常。多字段投影和聚合同时使用要借助 projectionListCriteria criteria session.createCriteria(Order.class); ProjectionList projectionList Projections.projectionList(); projectionList.add(Projections.groupProperty(customer)); projectionList.add(Projections.sum(amount)); projectionList.add(Projections.count(id)); criteria.setProjection(projectionList);这会返回一个ListObject[]每一行是一个 Object 数组数组里每个元素对应投影列表中的每一列。使用的时候要自己按顺序解析。这一部分比 HQL 稍微繁琐一些但好在结构清晰适合程序化地拼统计报表。4.3 一个容易翻车的细节setProjection 之后再次切换到普通查询如果同一个 Criteria 对象前面设置了投影后面你又想查完整实体只调 criteria.list() 是不行的得先把投影清掉。我踩过一次这样的坑在写一个导出功能时前半段用同一个 Criteria 对象查了总数后面想复用它再去查明细结果 list() 返回的全是 Object[] 而不是 Customer怎么强转都抛 ClassCastException。解决方案很直接要么每个查询都新建 Criteria 对象要么在查明细前调用criteria.setProjection(null)把它复位。我个人更偏向新建对象因为同一个查询对象上既要聚合又要明细维护起来容易出岔子不如拆成两个方法各自独立。这种经验在写通用查询组件时尤其重要查询组件的入参如果同时有统计模式和列表模式一定要在内部做好投影状态的切换。4.4 去重与 DISTINCT_ROOT_ENTITY 的坑另一个和查询结果形态相关的坑是关联查询后返回了重复的根实体。这个我在后面讲多表关联时会详细展开这里先提一点如果确定自己的查询会因为一对多关联而重复数据需要去重时可以设置criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)。但要注意这个去重是发生在内存里的不是 SQL 层的 distinct。也就是说当你同时又设置了 setFirstResult 和 setMaxResultsHibernate 先在数据库层做了 limit再回到内存里按根实体去重最终返回的结果条数可能小于你设置的一页大小。你在做分页时千万不要想当然地认为每页都会是固定数量需要根据业务在列表页做空数据补偿或用 count 查询得出真实总数。5. 多表关联查询的 Criteria 写法5.1 createAlias通过别名引入关联实体Criteria API 处理多表查询主要靠 createAlias。比如 Customer 和 Order 是一对多关系现在要求查所有下过单的客户并且订单金额大于 100标准写法Criteria criteria session.createCriteria(Customer.class); criteria.createAlias(orders, o); criteria.add(Restrictions.gt(o.amount, 100.0)); ListCustomer customers criteria.list();createAlias 的第一个参数是 Customer 实体里的属性名第二个参数是这个关联在本次查询中的别名之后所有针对订单属性的条件都通过别名加前缀。这里的写法相当于在 SQL 里给订单表起了个别名然后把这个别名作为过滤条件的一部分。有一点必须记牢旧版 org.hibernate.Criteria 的 createAlias 默认是 inner join 语义。也就是说用上面的查询没有订单的客户不会被查出来。如果你的业务需要把所有客户都查出来并按订单金额过滤即使没有订单也要显示旧 Criteria 就比较难实现了这种情况我更倾向用 HQL 的左连接或者直接换 JPA Criteria 的 join 类型控制。5.2 嵌套关联查询给关联的关联加条件多表关联不限于两层比如客户 → 订单 → 订单项如果你想查某个客户下过包含特定商品名称的订单可以给订单再创建二级别名。旧式 Criteria 的做法是连续 createAlias。有一个写法上的细节可以直接用点号路径创建别名写成criteria.createAlias(orders.items, item)这样只要一次调用就能跨两层关联绑定别名。但要注意如果路径中途有集合属性这依然会形成一个 inner join 链路数据重复风险很高。我的习惯是如果层级超过两层或者关联条件很复杂就改用 HQL不要让 Criteria 的可读性优势变成负担。5.3 一对多关联下的去重与分页最容易被数据量欺骗的场景先看一个实际场景查询所有客户并展示每个客户的最近订单金额。如果在 Criteria 中直接创建了 orders 的别名再 list()因为一个客户对应多条订单数据库返回的结果会按订单条数展开客户对象自然重复出现。这时你用 setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)可以解决集合重复的问题。但别高兴太早我刚才也提到了它和分页组合时结果集数量不可控。我建议做法是把筛选客户的逻辑和查询客户关联数据分开。第一段用子查询或 exists 语义筛选出符合条件的客户主键集合第二段再按主键集合查询完整客户并做分页。这样既避免了重复行对分页的干扰也更容易优化 SQL。旧版 Criteria 对 exists 的支持不够优雅但你可以先查出主键集合再用Restrictions.in(id, ids)完成第二段查询。对于数据量不算特别大的后台系统这种两步走完全够用。5.4 关联查询里抓取策略的影响多表查询还有一个隐藏问题抓取策略。如果在映射文件中配置了 fetch Lazy那么在你遍历 Customer 的 orders 属性时才发起新的查询容易造成 N1 次 SQL。用 Criteria 查询时可以在查询级别指定抓取模式Criteria criteria session.createCriteria(Customer.class); criteria.setFetchMode(orders, FetchMode.JOIN); ListCustomer customers criteria.list();这个命令相当于在业务层临时覆盖映射配置让本次查询使用 left join fetch 一次性把订单带出来。它和 createAlias 不同不会改变查询结果的行数也不往条件集合里面加约束只是告诉 Hibernate 立即加载这个关联。如果你要按客户的订单金额筛选客户应用 createAlias如果你只是要让返回结果不懒加载用 setFetchMode。两者语义不同我在早期曾经把两者互相当替换用结果出现奇怪的行重复或数据缺失后来才意识到它们面向的问题不一样。6. 必须吐槽的几个坑和对应的解决方案6.1 Hibernate 5.2 之后org.hibernate.Criteria 被列为 Deprecated这个坑严格来说不算 bug但是牵扯到技术选型。从 Hibernate 5.2 开始org.hibernate.Criteria 被标记为 deprecated官方更推荐使用 JPA 的 Criteria API也就是 javax.persistence.criteria.CriteriaBuilder、CriteriaQuery 这套。如果你开展的是全新项目我的建议是直接使用 JPA Criteria 或者 Spring Data JPA 的 Specification它们类型安全更强根实体、路径表达等设计也更现代。但老项目维护时你不用急着把全部 Criteria 重写它在 6.x 版本仍然可以用只是缺少新特性并且后续会被移除。如果你的项目停留在 Hibernate 3/4 时代或者正在维护一套十年前的老系统那 org.hibernate.Criteria 依然是你日常工作的重要工具学它绝对不亏。6.2 createAlias 与 createCriteria 的区别用错导致语义变化可能有同学不清楚Criteria 对象还有另一个方法createCriteria(String associatedPath)也可以实现关联查询。但它的语义和 createAlias 不同。createAlias 只是给已有查询追加一个别名引用不影响查询的返回根实体而 createCriteria 会把整个查询推进到关联实体上后续 add 的条件默认作用于关联实体返回结果的形态、根实体都可能改变。举个例子Criteria criteria session.createCriteria(Customer.class); criteria.createCriteria(orders).add(Restrictions.gt(amount, 100.0));这里你表面上是在查 Customer但后续对 amount 的约束是挂在订单实体上的。虽然也能查出下过订单金额大于 100 的客户但语义不如 createAlias 直观而且在返回值处理上容易让人困惑。所以我建议除非你有意识地要切换查询根实体否则关联查询统一使用 createAlias不要用 createCriteria 四处乱挂。6.3 Session 生命周期与懒加载的魔鬼细节Criteria 查询本身不会自动帮你管理 SessionSession 关闭以后再访问实体关联属性就可能抛懒加载异常。很多 Criteria 初学者写的业务方法是这样的在 Service 里用一个事务方法执行查询、关闭 Session、返回实体集合然后 Controller 层要展示关联的订单字段一访问就炸了。解决方式有三种其一在查询时像前面说的那样把需要的关联用 setFetchMode 或者 alias join 直接初始化其二使用 Spring 框架的话把 Open Session in View 模式开起来让 Session 生命周期覆盖到视图层其三更推荐的做法是查询后就地转换成 Vo/DTO只保留需要的字段既隔离了懒加载问题也让接口数据更可控。第三种方式可能代码量多点但对长期维护最友好。6.4 查询缓存与动态 SQL 的命中率原本以为 Criteria API 生成的 SQL因为是程序化构建缓存效果会好一些。实际不然。不同的条件组合会生成不同的 SQL 语句如果页面筛选框很多、组合五花八门Hibernate 的查询缓存和第二级缓存命中率可能会变得很低。对于这种场景我会把条件简单的、高频的查询单独做本地缓存或者结合 Redis 做结果级缓存而不是完全依赖 Hibernate 的查询缓存。同时把查询条件稳定性的因素纳入设计如果一个查询很少被复用就别开查询缓存省得维护缓存失效逻辑还踩脏读的坑。6.5 一个小经验写一个 Criteria 条件组装工具类多次踩坑之后我在项目里养成了一个习惯把动态条件组装封装到一个工具方法中统一处理空值判断、逻辑分组、排序字段白名单校验。排序尤其要注意Criteria 的 addOrder 是对属性名做处理的如果属性名来自前端传参非常容易被恶意注入或者产生错误映射。我一般会把允许排序的属性名放进一个 Map 白名单前端传什么字段名都转换成实体中的真实属性名不在白名单中就忽略排序条件。这样整个查询模块的稳定性和安全性都会提高很多。回到开头那个后台系统我用 Criteria API 重构完查询模块后原来一百多行的拼接逻辑压缩成了几十行同事看代码也更容易理解。如果你也在维护一个条件多、变化频繁的查询列表不妨在项目里找一个模块先试试 Criteria API感受下面向对象地描述查询和拼字符串写查询之间的差别。等习惯了这套思路再遇到复杂动态查询时你就知道该选 HQL 还是 Criteria 了。
延伸阅读

更多相关文章

2026/10/11 4:32:40

用C#实现OPC UA客户端并存入SQL Server的完整指南

简介:OPC UA客户端与SQL Server数据落地的C#实践项目,面向工业自动化、物联网数据采集及企业级数据集成的开发者,也可作为高校自动化专业项目的参考。项目借助OpcUaHelper开源库简化OPC UA协议交互,完整演示从服务器读取数据、以“…

2026/10/11 4:32:40

Java田径运动会管理系统实战:从表结构到并发报名与成绩排名

简介:面向Java课程设计、毕业设计及运动会信息化管理学习者,这套完整项目实现了运动员管理、赛事安排、在线报名、成绩录入与排名展示、信息发布和权限控制等功能。系统基于Java与MySQL开发,数据存储与管理由MySQL完成,代码注释清…

2026/10/11 7:32:47

律师智能办案系统有哪些推荐?先看这5个环节是否覆盖

"律师智能办案系统"这个词,现在被用得很宽。有人说的是案件管理(台账、日程、工时),有人说的是法律检索,有人说的是AI写文书。这三件事其实不是一回事。所以我不太建议上来就问"哪家好"&#xff0…

2026/10/11 7:32:47

Java线程池详解:ThreadPoolExecutor参数、阻塞队列与拒绝策略实战

1. 线程池到底解决了什么问题先说个我早年踩过的坑。那会儿刚工作没多久,接手一个内部报表系统,每次请求进来我都是 new Thread(...).start() 这种最朴素的写法。单机并发量不大,二三十个请求同时进来,系统也就撑住了。直到后来接…

2026/10/11 7:32:47

C盘爆红别乱删:用Codex精准揪出AppData 87.81GB垃圾

C盘变红这件事,放在任何一个开发者或者重度电脑用户面前,都足以让人血压升高。我前几天就遇到了这个情况:系统还在正常跑,但C盘容量条已经顶到最上面,磁盘清理工具扫了一圈也没给出什么像样的结果。后来我没有急着删东…

2026/10/11 7:32:47

WorkBuddy企业培训怎么选?腾讯云公开课程与红烁AI内训对比

9月2日,腾讯云公开课讨论WorkBuddy企业版中的Skill、专家和连接器如何配置与管理;9月15日,另一场课程把场景落到招聘助手。这些课程显示,AI办公培训已从对话技巧延伸到岗位任务和团队协作。 企业的采购问题也随之变了&#xff1a…

2026/10/11 7:32:47

储能系统调峰容量优化配置与全生命周期经济性分析Matlab复现

1. 项目定位与核心目标拆解储能系统参与调峰这个话题,在电力系统分析和能源经济领域已经热了好几年了。很多EI论文都在做类似的研究,核心思路其实高度一致:在给定的负荷曲线和分时电价机制下,怎么配置储能的容量和功率&#xff0c…

2026/10/11 7:27:47

程序员面试做题现象深度拆解:从算法题到技术招聘的底层逻辑

这两年“程序员面试做题”这个话题隔三差五就被顶上来一次,前阵子“八股文”和“手撕算法”又成了热点,我身边不少老同事也在转发吐槽。有人觉得是面试官偷懒,有人觉得是求职者能力不行,还有人说这就是大环境内卷的必然结果。在我…

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
免费获取方案
☎咨询二维码 ☎ ↑