1. 概述

在开发中,我们经常需要从数据库获取唯一数据。本文重点介绍如何使用Spring Data JPA实现数据去重查询,涵盖获取唯一实体和特定字段的多种方法。

2. 场景搭建

首先创建两个实体类:School(学校)和Student(学生)用于演示:

@Entity
@Table(name = "school")
public class School {
    @Id
    @Column(name = "school_id")
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private int id;

    @Column(name = "name", length = 100)
    private String name;

    @OneToMany(mappedBy = "school")
    private List<Student> students;

    // 构造函数、getter和setter
}
@Entity
@Table(name = "student")
public class Student {
    @Id
    @Column(name = "student_id")
    private int id;

    @Column(name = "name", length = 100)
    private String name;

    @Column(name = "birth_year")
    private int birthYear;

    @ManyToOne
    @JoinColumn(name = "school_id", referencedColumnName = "school_id")
    private School school;

    // 构造函数、getter和setter
}

我们定义了一对多关系:每个学校关联多个学生。

3. 去重实体查询

现在创建Repository接口,根据学生出生年份获取唯一学校数据。Spring Data JPA提供多种实现方式,第一种是使用派生查询:

@Repository
public interface SchoolRepository extends JpaRepository<School, Integer> {
    List<School> findDistinctByStudentsBirthYear(int birthYear);
}

这个派生查询直观易懂,能根据学生出生年份获取唯一的School实体。调用方法时,控制台会输出JPA生成的SQL(注意关联字段未被查询):

Hibernate: 
    select
        distinct s1_0.school_id,
        s1_0.name 
    from
        school s1_0 
    left join
        student s2_0 
            on s1_0.school_id=s2_0.school_id 
    where
        s2_0.birth_year=?

✅ 如果只需要去重计数而非完整实体,只需将方法名中的find替换为count

Long countDistinctByStudentsBirthYear(int birthYear);

4. 自定义查询实现字段去重

⚠️ 某些场景下不需要查询实体所有字段(比如Web界面搜索结果)。此时可通过限制查询字段提升性能,特别是在结果集较大时。

假设我们只需要获取唯一的学校名称,使用@Query创建自定义查询:

@Query("SELECT DISTINCT sch.name FROM School sch JOIN sch.students stu WHERE stu.birthYear = :birthYear")
List<String> findDistinctSchoolNameByStudentsBirthYear(@Param("birthYear") int birthYear);

执行时会生成更高效的SQL(只查询name字段):

Hibernate: 
    select
        distinct s1_0.name 
    from
        school s1_0 
    join
        student s2_0 
            on s1_0.school_id=s2_0.school_id 
    where
        s2_0.birth_year=?

5. 投影实现字段去重

Spring Data JPA的投影(Projections)功能是自定义查询的替代方案,允许获取实体部分字段而非全部。

创建投影接口(注意方法名必须与实体getter一致):

public interface NameView {
    String getName();
}

在Repository中添加查询方法:

List<NameView> findDistinctNameByStudentsBirthYear(int birthYear);

生成的SQL与自定义查询版本类似:

Hibernate: 
    select
        distinct s1_0.name 
    from
        school s1_0 
    left join
        student s2_0 
            on s1_0.school_id=s2_0.school_id 
    where
        s2_0.birth_year=?

6. 总结

本文探讨了Spring Data JPA实现数据去重的三种方案:

  • 派生查询:适合获取完整去重实体
  • 自定义查询:精准控制查询字段
  • 投影:声明式字段映射

根据实际需求选择合适方案,避免踩坑。完整代码示例可在GitHub获取。


原始标题:Find Distinct Rows Using Spring Data JPA | Baeldung