JPA: выбор объектов на основе нескольких критериев для нескольких дочерних объектов - PullRequest
3 голосов
/ 16 января 2012

У меня проблема с получением следующего сценария для работы.Студент может сдавать тесты.Студент со временем прошел несколько тестов и получил оценку за каждый тест.У каждого студента есть список тестов, которые они выполнили, отображенные как @OneToMany

Теперь я хочу выбрать всех студентов, которые прошли тестирование по ряду сгруппированных критериев.Я хочу, например, найти всех студентов, которые имеют:

Группа 1: завершила «Тест 1» и получила оценку «от 75 до 100»

и /или

Группа 2: завершила «Тест 2» и получила оценку «от 50 до 80»

Это то, что у меня есть, но оно не выполняетМне нужно (не могу выполнить поиск по нескольким параметрам, что означает, что я должен выполнить запрос несколько раз):

SELECT s FROM Student s JOIN s.tests t WHERE t.score BETWEEN :minScore AND :maxScore AND t.testName = :testName

Есть ли способ использовать один NamedQuery для достижения того, что я хочу?Чтобы найти всех учащихся, которые прошли тест, который соответствует хотя бы одной из групп параметров выше?Я экспериментировал с соединениями, но продолжаю врезаться в стену.

Я сделал пример скелета кода ниже, чтобы проиллюстрировать, что я пытаюсь сделать.

@Entity
@NamedQueries({
    @NamedQuery(name="Student.findStudentByParams", query="????????") // What should this query look like to satisfy the criteria? (see below for more detail)
})
public class Student {
    // .. Some other variables that are not relevant for this example

    @Id
    private String name;

    @OneToMany(fetch=FetchType.EAGER, mappedBy = "student")
    private List<Test> tests;

    // Setters and getters
}

@Entity
public class Test {
    private double score;
    private String testName;
    // .. Some other variables that are not relevant for this example

    @ManyToOne(cascade=CascadeType.ALL)
    private Student student;

    // Setters and getters
}

public class SearchParameters {
    private double minScore;
    private double maxScore;
    private String testName; 

    public SearchParameters(String minScore, String maxScore, String testName) {
        this.minScore = minScore;
        this.maxScore = maxScore;
        this.testName = testName;
    }

    // Setters and getters
}

public class MainClass {
    public static List<Student> getStudents(List<SearchParameters> searchParams) {

        // Database initialization stuff

        // What should the query look like to find all students that match any of the combined requirements in the searchParams list? 
        // Is it possible to do in a single query or should i make multiple ones?
        // What parameters should i set? Is it possible to put in the entire array and do some sort of join?

        // Retrieve all students which matches any of these search parameters:
        // Have either:
        //      Completed "Test 1" and got a score between 75 and 100
        // and/or:
        //      Completed "Test 2" and got a score between 50 and 80
        Query namedQuery = em.createNamedQuery("Student.findStudentByParams");
        namedQuery.setParameter(??); 

        return (List<Student>)namedQuery.getResultList();

    }
    public static void main() {
        List<SearchParams> searchParams = new ArrayList<SearchParams();
        searchParams.add(new SearchParameters(75,100, "Test 1"));
        searchParams.add(new SearchParameters(50,80, "Test 2"));

        // Retrieve all students which matches any of these search parameters:
        // Have either:
        //      Completed "Test 1" and got a score between 75 and 100
        // and/or:
        //      Completed "Test 2" and got a score between 50 and 80
        ArrayList<Student> students = getStudents(searchParams);
        for(Student s: students) // Print all user that match the criteria
        {
            System.out.println("Name: " + s.getName());
        }
    }   
}

Ответы [ 2 ]

3 голосов
/ 16 января 2012

Вам необходимо использовать Criteria Builder (и, в конечном итоге, каноническую метамодель).

Попробуйте что-то вроде этого (код не проверен):

EntityManager em;    // put here your EntityManager instance
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Student> cq = cb.createQuery(Student.class);
Root<Student> student = cq.from(Student.class);
Predicate predicate = cb.disjunction();
for (SearchParams param : searchParams) {
    ListJoin<Student, Test> tests = student.join(Student_.tests);
    Predicate tempPredicate1 = cb.equal(tests.get(Test_.testName), param.getTestName());
    Predicate tempPredicate2 = cb.ge(tests.get(Test_.score), param.getMinScore());
    Predicate tempPredicate3 = cb.le(tests.get(Test_.score), param.getMaxScore());
    Predicate tempPredicate = cb.and(tempPredicate1, tempPredicate2, tempPredicate3);
    predicate = cb.or(predicate, tempPredicate);
}
cq.where(predicate);
TypedQuery<Student> tq = em.createQuery(cq);
return tq.getResultList();
1 голос
/ 16 января 2012

Я не понимаю, как это было бы возможно без составления динамического запроса.Подумайте об использовании Criteria API для его создания.

Я бы разработал запрос следующим образом:

select s from Student s where
    exists (select t.id from Test t where t.student.id = s.id and ...)
or
    exists (select t.id from Test t where t.student.id = s.id and ...)
or
    exists (...)

Как видите, есть повторяющийся шаблон, и все эти подзапросы похожи и объединяютсяв дизъюнкции.

...