Hibernate查询重复返回最后一条记录问题求助
我之前碰到过一模一样的问题,这种情况大多和Hibernate处理结果集的映射逻辑有关,尤其是当你只查询实体的部分字段而非整个实体的时候。结合你的场景,我给你几个可行的排查和解决方向:
1. 先确认实体的主键映射是否正确
你的ImpiantiGestitiDto中id是自增integer,一定要检查主键的注解配置是否正确:
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) // 对应MySQL自增策略 private Integer id;
Hibernate在处理结果集时,会依赖主键来区分不同的记录,如果主键映射有问题,Hibernate会把所有行识别为同一个实体,最终导致所有结果都被最后一行覆盖。
2. 使用构造函数查询(最直接的解决方案)
当你执行SELECT ig.id, ig.codice FROM ImpiantiGestitiDto as ig这类部分字段查询时,Hibernate默认会复用对象实例来填充结果,这就是导致数据覆盖的核心原因。你可以通过构造函数查询强制Hibernate为每一行创建新的实例:
首先在ImpiantiGestitiDto中添加对应字段的构造函数:
public ImpiantiGestitiDto(Integer id, String codice) { this.id = id; this.codice = codice; }
然后修改HQL语句为:
SELECT new ImpiantiGestitiDto(ig.id, ig.codice) FROM ImpiantiGestitiDto as ig
这样Hibernate会为每一条查询结果创建独立的ImpiantiGestitiDto实例,就不会出现所有行都是最后一条记录的问题了。
3. 改用Tuple或自定义投影接收结果
如果不想修改实体的构造函数,你可以用Tuple来接收结果,确保每一行都是独立的数据项:
Query<Tuple> query = session.createQuery(hql, Tuple.class); List<Tuple> resultList = query.getResultList(); for (Tuple tuple : resultList) { Integer id = tuple.get("id", Integer.class); String codice = tuple.get("codice", String.class); // 处理每一行数据 }
或者使用ResultTransformer将结果转为Map集合,避免对象复用:
Query<Object[]> query = session.createQuery(hql); query.setResultTransformer(new ResultTransformer() { @Override public Object transformTuple(Object[] tuple, String[] aliases) { Map<String, Object> rowMap = new HashMap<>(); for (int i = 0; i < aliases.length; i++) { rowMap.put(aliases[i], tuple[i]); } return rowMap; } @Override public List transformList(List collection) { return collection; } }); List<Map<String, Object>> resultList = query.getResultList();
4. 排查缓存配置问题
如果你的项目开启了二级缓存或查询缓存,也可能导致结果被错误缓存覆盖。可以临时关闭查询缓存测试:
query.setCacheable(false);
如果关闭后结果正常,就需要调整缓存的策略或配置,避免对这类部分字段查询进行缓存。
补充说明
你提到FROM ImpiantiGestitiDto order by id可以正常工作,是因为Hibernate查询整个实体时,会为每一行创建独立的实体实例,不会复用对象;而部分字段查询默认会采用更轻量的对象复用逻辑,才导致了这个问题。
内容的提问来源于stack exchange,提问作者Filippo De Bellis

