API-интерфейс JPA с несколькими параметрами

Мне нужно создать метод поиска, который использует API-интерфейс JPA с несколькими параметрами. Теперь проблема заключается в том, что не каждый параметр требуется. Таким образом, некоторые могут быть пустыми, и они не должны включаться в запрос. Я пробовал это с помощью CriteriaBuilder, но я не мог понять, как заставить его работать.

С API-интерфейсом Hibernate Criteria это довольно просто. Просто создайте критерии, а затем добавьте ограничения.

Criteria criteria = session.createCriteria(someClass.class);
if(someClass.getName() != null) {
   criteria.add(Restrictions.like("name", someClass.getName());
}

Как я могу достичь того же с API-интерфейсом JPA?

Ответ 1

Концепция состоит в том, чтобы построить массив javax.persistence.Predicate, который содержит только предикаты, которые мы хотим использовать:

Пример запрашиваемого объекта:

@Entity
public class A {
    @Id private Long id;    
    String someAttribute;
    String someOtherAttribute;
    ...
}

Сам запрос:

    //some parameters to your method
    String param1 = "1";
    String paramNull = null;

    CriteriaBuilder qb = em.getCriteriaBuilder();
    CriteriaQuery cq = qb.createQuery();
    Root<A> customer = cq.from(A.class);

    //Constructing list of parameters
    List<Predicate> predicates = new ArrayList<Predicate>();

    //Adding predicates in case of parameter not being null
    if (param1 != null) {
        predicates.add(
                qb.equal(customer.get("someAttribute"), param1));
    }
    if (paramNull != null) {
        predicates.add(
                qb.equal(customer.get("someOtherAttribute"), paramNull));
    }
    //query itself
    cq.select(customer)
            .where(predicates.toArray(new Predicate[]{}));
    //execute query and do something with result
    em.createQuery(cq).getResultList();

Ответ 2

Взгляните на этот сайт JPA Criteria API. Есть много примеров.

Обновление: Предоставление конкретного примера

Разрешите поиск Аккаунтов с балансом ниже определенного значения:

SELECT a FROM Account a WHERE a.balance < :value

Сначала создайте построитель критериев

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<Account> accountQuery = builder.createQuery(Account.class);
Root<Account> accountRoot = accountQuery.from(Account.class);
ParameterExpression<Double> value = builder.parameter(Double.class);
accountQuery.select(accountRoot).where(builder.lt(accountRoot.get("balance"), value));

Чтобы получить результат, установите параметры и запустите запрос:

TypedQuery<Account> query = entityManager.createQuery(accountQuery);
query.setParameter(value, 1234.5);
List<Account> results = query.getResultList();

Кстати: entityManager внедряется где-то в EJB/Service/DAO.

Ответ 3

Микко ответ работал прекрасно. Единственное изменение, которое мне нужно было сделать, это заменить:

cq.select(customer).where(predicates.toArray(new Predicate[]{}));

с:

Predicate [] predicatesarr = predicates.toArray(new Predicate[predicates.size()]); 
cq.select(customer).where(predicatesarr);

Где-то преобразование из списка в массив в оригинале не сработало.

Ответ 4

Во-первых, ответ Микко заставил меня ответить. Upvote для этого.

Мой сценарий состоял в том, что я хотел отношения между родителями и детьми и хотел найти совпадение для любого ребенка.

Сотрудник имеет несколько JobTitle (ов).

Я хотел найти сотрудника (где у него много названий должностей), но нашел его в любом из названий, которые я отправляю.

SQL будет выглядеть так:

Выбрать * из dbo.Employee e присоединиться к dbo.JobTitle jt e.EmployeeKey = jt.EmployeeKey WHERE (jt.JobTitleName = 'programmer' ИЛИ jt.JobTitleName = 'badcop')

Я добавил пол и дату рождения, чтобы завершить пример (и дать более "необязательные") критерии)

Мой код JPA

import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;

public class MyEmployeeSpecification implements Specification<MyEmployee> {

    private MyEmployee filter;

    public MyEmployeeSpecification(MyEmployee filter) {
        super();
        this.filter = filter;
    }

    public Predicate toPredicate(Root<MyEmployee> root, CriteriaQuery<?> cq,
                                 CriteriaBuilder cb) {

        Predicate returnPred = cb.disjunction();

        List<Predicate> patientLevelPredicates = new ArrayList<Predicate>();

        if (filter.getBirthDate() != null) {
            patientLevelPredicates.add(
                    cb.equal(root.get("birthDate"), filter.getBirthDate()));
        }

        if (filter.getBirthDate() != null) {
            patientLevelPredicates.add(
                    cb.equal(root.get("gender"), filter.getGender()));
        }

        if (null != filter.getJobTitles() && filter.getJobTitles().size() > 0) {

            List<Predicate> jobTitleLevelPredicates = new ArrayList<Predicate>();

            Join<JobTitle, JobTitle> hnJoin = root.join("jobtitles");

            for (JobTitle hnw : filter.getJobTitles()) {
                if (null != hnw) {
                    if (StringUtils.isNotBlank(hnw.getJobTitleName())) {
                        jobTitleLevelPredicates.add(cb.equal(hnJoin.get("getJobTitleName"), hnw.getFamily()));
                    }
                }
            }

            patientLevelPredicates.add(cb.or(jobTitleLevelPredicates.toArray(new Predicate[]{})));
        }

        returnPred = cb.and(patientLevelPredicates.toArray(new Predicate[]{}));

        return returnPred;
    }
}

Но я понял это благодаря предикатам. ToArray (новый Predicate [] {}), иначе говоря, трюк varargs. (Спасибо Микко)

Я также делаю метод "Спецификации реализует".

Другие полезные ссылки:

JPA Технические характеристики на примере

JPA CriteriaBuilder критерии соединения в критерии дизъюнкции

Ответ 5

Простое решение для Spring с использованием лямбда-выражений:

Specification<User> specification = (root, query, builder) -> {
    List<Predicate> predicates = new ArrayList<>();
    if (criteria.getName() != null) {
        // like
        predicates.add(builder.like(root.get("name"), "%" + criteria.getName() + "%"));
    }
    if (criteria.getParentId() != 0) {
        // equal
        predicates.add(builder.equal(root.get("parent"), criteria.getParentId()));
    }

    // AND all predicates
    return builder.and(predicates.toArray(new Predicate[0]));
};

repository.findAll(specification);