Spring Data JPA左连接查询返回重复记录问题求助
解决Spring Data JPA原生查询返回重复条目问题
我一眼就看出问题出在哪了——你的GetList实体把t1.id当成了唯一主键@Id,但JPA是通过主键来识别实体唯一性的。当你的左连接查询返回多条同一t1.id的结果时(比如id=605对应4条t2的记录),JPA会觉得这些都是同一个实体,最终返回的列表里就会重复出现最后那条被映射的实体(也就是你看到的4 605重复四次)。
给你两个可行的解决方案,按需选择:
方案一:改用复合主键(推荐,适合需要实体映射的场景)
因为你的查询结果里,真正唯一的标识是t1.id + t2.t2Id的组合,所以需要把这两个字段设为复合主键:
- 先创建一个复合主键类,必须实现
Serializable:
import java.io.Serializable; import java.util.Objects; import javax.persistence.Embeddable; @Embeddable public class GetListId implements Serializable { @Column(name = "id") private Integer id; @Column(name = "t2Id") private Integer t2Id; // JPA要求必须有无参构造函数 public GetListId() {} public GetListId(Integer id, Integer t2Id) { this.id = id; this.t2Id = t2Id; } // 重写equals和hashCode,确保复合主键的唯一性判断正确 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GetListId that = (GetListId) o; return Objects.equals(id, that.id) && Objects.equals(t2Id, that.t2Id); } @Override public int hashCode() { return Objects.hash(id, t2Id); } // 记得加getter和setter方法 }
- 修改你的
GetList实体,用复合主键替代原来的单一主键:
@Entity public class GetList { @EmbeddedId private GetListId id; // 如果需要t1的status字段,也可以加上 @Column(name = "status") private String status; // getter和setter方法 }
- 调整查询语句,确保返回所有需要的字段(包括status如果需要的话):
@Query(value = "SELECT t1.id, im.t2Id, t1.status FROM t1 LEFT JOIN t2 im ON t1.id=im.id", nativeQuery = true) public List<GetList> getList();
这样JPA就能正确识别每一条不同的结果,不会再返回重复条目了。
方案二:用DTO或Object数组接收结果(适合不需要实体的场景)
如果只是需要获取数据,不需要把结果映射成JPA实体,可以跳过主键的限制:
方式A:返回Object数组
直接修改返回类型为List<Object[]>,然后在业务代码里手动提取字段:
@Query(value = "Select * FROM t1 LEFT JOIN t2 im ON t1.id=im.id", nativeQuery = true) public List<Object[]> getList();
遍历的时候,比如Object[] row = list.get(0);,row[0]对应t1的status,row[1]对应t1的id,row[2]对应t2的t2Id,依此类推(具体索引对应数据库表的字段顺序)。
方式B:用DTO接收
创建一个普通的DTO类(不需要加@Entity注解):
public class GetListDTO { private Integer id; private Integer t2Id; private String status; // 构造函数的参数顺序要和查询返回的字段顺序完全一致 public GetListDTO(String status, Integer id, Integer t2Id) { this.status = status; this.id = id; this.t2Id = t2Id; } // getter和setter方法 }
然后改用JPQL查询来映射到DTO(比原生查询映射更简单):
@Query(value = "SELECT new com.yourpackage.GetListDTO(t1.status, t1.id, im.t2Id) FROM t1 LEFT JOIN t2 im ON t1.id=im.id") public List<GetListDTO> getList();
注意把com.yourpackage换成你实际的DTO包路径。
为什么原来的方法会失败?
简单来说,JPA的持久化上下文(一级缓存)会跟踪实体的主键,当查询返回多条主键相同的结果时,它会认为这些是同一个实体实例,只会保留最后一次加载的那个,然后重复添加到结果列表里,就出现了你看到的重复问题。
内容的提问来源于stack exchange,提问作者Manak Bhardwaj
相关产品推荐
相关产品推荐

