首页 / 资讯中心 / 文章详情

Hibernate Criteria查询:面向对象的动态查询API实战指南

Hibernate Criteria查询:面向对象的动态查询API实战指南 ★ FEATURED ARTICLE
Hibernate32什么是Hibernate的Criteria查询先直接回答标题里的问题Hibernate的Criteria查询是一套完全面向对象、类型安全的动态查询API。它允许你用Java对象和方法调用的方式拼装查询条件而不是把条件塞进字符串里。换句话说你在Java代码里写cb.equal(root.get(status), 1)而不是写where status 1这样的HQL片段。这句话听起来简单但在条件数量不确定、组合方式千变万化的业务场景里差别是天上地下。关于热搜词hibernate还有人用吗我也想说两句。Hibernate这几年在互联网新项目里的占比确实不如MyBatis阵营但它绝对没有退出历史舞台。在我接触的政府项目、金融系统、制造业ERP甚至一些SaaS产品的核心模块里Hibernate依然大量服役。而且JPA是Java EE的官方规范Hibernate作为JPA最主流的实现它的Criteria API注意这里是JPA标准下的Criteria不是Hibernate旧版原生Criteria是官方钦定的动态查询方案。所以与其纠结框架新旧不如先把Criteria这个硬通货吃透——你换任何一个JPA实现Spring Data JPA、OpenJPA、EclipseLink这套思路完全通用。这篇文章不打算写成API文档罗列而是按我自己实际写项目的路径来讲先梳理Hibernate的查询生态和Criteria到底解决什么问题再拆核心概念然后给一套可以直接抄的实操代码最后把我在生产环境里踩过的坑、排查过的性能问题一并倒出来。如果你正被动态SQL拼接折磨或者接手了一个用Hibernate的遗留项目看到createCriteria满头问号这篇文章应该能帮你把这块拼图补上。1. 先聊聊Hibernate的查询生态和Criteria的定位1.1 三种查询方式的关系用Hibernate的人每天写查询基本绕不开三套东西HQL、原生SQL、Criteria。很多人一上来就梦想着一套HQL走天下但真实项目里三者分工应该是这样的HQLHibernate Query Language适合写固定结构的查询。它操作的是实体和属性而不是表和字段所以继承了面向对象的优点比如多态查询、隐式关联、级联处理。比如from Order o where o.status :status这种条件写死结构固定HQL是非常舒服的。缺点也明显一旦条件数量可变例如用户在前台筛选商品价格区间、品牌、分类、关键字可以有也可以没有你就不得不拿字符串去拼HQL。拼接字符串这活儿写起来容易维护起来想哭——漏个空格、引号没配对、条件顺序错乱都是运行期才炸的雷。原生SQL则完全不绕弯子直接操作数据库。Hibernate里通过createNativeQuery执行返回结果可以绑定到DTO或者实体。它的存在价值主要是三个场景复杂报表查询、数据库特有函数调用比如PostgreSQL的JSON操作、MySQL的GROUP_CONCAT、以及追求极致性能的查询优化。代价是你失去了跨数据库移植性也失去了实体的类型检查和缓存管理的部分便利。那Criteria是什么位置它是这几者之间最特殊的那个它把查询条件变成了Java对象图让程序代码可以生成查询而不是写死查询。同样是动态条件用HQL需要拼字符串用SQL更是拼到怀疑人生用Criteria却是纯Java方法调用编译期就能发现属性名写错的问题配合Metamodel还能做到绝对的类型安全。所以Criteria的定位可以总结成一句它是Hibernate为动态查询场景准备的亲儿子。1.2 动态查询场景才是Criteria的主场我见过很多刚接触Criteria的人抱怨写个简单查询HQL三行搞定Criteria写了一大坨麻烦死了。这个吐槽没毛病如果你是写死条件的查询比如登录时按用户名查用户Criteria确实不如HQL直观。但请记住Criteria的设计目标从来不是替代HQL而是彻底解决动态查询这个Hibernate早期最被诟病的短板。什么是真正的动态查询举个例子后台用户管理列表页面。这个列表的筛选项有用户名模糊搜索、手机号精确匹配、角色ID下拉过滤、状态单选、注册时间范围。每个筛选项都可能为空。用HQL写你大概会这样StringBuilder hql new StringBuilder(from User u where 11 ); if (StringUtils.hasText(username)) { hql.append(and u.username like :username ); } if (StringUtils.hasText(phone)) { hql.append(and u.phone :phone ); } // ... 更多拼接 QueryUser query session.createQuery(hql.toString(), User.class); // 然后逐个setParameter这么写能跑但有几个问题第一where 11这种经典hack看起来就透着无奈第二条件一多这个方法的长度会迅速失控可读性直线下降第三一旦条件有相互依赖的逻辑比如选了全部角色就不拼角色条件字符串拼接的顺序和逻辑嵌套简直是维护地狱。更恐怖的是这种拼接代码在接手两年后看你根本分不清哪个条件对应哪个参数。Criteria做同样的事结构完全清晰CriteriaBuilder cb session.getCriteriaBuilder(); CriteriaQueryUser cq cb.createQuery(User.class); RootUser root cq.from(User.class); ListPredicate predicates new ArrayList(); if (StringUtils.hasText(username)) { predicates.add(cb.like(root.get(username), % username %)); } if (StringUtils.hasText(phone)) { predicates.add(cb.equal(root.get(phone), phone)); } // ... 更多条件 cq.where(predicates.toArray(new Predicate[0]));这个写法里所有的条件都变成Predicate对象加条件就是往List里塞一个元素删除/修改条件就是调整Predicate的生成逻辑完全不用担心字符串语法也不用管顺序错乱。在此基础上还能轻松组合and/or比如cb.or(pred1, pred2)这在字符串拼接的HQL里处理起来要多写一堆括号。所以说如果你只处理固定条件CRUD用不着Criteria但只要你的系统里超过50%的查询都有条件可选的需求Criteria就是绕不开的关键技术。2. Criteria API的核心概念拆解2.1 核心接口的角色分工要上手Criteria先得认识它那几个核心接口不然看代码就像看天书。我把它们类比成一次查询组队CriteriaBuilder建造者整个查询的工厂。它负责提供所有查询操作的零件比如构造equal、like、greaterThan、orderBy甚至sum、avg、count等聚合函数。它通过EntityManager.getCriteriaBuilder()或session.getCriteriaBuilder()获取。CriteriaQueryT施工图纸描述我要查什么类型的结果以及查询的整体结构。可以是实体类CriteriaQueryUser也可以是自定义DTO类CriteriaQueryUserVO。它定义了from、where、orderBy、groupBy、select这些方法的调用顺序。RootT表别名/立足点代表查询的根实体相当于SQL里的主表。所有条件的路径都从它展开root.get(username)就是获取实体的某个属性路径。Predicate条件片段一个条件像SQL里WHERE后面的一个表达式比如username 张三。它可以被and/or组合成更复杂的条件树。Path属性路径其实root.get(username)返回的就是一个Path对象代表从根实体到某个属性的路径。它支持链式调用比如查订单的所属用户名称orderRoot.get(user).get(name)。至于旧版Hibernate原生Criteria的org.hibernate.Criteria接口它比JPA规范早出现用起来更Hibernate风session.createCriteria(User.class).add(Restrictions.eq(username, 张三))。但那套API在Hibernate 5.2起就被标记为DeprecatedHibernate 6里基本只剩JPA标准一套了。所以你现在看新代码、写新功能直接学JPA Criteria就行别在旧API上浪费时间。2.2 类型安全从哪来Criteria第二个关键卖点是它对类型安全的坚持。HQL里where u.username :name这个username属性名是字符串它到底是实体里的哪个字段写的时候编译器根本不检查只有运行时Hibernate解析HQL才会暴露错误。而Criteria里root.get(User_.username)如果你用了JPA的Metamodel元模型这个User_类是编译期生成的里面每个静态属性名都指向实体字段的真实名称。写错属性名编译期直接红线根本没机会到运行时才报java.lang.IllegalArgumentException: Unable to locate Attribute。就算不用Metamodel用root.get(username)这种字符串版本类型安全也体现在取值类型上cb.equal(root.get(status), 1)equal方法签名是Predicate equal(Expression? x, Object y)如果你把root.get(status)取成一个String类型路径去和1这个整数比较Hibernate在构建查询时能感知到类型不匹配运行时也能抛异常告诉你类型不一致不会像SQL拼接那样出现隐式的、意想不到的类型转换。那Metamodel类怎么生成如果你用Maven配合hibernate-jpamodelgen这个注解处理器编译时自动生成User_这类以实体名为前缀、下划线结尾的类。我自己的经验是只要项目不是特别老尽量用Metamodel代码的健壮性是肉眼可见的提升。代价只是多一个依赖和一个annotationProcessor配置绝对值得。3. 实操手记从静态查询到动态查询的完整落地3.1 环境准备与基本查询先说明一下我这篇文章的示例基于Hibernate 6.x JPA 3也就是目前的主流版本。如果是Hibernate 5.xAPI基本一样只有个别包名和Session获取方式的差异不影响阅读。第一步获取CriteriaBuilder和构建查询PersistenceContext private EntityManager entityManager; public ListUser findActiveUsers() { CriteriaBuilder cb entityManager.getCriteriaBuilder(); CriteriaQueryUser cq cb.createQuery(User.class); RootUser root cq.from(User.class); cq.select(root); cq.where(cb.equal(root.get(status), 1)); cq.orderBy(cb.desc(root.get(createdAt))); return entityManager.createQuery(cq).getResultList(); }这段代码翻译成SQL大概是select * from user where status 1 order by created_at desc。你注意cq.select(root)是选择根实体本身作为查询结果而cq.from(User.class)指定了从user表开始查。createQuery(cq)把CriteriaQuery交给EntityManager执行。到这一步你应该能感觉到CriteriaQuery就是一棵用Java对象构造出来的查询语法树最终由Hibernate负责解释成SQL并执行。如果你想要自定义返回字段而不是整个实体可以用cq.multiselect(...)CriteriaQueryObject[] cq cb.createQuery(Object[].class); RootUser root cq.from(User.class); cq.multiselect(root.get(username), root.get(phone));但Object[]用起来不舒服Hibernate 5.2支持构造器表达式可以映射到DTOCriteriaQueryUserVO cq cb.createQuery(UserVO.class); RootUser root cq.from(User.class); cq.select(cb.construct(UserVO.class, root.get(username), root.get(phone)));要求UserVO提供一个接收两个参数的构造器。这是我在报表和列表接口里最常用的写法既不用把整个实体捞出来又能得到类型明确的VO对象。3.2 多条件动态查询的经典模板接下来直接上我在后台管理系统中反复使用的一个动态查询模板。这个模板覆盖了条件可选时间范围模糊匹配IN集合排序分页这些最常规的需求是我个人总结出的通用结构public PageResultUserVO searchUsers(UserSearchDTO searchDTO, int page, int size) { CriteriaBuilder cb entityManager.getCriteriaBuilder(); CriteriaQueryUser cq cb.createQuery(User.class); RootUser root cq.from(User.class); ListPredicate predicates new ArrayList(); // 模糊匹配 if (StringUtils.hasText(searchDTO.getKeyword())) { predicates.add(cb.like(root.get(username), % searchDTO.getKeyword() %)); } // 精确匹配 if (StringUtils.hasText(searchDTO.getPhone())) { predicates.add(cb.equal(root.get(phone), searchDTO.getPhone())); } // IN集合 if (CollectionUtils.isNotEmpty(searchDTO.getRoleIds())) { JoinUser, Role roleJoin root.join(roles); predicates.add(roleJoin.get(id).in(searchDTO.getRoleIds())); } // 时间范围 if (searchDTO.getStartTime() ! null) { predicates.add(cb.greaterThanOrEqualTo(root.get(createdAt), searchDTO.getStartTime())); } if (searchDTO.getEndTime() ! null) { predicates.add(cb.lessThanOrEqualTo(root.get(createdAt), searchDTO.getEndTime())); } // 状态过滤注意为空时查询全部 if (searchDTO.getStatus() ! null) { predicates.add(cb.equal(root.get(status), searchDTO.getStatus())); } cq.where(predicates.toArray(new Predicate[0])); cq.orderBy(cb.desc(root.get(createdAt))); // 先统计总数 CriteriaQueryLong countQuery cb.createQuery(Long.class); RootUser countRoot countQuery.from(User.class); countQuery.select(cb.count(countRoot)); countQuery.where(predicates.toArray(new Predicate[0])); Long total entityManager.createQuery(countQuery).getSingleResult(); // 再查当前页数据 ListUser list entityManager.createQuery(cq) .setFirstResult((page - 1) * size) .setMaxResults(size) .getResultList(); return new PageResult(total, convertToVO(list)); }这一套模板我用了好几年几乎没出过岔子。特别注意几个细节第一cq.where(predicates.toArray(new Predicate[0]))当predicates为空时等于没有where条件查询全部数据这符合业务语义。千万别写cb.conjunction()这种看起来能占位、实际就是真值的写法虽然它也能用但代码可读性反而下降。第二分页用了setFirstResult和setMaxResultsHibernate会自动生成数据库方言对应的分页SQL比如MySQL的limitOracle的rownum。不要自己拼limit到JPQL里那样会失去数据库移植性。第三统计总数这条注意我新建了一个CriteriaQueryLong和新的Root而不是复用之前的cq和root。如果你尝试cq.select(cb.count(root))再getResultList虽然跑得出来但你会改动原查询的类型和结构最后拿到一个奇怪的Long列表。分开建两个CriteriaQuery互相不干扰是最清晰、最不容易出错的写法。3.3 多表关联join的正确姿势多表查询是Criteria的一个重点也是很多人看不懂的坎。HQL里多表关联用join关键字Criteria里用Root.join方法。我们来一个实际例子查询所有下单次数超过5次的用户结果包含用户名和下单次数。CriteriaQueryObject[] cq cb.createQuery(Object[].class); RootUser userRoot cq.from(User.class); JoinUser, Order orderJoin userRoot.join(orders, JoinType.INNER); cq.multiselect(userRoot.get(username), cb.count(orderJoin.get(id))); cq.groupBy(userRoot.get(username)); cq.having(cb.greaterThan(cb.count(orderJoin.get(id)), 5L)); ListObject[] results entityManager.createQuery(cq).getResultList();这里JoinUser, Order就是SQL里的inner joinjoin(orders, JoinType.INNER)里的orders是User实体里对应的集合属性名。默认就是内连接如果要左连接传JoinType.LEFT。有两个容易踩的坑第一groupBy分组后select里使用非聚合字段比如username在MySQL里是允许的但在PostgreSQL会报错。标准SQL要求非聚合字段必须出现在groupBy里我上面的写法是严格标准的。第二having后面用的聚合条件和where里的条件完全不同——where作用于行级别是在关联之前过滤having作用于分组级别是在关联分组之后过滤。这个顺序搞反了查询结果就会错得莫名其妙。还一种更复杂的场景子查询。Criteria同样支持。比如查状态不是最新的订单用户。用cb.subquery创建子查询CriteriaQueryUser cq cb.createQuery(User.class); RootUser userRoot cq.from(User.class); SubqueryInteger subquery cq.subquery(Integer.class); RootOrder orderRoot subquery.from(Order.class); subquery.select(orderRoot.get(userId)); subquery.where(cb.equal(orderRoot.get(status), -1)); cq.where(cb.in(userRoot.get(id)).value(subquery));这段生成SQL大致是where user.id in (select user_id from order where status -1)。子查询的API稍微绕一点核心是cq.subquery(X.class)拿一个子查询的CriteriaQuery然后正常设置select、from、where再通过cb.in(...).value(subquery)把它嵌入主查询。注意这个value方法接收一个Subquery作为参数版本不同可能略有差异新版JPA里还可以直接.in(subquery)。4. 实战中的暗礁常见问题与排查技巧4.1 性能陷阱N1查询和超大IN集合Criteria写起来方便但它底层最终还是要生成SQL所以性能问题和原生SQL一样存在甚至更容易踩坑因为它隐藏了SQL细节。最大的坑是N1查询。比如你查订单列表然后订单里有个用户如果Criteria里直接select订单实体又没设置join fetch那循环里访问order.getUser().getName()就会导致对每个订单都发一条select * from user where id ?100条订单就是101条SQL这叫N1。解决办法是用fetch join。Criteria里这样写RootOrder root cq.from(Order.class); root.fetch(user, JoinType.INNER); cq.select(root);fetch和join的区别是join只是提供一条关联路径用于条件过滤而fetch会额外告诉Hibernate把关联对象一并查出来拼到结果里这样循环访问时不会触发新的SQL。注意一旦用了fetch返回的实体就是托管状态它的关联属性已经被填充不能再改变fetch策略否则会抛QueryException。第二个容易炸的地方是超大的IN集合。Criteria的root.get(id).in(ids)写起来很优雅但如果ids有几千上万条生成的SQL就是in (1,2,3...几千个...)。Oracle对IN列表元素数量有1000的上限MySQL也有超出优化器预期的性能问题。我在生产环境遇到过Oracle报错ORA-01795: maximum number of expressions in a list is 1000排查半天没想到是in集合太大。解决办法是分批比如每500个一批用OR把多批串起来或者改成join临时表。Criteria这边你可以先把ids切成多个列表然后循环构造cb.or(...)。还有一个和Criteria关系很大的性能问题隐式join导致的多行结果集。如果你通过root.join(roles)来过滤角色条件但查询结果还是要返回UserHibernate会在SQL里生成inner join。如果某个用户有3个角色那么结果里会出现3行相同的User记录然后Hibernate会按实体id去重最终返回给Java的List是去重后的User。听着好像没事但SQL返回行数膨胀了3倍数据库和网络白白多扛了2倍数据。更隐蔽的是如果你在这个查询里同时用了分页setMaxResults(10)这个分页是在SQL层面执行的——SQL先join出30行再取10行但有10行可能是同一个用户的不同角色行最后Hibernate去重后可能只剩3个用户。这是典型的分页数量对不上问题。解决办法是多个角色条件时用exists子查询代替显式join或者对User使用distinct。4.2 易错点Cast处理、字段名、连接泄漏CriteriA易错点不少。第一个头号问题就是绝大多数字段不存在错误。root.get(createTime)如果实体里实际是createdAt运行时直接扔IllegalArgumentException: Unable to locate Attribute with the given name。它不像HQL会告诉你在哪个位置解析失败Criteria报错就干巴巴一行。所以我的习惯是能用Metamodel就用Metamodelroot.get(User_.createdAt)编译期就能避免这种低级错误。如果项目暂时没法引入注解处理器那就保持实体字段命名的强规范一个单词不缩写、不换写法比如userName就是userName别一会儿userName一会儿username。第二个常见问题是select没写全导致的ClassCastException。比如createQuery(cq)返回的是TypedQueryUser如果你在multiselect里只选了name和phone实际查询结果类型是Object[]运行期你拿getSingleResult()强转成User必然炸。这个错误在Hibernate 6里有时候会被包装成模糊的Cannot cast java.lang.Object[] to User排查比较费劲。建议凡是multiselect多字段一律用cb.construct(DTO.class, ...)造DTO或者泛型用Object[]别让返回类型和实际类型不一致。第三个坑是EntityManager生命周期。很多新手在DAO里拿到EntityManager就entityManager.getCriteriaBuilder()用完不关甚至在循环里反复创建EntityManager最后连接池耗尽日志里全是Connection is not available, request timed out。这个其实不算Criteria的锅但因为Criteria查询通常出现在复杂业务中连接持有时间更长更容易触发。在Spring管理的环境里用PersistenceContext注入的EntityManager是线程安全的、由容器管理的别手动close在非Spring环境自己用EntityManagerFactory创建出来的必须在finally块里关闭。第四很大的字段名大小写和关键字撞名问题。Criteria生成的SQL会自动加引号吗默认不加。如果你的实体字段名是order、status这类和SQL关键字撞上的单词Hibernate会原样拼进SQL在有些数据库上直接语法错误。解决办法是给实体表名/列名显式设置Table(name t_order)、Column(name order_status)既符合规范又避开关键字。4.3 老版本代码迁移从Hibernate原生Criteria到新API如果你在维护一个Hibernate 4/5时代的老项目会看到一堆这样的代码Criteria criteria session.createCriteria(User.class); criteria.add(Restrictions.eq(username, username)); criteria.add(Restrictions.like(phone, % phone %)); criteria.setMaxResults(20); ListUser list criteria.list();这套旧API在Hibernate 5.2之后被标记为DeprecatedHibernate 6里已经移除或者灰度移除升级时编译都过不去。迁移到JPA Criteria的映射关系其实很清晰旧Hibernate原生Criteria新JPA Criteriasession.createCriteria(User.class)entityManager.getCriteriaBuilder().createQuery(User.class)criteria.add(Restrictions.eq(name, value))cb.equal(root.get(name), value)Restrictions.like(name, %xxx%)cb.like(root.get(name), %xxx%)Restrictions.in(id, ids)root.get(id).in(ids)criteria.addOrder(Order.desc(time))cq.orderBy(cb.desc(root.get(time)))criteria.setMaxResults(n)query.setMaxResults(n)迁移时我建议分三步走第一步把所有session.createCriteria替换成EntityManager方式。如果你的项目还是Session操作用session.getEntityManagerFactory().createEntityManager()或者直接注入EntityManager。第二步按上面的映射表一个个把Restrictions替换成Predicate这个阶段先不追求代码优化能做等价迁移就行。第三步再把重复的、条件散的查询整理成前面那个predicates列表模板消除重复代码。我迁移过一个200多个查询的老模块按这个节奏一周多全部搞定回归测试基本没出问题。4.4 快速排查Criteria查询报错的经典解决方案把常见排查经验整理成一张速查表至少能帮你省掉半天搜索时间报错信息原因与解法Unable to locate Attribute with the given nameroot.get(xxx)里的属性名和实体字段名不匹配。对比实体字段名或者改用MetamodelUser_.xxxCannot cast ... Object[] to ...查询select返回类型和赋值的接收类型不一致。统一用cb.construct(DTO.class, ...)或者泛型接收Object[]QuerySyntaxException: unexpected token多半是把HQL语法写进了Criteria。Criteria不解析HQL字符串检查是否有字符串拼查询ORA-01795: maximum number of expressions in a list is 1000IN集合超过1000。把集合分批或改SQL临时表joinHHH90003004: firstResult/maxResults specified with collection fetch带集合fetch又同时分页Hibernate会在内存过滤分页。去掉fetch或先查id再二次查询javax.persistence.PersistenceException: ... could not extract ResultSet底层SQL执行出错。开启show_sql和format_sql看生成的SQL到数据库客户端里执行一遍定位问题MultipleBagFetchException同一个查询fetch了两个List类型的集合属性。换成Set集合属性或拆成多次查询最后一条值得一提。MultipleBagFetchException是Criteria和JPQL特有的坑你fetch了两个List关联比如订单同时有ListItem和ListLog数据库会做笛卡尔积行数爆炸Hibernate直接拒绝执行抛异常。解决办法不是硬扛而是拆分查询主查询用fetch查第一个集合属性然后用BatchSize或者EntityGraph处理第二个或者干脆先查主实体id列表再用两条in查询分别加载两个集合。5. 从老框架质疑到新用法Criteria的未来形态回到开头热搜词的延伸Hibernate还有人用吗我用数据说话。Spring Boot 3默认的持久层依赖里依然把Hibernate作为默认JPA实现Spring Data JPA的底层也还是它。那些说Hibernate没人用的人其实更多是在说新项目不首选MyBatis系。但在大量的企业级应用、复杂领域模型、需要跨数据库适配的项目里JPA/Hibernate依然活得很好。而且Hibernate 6在架构上做了大量现代化改进比如更严格的类型检查、更好的SQL生成器、对JDK 17的支持这些都不是一个垂死框架会投入的研发资源。那Criteria在未来的位置呢我可以大胆说一句动态查询场景里Criteria的地位不仅不会削弱反而会越来越重。因为像QueryDSL这类库本质上就是用类型安全的表达式来构建动态查询背后一样是Criteria API做载体Spring Data JPA的Specification底层也是Criteria。所以你今天学的这套东西某种意义上是在给QueryDSL、Spring Data JPA这些上层建筑打地基。地基学会了再接触它们的语法会事半功倍。还有一点值得注意Hibernate 6里的Criteria API和Hibernate 5相比底层解析器完全重写了性能和生成的SQL质量有提升。比如以前查询select * from user where (status ?) order by created_at descHibernate 6生成的SQL依然干净利落而某些带有or的复杂谓词Hibernate 6可以自动做简单的谓词下推让SQL更接近手写优化的样子。这些都是实打实的进步。如果你决定要在新项目里尝试Hibernate我给一个建议不要再把Hibernate只当成一个自动建表的ORM玩具试着用它完整的查询体系——固定查询用JPQL/HQL动态筛选用Criteria极端复杂报表用原生SQL三者配合才是这个框架真正完整的打开方式。而其中把Criteria练熟是你从会用Hibernate到真正理解Hibernate的分水岭。我从第一次看到createCriteria的用法至今已经有七八年了。中途也试过绕开它用字符串拼HQL、用MyBatis的注解SQL但每次遇到筛选条件十来个、每种可选可不选、还要动态排序的后台查询我最后还是会回到Criteria这个老伙计身边。它不绚丽但它踏实——老老实实让你用对象模型拼装查询该报错的编译期就报错该跑快的分页也能正确完成。如果你正在和动态查询较劲不妨花一周时间把文章里的模板在你自己的项目里跑一遍。跑通之后你会发现那些让你头疼的条件忽多忽少的列表查询在Criteria面前真的只是一个小循环而已。
阅读完成 · 觉得有帮助?
咨询建站