You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Data JPA左连接查询返回重复记录问题求助

解决Spring Data JPA原生查询返回重复条目问题

我一眼就看出问题出在哪了——你的GetList实体把t1.id当成了唯一主键@Id,但JPA是通过主键来识别实体唯一性的。当你的左连接查询返回多条同一t1.id的结果时(比如id=605对应4条t2的记录),JPA会觉得这些都是同一个实体,最终返回的列表里就会重复出现最后那条被映射的实体(也就是你看到的4 605重复四次)。

给你两个可行的解决方案,按需选择:

方案一:改用复合主键(推荐,适合需要实体映射的场景)

因为你的查询结果里,真正唯一的标识是t1.id + t2.t2Id的组合,所以需要把这两个字段设为复合主键:

  1. 先创建一个复合主键类,必须实现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方法
}
  1. 修改你的GetList实体,用复合主键替代原来的单一主键:
@Entity
public class GetList {
    @EmbeddedId
    private GetListId id;

    // 如果需要t1的status字段,也可以加上
    @Column(name = "status")
    private String status;

    // getter和setter方法
}
  1. 调整查询语句,确保返回所有需要的字段(包括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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 21:42:51