Hibernate Criteria API中multiselect结合fetch抓取关联实体报错
报错根因
这个异常是Hibernate的核心校验规则导致的:
- 无论是Criteria API中调用
fetch()方法,还是通过EntityGraph配置抓取规则,本质都是面向完整实体查询设计的特性,要求select子句中必须包含被抓取关联的所属实体的完整引用 - 你的代码中
multiselect只选择了Account的id、accountNumber两个标量字段,没有将Account实体本身放入查询选择列表,Hibernate无法将抓取到的关联对象挂载到不完整的查询结果上,就会抛出该异常。 - EntityGraph方案报错逻辑完全一致:EntityGraph仅在查询完整实体时生效,投影查询场景下不会触发抓取逻辑,同样会触发该校验。
可行实现方案
根据你的需求(使用multiselect、抓取关联、返回POJO而非Tuple),有两种可落地的实现方式:
方案1:查询完整实体(适合需要Account全字段的场景)
如果不需要裁剪Account表的查询字段,直接将select子句改为查询根实体即可,不需要手动指定零散字段,fetch/EntityGraph都能正常生效:
private Session getSession() { return entityManager.unwrap(SessionImplementor.class); } @Override public Account getGatewayAccount(Long appId, String accountNumber) { Session session = getSession(); CriteriaBuilder criteria = session.getCriteriaBuilder(); CriteriaQuery<Account> query = criteria.createQuery(Account.class); Root<Account> from = query.from(Account.class); from.fetch(Account_.APP, JoinType.INNER); // 直接选择根实体,不要仅选择零散标量字段 query.select(from) .where(criteria.equal(from.get(Account_.ACCOUNT_NUMBER), accountNumber), criteria.equal(from.get(Account_.APP).get(App_.ID), appId)); try { return session.createQuery(query) .getSingleResult(); } catch (NoResultException e) { return null; } }
方案2:投影查询+构造器组装(适合需要裁剪查询字段的场景)
如果确实需要通过multiselect指定查询字段、避免查询无用列,就不要使用fetch/EntityGraph,改用普通join显式选择所有需要的字段,通过构造器表达式直接组装POJO,单条SQL即可完成查询,不会产生N+1问题:
- 先为Account类(或你自定义的Account DTO类)添加参数顺序匹配的构造方法,在构造方法内完成关联App对象的赋值
- 调整查询代码如下:
private Session getSession() { return entityManager.unwrap(SessionImplementor.class); } @Override public Account getGatewayAccount(Long appId, String accountNumber) { Session session = getSession(); CriteriaBuilder criteria = session.getCriteriaBuilder(); CriteriaQuery<Account> query = criteria.createQuery(Account.class); Root<Account> from = query.from(Account.class); // 用普通join关联即可,不需要fetch Join<Account, App> appJoin = from.join(Account_.APP, JoinType.INNER); // multiselect按构造器参数顺序传入所有需要的字段,包含关联对象需要的字段 query.multiselect( from.get(Account_.ID), from.get(Account_.ACCOUNT_NUMBER), appJoin.get(App_.ID), appJoin.get(App_.NAME) // 按需补充你需要的App实体字段 ) .where(criteria.equal(from.get(Account_.ACCOUNT_NUMBER), accountNumber), criteria.equal(appJoin.get(App_.ID), appId)); try { return session.createQuery(query) .getSingleResult(); } catch (NoResultException e) { return null; } }
注意事项
fetch()和EntityGraph仅在查询完整托管实体的场景下生效,不要和仅选部分字段的投影查询混用- 投影查询+构造器组装的方式性能更可控,能明确指定需要查询的列,避免加载无用字段,也不会出现懒加载序列化异常问题
内容的提问来源于stack exchange,提问作者ghost
相关产品推荐
相关产品推荐

