How to write a Spring Data JPA Specification with multiple joins?

不羁的心 提交于 2021-02-08 10:17:17

问题


The project I'm working on was generated with JHipster with support for entity filtering, which uses Spring Data JPA Specifications under the hood.

The model is as follows (in JDL):

entity Student {
    name String
}

entity Course {
    name String
}

entity Enrollment {
}

entity Attendance {
    date LocalDate
}

relationship OneToMany {
    Student to Enrollment(student required),
    Course to Enrollment(course required),
    Enrollment to Attendance(enrollment required)
}

filter all
service all with serviceClass

JHipster generates boilerplate for filtering Attendance by enrollmentId, but I'd like to extend it to be able to filter by studentId and courseId as well.

So, how can I implement a Specification that would perform a query like:

SELECT
  attendance.date, student.name as student, course.name as course
FROM attendance
JOIN enrollment
  ON enrollment.id = attendance.enrollment_id
JOIN student
  ON student.id = enrollment.student_id
  AND studend.id = 1
JOIN course
  ON course.id = enrollment.course_id
  AND course.id = 2;

回答1:


Following @GaëlMarziou's advice, I've implemented simple methods create the Specification<Attendance> for joining with Student and Course.

I added the fields, getters and setters to AttendanceCriteria class, and recompiled to update the JPA metamodel:

private LongFilter studentId;

private LongFilter courseId;

public LongFilter getStudentId() {
    return studentId;
}

public void setStudentId(LongFilter studentId) {
    this.studentId = studentId;
}

public LongFilter getCourseId() {
    return courseId;
}

public void setCourseId(LongFilter courseId) {
    this.courseId = courseId;
}

Here's the updated snippet from AttencanceQueryService class:

private Specification<Attendance> createSpecification(AttendanceCriteria criteria) {
    Specification<Attendance> specification = Specification.where(null);
    if (criteria != null) {
        if (criteria.getId() != null) {
            specification = specification.and(buildSpecification(criteria.getId(), Attendance_.id));
        }
        if (criteria.getDate() != null) {
            specification = specification.and(buildRangeSpecification(criteria.getDate(), Attendance_.date));
        }
        if (criteria.getEnrollmentId() != null) {
            specification = specification.and(buildReferringEntitySpecification(criteria.getEnrollmentId(), Attendance_.enrollment, Enrollment_.id));
        }
        if (criteria.getStudentId() != null) {
            specification = specification.and(buildJoinSpecification(criteria.getStudentId(), Attendance_.enrollment, Enrollment_.student, Student_.id));
        }
        if (criteria.getCourseId() != null) {
            specification = specification.and(buildJoinSpecification(criteria.getCourseId(), Attendance_.enrollment, Enrollment_.course, Course_.id));
        }
    }
    return specification;
}

private <REFERENCE, JOIN, FILTER extends Comparable<? super FILTER>> Specification<Attendance> buildJoinSpecification(RangeFilter<FILTER> filter, SingularAttribute<? super Attendance, REFERENCE> reference, SingularAttribute<REFERENCE, JOIN> joinField, SingularAttribute<JOIN, FILTER> valueField) {
    Specification<Attendance> result = Specification.where((Specification) null);
    if (filter.getEquals() != null) {
        result = this.equalsSpecification(reference, joinField, valueField, filter.getEquals());
    }
    if (filter.getIn() != null) {
        result = this.valueIn((SingularAttribute) reference, joinField, valueField, filter.getIn());
    }
    return result;
}

private <REFERENCE, JOIN, FILTER> Specification<Attendance> equalsSpecification(SingularAttribute<? super Attendance, REFERENCE> reference, SingularAttribute<REFERENCE, JOIN> joinField, SingularAttribute<JOIN, FILTER> idField, FILTER value) {
    return (root, query, builder) ->
        builder.equal(root.join(reference).join(joinField).get(idField), value);
}

private <REFERENCE, JOIN, FILTER> Specification<Attendance> valueIn(SingularAttribute<? super Attendance, REFERENCE> reference, SingularAttribute<REFERENCE, JOIN> joinField, SingularAttribute<JOIN, FILTER> valueField, Collection<FILTER> values) {
    return (root, query, builder) -> {
        CriteriaBuilder.In<FILTER> in = builder.in(root.join(reference).join(joinField).get(valueField));
        for (FILTER value : values) {
            in = in.value(value);
        }
        return in;
    };
}

I hope this helps someone, and the credit goes to @GaëlMarziou and Blackdread/Yoann Caplain.



来源:https://stackoverflow.com/questions/52424456/how-to-write-a-spring-data-jpa-specification-with-multiple-joins

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!