使用Hibernate 3 Criteria及复杂投影查询关联子对象(Profile)数据失败问题
问题排查与解决方案
首先咱们先明确:你的实体关联配置本身没有语法错误,问题出在服务层的查询逻辑上——尤其是投影(Projection)和结果转换器的使用,直接导致关联的Profile数据根本没被查询和映射进来。
核心问题分析
你在分页查询时用ProjectionList只选择了User的几个基础字段(login、first_name等),完全没包含default_profile关联对象的任何属性。而且AliasToBeanResultTransformer只能把查询结果直接映射到User的平级属性上,没办法自动把嵌套的Profile属性赋值给User的default_profile字段。这就导致Hibernate根本不会去查询关联的Profile数据,自然获取不到。
补充一点:@ManyToOne默认的FetchType是LAZY(懒加载),但就算你改成EAGER,在使用投影的情况下也没用——因为投影明确指定了只查User的部分字段,Hibernate不会主动加载关联对象。
解决方案
方案1:取消投影,加载完整实体并主动关联查询
如果业务允许加载完整的User实体,这是最简单的解决方式:去掉投影相关代码,同时在Criteria里用fetch join主动加载关联的Profile,避免懒加载问题:
if (search.getPageNumber() >= 0 && search.getPageSize() > 0) { // 移除所有投影相关代码 // 用fetch join主动加载default_profile,这里用INNER JOIN表示User必须有对应的Profile criteria.createCriteria("default_profile", CriteriaSpecification.INNER_JOIN); // 如果需要允许User没有default_profile,改用LEFT_JOIN: // criteria.createCriteria("default_profile", CriteriaSpecification.LEFT_JOIN); users = userDalService.searchByCriteriaPaginated(criteria, search.getPageNumber(), search.getPageSize()); result.setResults(users); }
方案2:保留投影,手动映射关联数据
如果必须用投影(比如要优化性能只查部分字段),你需要手动处理关联数据的映射:
- 在投影里加入Profile的属性并起别名
- 先把结果映射到Map,再手动转成User并设置Profile对象
示例代码:
if (search.getPageNumber() >= 0 && search.getPageSize() > 0) { ProjectionList projList = Projections.projectionList(); // User基础字段 projList.add(Projections.property("login"), "login"); projList.add(Projections.property("first_name"), "first_name"); projList.add(Projections.property("last_name"), "last_name"); projList.add(Projections.property("status"), "status"); projList.add(Projections.property("UserId"), "UserId"); // 加入Profile的属性,用别名区分避免冲突 projList.add(Projections.property("default_profile.type"), "profileType"); projList.add(Projections.property("default_profile.label"), "profileLabel"); criteria.setProjection(Projections.distinct(projList)); // 先把结果映射成Map,方便后续处理 criteria.setResultTransformer(CriteriaSpecification.ALIAS_TO_ENTITY_MAP); List<Map> resultMaps = userDalService.searchByCriteriaPaginated(criteria, search.getPageNumber(), search.getPageSize()); // 手动将Map转换为User对象,并设置关联的Profile users = resultMaps.stream().map(map -> { User user = new User(); user.setLogin((String) map.get("login")); user.setFirstName((String) map.get("first_name")); user.setLastName((String) map.get("last_name")); user.setStatus((String) map.get("status")); user.setUserId((Long) map.get("UserId")); Profile profile = new Profile(); profile.setType((String) map.get("profileType")); profile.setLabel((String) map.get("profileLabel")); user.setDefault_profile(profile); return user; }).collect(Collectors.toList()); result.setResults(users); }
额外优化建议
- 实体类属性建议遵循Java驼峰命名规范,比如把
default_profile改成defaultProfile,数据库字段映射用@JoinColumn(name = "default_profile")即可,代码可读性会更好。 - 如果使用懒加载,要确保在Session/事务范围内访问关联属性,否则会抛出
LazyInitializationException。
内容的提问来源于stack exchange,提问作者adicto.verde
相关产品推荐
相关产品推荐

