Spring Boot JPA多对多查询:查找指定学生共同参加的课程
问题描述
现有学生S1、S2、S3、S4,课程C1、C2、C3。其中S1、S2参加C1,S1、S2、S3参加C2。需求:查找所有S1和S2共同参加的课程(预期结果为C1、C2),以下是基于Spring JPA/CRUD Repository的几种实现方案。
实体类代码如下:
class Course { @Id private String id; private String name; @ManyToMany(fetch = FetchType.EAGER) //debugging purpouses @JoinTable(name = "course_students", joinColumns = @JoinColumn(name = "course_id"), inverseJoinColumns = @JoinColumn(name = "student_id")) Set<Student> students; } class Student { @Id String id; String firstName; String middleName; String lastName; String phoneNumber; String email; String avatar; int age; @ManyToMany(fetch = FetchType.EAGER, mappedBy = "students") Set<Course> courses; }
方案1:JPQL动态查询(支持任意数量学生)
在CourseRepository中定义通用查询方法,通过JPQL筛选包含所有指定学生的课程:
public interface CourseRepository extends JpaRepository<Course, String> { @Query("SELECT c FROM Course c JOIN c.students s WHERE s.id IN :studentIds GROUP BY c.id HAVING COUNT(DISTINCT s.id) = :studentCount") List<Course> findCommonCoursesByStudentIds(@Param("studentIds") List<String> studentIds, @Param("studentCount") int studentCount); }
调用示例(假设S1的ID为"1",S2的ID为"2"):
List<String> targetStudentIds = Arrays.asList("1", "2"); List<Course> commonCourses = courseRepository.findCommonCoursesByStudentIds(targetStudentIds, 2);
原理:通过关联课程与学生表,筛选出关联指定学生的课程后分组,统计每组的不同学生数量等于目标学生总数,确保课程包含所有指定学生。
方案2:Spring Data方法命名(仅适用于固定2个学生)
如果仅需查询两个学生的共同课程,可直接利用Spring Data的方法命名规则:
public interface CourseRepository extends JpaRepository<Course, String> { List<Course> findByStudentsContainsAndStudentsContains(Student student1, Student student2); }
调用示例:
Student s1 = studentRepository.findById("1").orElseThrow(); Student s2 = studentRepository.findById("2").orElseThrow(); List<Course> commonCourses = courseRepository.findByStudentsContainsAndStudentsContains(s1, s2);
注意:该方法简洁但灵活性差,无法支持动态数量的学生查询。
方案3:Specification动态查询(复杂场景适配)
若需支持更灵活的动态条件(比如动态选择多个学生),可使用Spring Data的Specification:
- 定义Specification实现:
public class CourseSpecifications { public static Specification<Course> containsAllStudents(List<Student> targetStudents) { return (root, query, criteriaBuilder) -> { Join<Course, Student> studentJoin = root.join("students"); // 按课程分组,统计关联的不同学生数量等于目标学生数 query.groupBy(root.get("id")); query.having(criteriaBuilder.equal(criteriaBuilder.countDistinct(studentJoin.get("id")), targetStudents.size())); // 筛选关联学生在目标列表中的课程 return studentJoin.in(targetStudents); }; } }
- 让Repository继承
JpaSpecificationExecutor:
public interface CourseRepository extends JpaRepository<Course, String>, JpaSpecificationExecutor<Course> { }
- 调用示例:
List<Student> targetStudents = Arrays.asList(s1, s2); List<Course> commonCourses = courseRepository.findAll(CourseSpecifications.containsAllStudents(targetStudents));
内容的提问来源于stack exchange,提问作者Nenad Jovanovski
相关产品推荐
相关产品推荐

