Spring Boot Data JPA多ID循环查询数据重复问题及解决
Spring Boot原生SQL查询循环返回重复数据问题分析与修复
可能的原因
- 实体类未正确重写equals/hashCode方法:如果
LeaveApproval实体没有基于唯一标识(如headerId)重写这两个方法,Spring Data JPA的对象识别机制会误将不同数据视为同一对象,导致后续查询结果覆盖前序结果。 - 一级缓存(EntityManager缓存)复用:在同一个EntityManager会话中,首次查询的对象会被缓存。若后续查询的实体@Id字段映射错误,或
headerId并非实体的@Id,JPA会认为新查询的对象与缓存中是同一个,直接返回缓存数据。 - SQL结果映射错误:原生SQL查询的字段与实体属性映射不匹配,比如
header_id没有正确映射到实体的headerId属性,导致所有结果的headerId都被设置为第一个查询的数值。 - 参数绑定失误:循环查询时未正确更新参数(比如复用PreparedStatement却未重新设置参数),导致后续查询始终使用第一个
headerId执行。
修复方法
1. 修正实体类的equals/hashCode
基于headerId(或实体的唯一主键)重写这两个方法,确保JPA能正确区分不同对象:
import java.util.Objects; public class LeaveApproval { private Long headerId; // 其他属性、getter/setter @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LeaveApproval that = (LeaveApproval) o; return Objects.equals(headerId, that.headerId); } @Override public int hashCode() { return Objects.hash(headerId); } }
2. 清除EntityManager缓存或改用批量查询
- 清除缓存:在循环查询后调用
entityManager.clear(),避免缓存复用:
@Service public class LeaveApprovalService { @Autowired private LeaveApprovalRepository repository; @Autowired private EntityManager entityManager; public List<LeaveApproval> getApprovals(List<Long> headerIds) { List<LeaveApproval> result = new ArrayList<>(); for (Long id : headerIds) { LeaveApproval approval = repository.findByHeaderId(id); result.add(approval); entityManager.clear(); // 清除一级缓存 } return result; } }
- 推荐:改用批量查询:用
IN条件一次查询所有ID,既避免缓存问题,又提升性能:
// 在LeaveApprovalRepository中添加方法 @Query(value = "SELECT * FROM leave_approval WHERE header_id IN (:ids)", nativeQuery = true) List<LeaveApproval> findByHeaderIdsIn(@Param("ids") List<Long> ids);
3. 检查并修正SQL映射
确保原生SQL的字段与实体属性正确映射,比如用别名匹配实体属性名:
@Query(value = "SELECT header_id AS headerId, approval_status AS approvalStatus, " + "apply_user AS applyUser FROM leave_approval WHERE header_id = ?1", nativeQuery = true) LeaveApproval findByHeaderId(Long headerId);
如果使用@SqlResultSetMapping,需确保映射的列名与实体属性完全对应。
4. 修正参数绑定逻辑
若手动使用JdbcTemplate或原生JDBC,确保每次查询都正确设置当前headerId:
// 正确示例:每次查询更新参数 for (Long id : headerIds) { String sql = "SELECT * FROM leave_approval WHERE header_id = ?"; LeaveApproval approval = jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper<>(LeaveApproval.class)); result.add(approval); }
内容的提问来源于stack exchange,提问作者Habibur Rahman
相关产品推荐
相关产品推荐

