使用@NamedNativeQueries时Hibernate主实体嵌套关联列表取数问题咨询
现有代码的基础映射错误
- 关联类型错误:在
EmpListBean中用@ManyToOne修饰List<WorkListBean>类型字段,本身和语义完全不匹配——多对一关系的映射对象是单个关联实体,无法对应集合类型,要实现“一个员工对应多个工作项列表”的需求,正确的关联注解应该是@OneToMany - 注解使用错误:关联关系字段上错误添加
@Column注解,@Column仅用于普通标量字段的列映射,关联关系需要搭配@JoinColumn或@JoinTable定义关联规则
关联字段无法自动填充的核心原因
你当前使用命名原生查询时配置了resultClass = EmpListBean.class,这种配置下Hibernate只会做平铺结果集和实体标量字段的一对一映射,完全不会触发嵌套关联实体的自动装配逻辑,因此workList字段始终为空。
适配复杂生产查询场景的落地方案
下面几个方案都可以避免循环查库的性能损耗,你可以根据现有业务代码的改动成本选择:
方案1:基于SqlResultSetMapping的原生查询自动装配
这个方案不需要把你复杂的多表关联SQL改写成HQL/JPQL,只需要在原有原生查询的select列中补充关联WorkList需要的字段,通过结果映射定义让Hibernate自动完成嵌套结构的装配。
改动步骤:
- 把原来
@NamedNativeQuery中的resultClass配置替换为指定自定义的结果映射名称 - 新增
@SqlResultSetMapping注解,分别定义主实体、关联实体的字段和查询列的映射关系 - 原有业务SQL保持不变,只需要在select列中追加关联工作项的查询字段(保证列名不重复即可)
代码示例:
@NamedNativeQueries({ @NamedNativeQuery( name = "EmpList", query = " SELECT e.ID , e.NAME, w.ID as w_id, w.NAME as w_name FROM EMPLOYEE e LEFT JOIN Work_List w ON e.ID = w.EMP_ID WHERE e.EMP_ID=:EMPID", resultSetMapping = "EmpWorkListMapping" ) }) @SqlResultSetMapping( name = "EmpWorkListMapping", entities = { @EntityResult( entityClass = EmpListBean.class, fields = { @FieldResult(name = "id", column = "ID"), @FieldResult(name = "empName", column = "NAME") } ), @EntityResult( entityClass = WorkListBean.class, fields = { @FieldResult(name = "id", column = "w_id"), @FieldResult(name = "workName", column = "w_name") } ) } ) @Entity public class EmpListBean { // 原有字段保持不变 @OneToMany @JoinColumn(name = "EMP_ID") private List<WorkListBean> workList; }
执行查询后,Hibernate会自动根据主实体的主键去重,把同一行的关联WorkListBean收集到对应的workList集合中,全程只执行一条SQL,性能和原查询完全一致。
方案2:DAO层内存拼装(零实体改动方案)
如果不想调整实体注解、不想修改原有命名查询的定义,可以在原有SQL基础上补查关联WorkList的字段,拿到平铺的查询结果后在内存中按主实体ID去重拼装成嵌套结构,改动成本最低。
代码示例:
Query query = session.getNamedQuery("EmpList"); query.setParameter("EMPID", myObj.getEmpId()); // 把每行查询结果转为Map结构,方便获取列值 query.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP); List<Map<String, Object>> rawRows = query.list(); // 按员工ID去重拼装 Map<Integer, EmpListBean> empContainer = new LinkedHashMap<>(); for (Map<String, Object> row : rawRows) { Integer empId = (Integer) row.get("ID"); EmpListBean emp = empContainer.get(empId); if (emp == null) { emp = new EmpListBean(); emp.setId(empId); emp.setEmpName((String) row.get("NAME")); emp.setWorkList(new ArrayList<>()); empContainer.put(empId, emp); } // 组装对应的工作项 WorkListBean work = new WorkListBean(); work.setId((Integer) row.get("w_id")); work.setWorkName((String) row.get("w_name")); emp.getWorkList().add(work); } List<EmpListBean> finalResult = new ArrayList<>(empContainer.values());
这种方式同样只执行一条SQL,内存拼装的性能开销在数据量万级以内几乎可以忽略,完全不会出现逐次查库的性能损耗。
方案3:批量抓取关联数据(无SQL改动方案)
如果主查询的SQL完全不能改动,也可以放弃循环逐次查询,采用两次SQL的批量查询模式:
- 第一次正常执行原命名查询拿到所有主实体列表,收集所有主实体的ID
- 第二次用所有主实体ID作为查询条件,一次性把所有关联的WorkList数据查出来
- 在内存中把关联数据按外键分组,匹配回对应的主实体
代码示例:
// 第一步:执行原有查询拿到主实体列表 List<EmpListBean> empList = query.list(); List<Integer> empIds = empList.stream().map(EmpListBean::getId).collect(Collectors.toList()); // 第二步:批量一次性查询所有关联工作项,禁止循环单条查询 Query workBatchQuery = session.getNamedQuery("WorkListBeanList"); // 改造原WorkListBeanList查询,支持传入ID列表批量查询 workBatchQuery.setParameterList("EMP_ID_LIST", empIds); List<WorkListBean> allWorks = workBatchQuery.list(); // 第三步:按员工ID分组匹配 Map<Integer, List<WorkListBean>> workGroup = allWorks.stream() .collect(Collectors.groupingBy(WorkListBean::getEmpId)); for (EmpListBean emp : empList) { emp.setWorkList(workGroup.getOrDefault(emp.getId(), Collections.emptyList())); }
这种方式一共只执行2次SQL,性能比逐次查询高几个量级,完全适配复杂生产查询的改动限制。
内容的提问来源于stack exchange,提问作者Deepak Ramakrishnan Kalidass
相关产品推荐
相关产品推荐

