Spring-Data-JPA с QueryDslPredicateExecutor и объединением в коллекцию

Допустим, у меня есть такая модель данных (псевдокод):

@Entity
Person {
    @OneToMany
    List<PersonAttribute> attributes;
}

@Entity
PersonAttribute {
    @ManyToOne
    AttributeName attributeName;

    String attributeValue;
}

@Entity
AttributeName {
    String name;
}

У меня есть репозиторий Spring-Data-JPA, определенный, например:

public interface PersonRepository extends PagingAndSortingRepository<Person, Long>, QueryDslPredicateExecutor<Person>{}

Я вижу в документации QueryDSL, что есть механизм для присоединения от Person к PersonAttribute, но похоже, что вам нужен доступ к объекту QueryDsl Query, которого не было бы у клиента репозитория.

Что я хотел бы сделать с моим предикатом, так это найти всех тех людей, которые имеют AttributeValue (есть одно соединение) со значением «синий» и AttributeName (есть другое соединение) с именем «цвет глаз». Я не уверен, как бы я сделал это с any() и заставил бы меня получать только те, у кого eye_color = blue, а не те, у кого shoes_color = blue.

Я надеялся, что смогу сделать что-то вроде этого:

QPerson person = QPerson.person;
QPersonAttribute attribute = person.attributes.any();

Predicate predicate = person.name.toLowerCase().startsWith("jo")
    .and(attribute.attributeName().name.toLowerCase().eq("eye color")
          .and(attribute.attributeValue.toLowerCase().eq("blue")));

но с any() там он просто соответствует чему-либо со значением атрибута «синий» и чему-либо с атрибутом «цвет глаз», независимо от цвета. Как применить эти условия к одному и тому же атрибуту в наборе?


person digitaljoel    schedule 07.02.2014    source источник


Ответы (2)


Вы не можете напрямую присоединиться к столбцу в предикате, но вы можете создать выражения any(), подобные этому

QPerson.person.attributes.any().attributeValue.eq("X")

Этот подход имеет ограничение, заключающееся в том, что выражение соединения QPerson.person.attributes.any() может использоваться только в одном фильтре. Однако преимущество заключается в том, что это выражение внутренне преобразуется в подзапрос, который не конфликтует с пейджингом.

Для нескольких ограничений вам нужно будет явно построить выражение подзапроса, подобное этому

QPersonAttribute attribute = QPersonAttribute.personAttribute;
new JPASubQuery().from(attribute)
    .where(attribute.in(person.attributes),
           attribute.attributeName().name.toLowerCase().eq("eye color"),
           attribute.attributeValue.toLowerCase().eq("blue"))
     .exists()

В дополнение к QueryDslPredicateExecutor вы также можете использовать запросы Querydsl через Spring Data, как это

public class CustomerRepositoryImpl
 extends QuerydslRepositorySupport
 implements CustomerRepositoryCustom {

    public Iterable<Customer> findAllLongtermCustomersWithBirthday() {
        QCustomer customer = QCustomer.customer;
        return from(customer)
           .where(hasBirthday().and(isLongTermCustomer()))
           .list(customer);
    }
}

Пример взят отсюда https://blog.42.nl/articles/spring-data-jpa-with-querydsl-repositories-made-easy/

person Timo Westkämper    schedule 07.02.2014
comment
ага, я это понимаю. Я пытаюсь выяснить, есть ли способ сделать это с помощью QueryDslPredicateExecutor, что означало бы, что мне не нужно было бы предоставлять какую-либо реализацию, а вызывающая сторона репозитория могла бы создавать более интересные запросы без необходимости открывать новый метод через API. В любом случае спасибо за ответ и ссылку. - person digitaljoel; 08.02.2014
comment
Отличное обновление, спасибо, проголосовал. Я знал о любом, но не видел, как присоединиться через AttributeValue к AttributeName и сопоставить их. Я обновил свой вопрос, чтобы отразить это. - person digitaljoel; 09.02.2014
comment
Тимо, я очень ценю ваше время и усилия на это. Я понимаю, что не смогу делать то, что хочу, используя только QueryDslPredicateExecutor и модуль spring-data-jpa. Немного кода никогда не помешает, я просто хотел посмотреть, каковы пределы подхода, основанного только на интерфейсе. - person digitaljoel; 10.02.2014
comment
new JPASubQuery()...exists() является предикатом, поэтому он совместим с QueryDslPredicateExecutor. - person Timo Westkämper; 10.02.2014
comment
Я знаю, что это устарело, но действует ли ограничение на пейджинг? Я использую метод any() в своем свойстве списка, и, похоже, он работает, за исключением разбиения на страницы - person Vetemi; 22.11.2018

Чтобы выполнять более сложные запросы, я создал свой собственный QueryDslRepository с поддержкой запросов JPQL и разбиением на страницы JPA данных Spring.

Интерфейс:

public interface QueryDslRepository<T> {

    Page<T> findAll(JPQLQuery<T> jpqlQuery, Pageable pageable);

}

Реализация:

@Repository
public class QueryDslRepositoryImpl<T> implements QueryDslRepository<T> {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    @SuppressWarnings("unchecked")
    public Page<T> findAll(JPQLQuery jpqlQuery, Pageable pageable) {
        Assert.notNull(jpqlQuery, "JPQLQuery must not be null!");
        Assert.notNull(pageable, "Pageable must not be null!");

        Querydsl querydsl = new Querydsl(entityManager, new PathBuilderFactory()
                                         .create(jpqlQuery.getType()));

        JPQLQuery<T> countQuery = ((AbstractJPAQuery) jpqlQuery).clone(entityManager);
        AbstractJPAQuery query = (AbstractJPAQuery) querydsl.applyPagination(pageable, jpqlQuery);
        return PageableExecutionUtils.getPage(
                  // Clone query in order to provide entity manager instance.
                  query.clone(entityManager).fetch(), 
                  pageable, 
                  countQuery::fetchCount);
    }

}

Пример использования:

@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long>, QueryDslRepository<Customer>,
        QuerydslPredicateExecutor<Customer> {

}

Фактический вызов репозитория:

 BooleanBuilder predicates = new BooleanBuilder();
 predicates = predicates.and(QCustomer.customer.active.eq(true));

 JPQLQuery<Customer> q = new JPAQuery<Customer>()
            .select(QCustomer.customer)
            // You can use .join() method here.
            .where(predicates);

 Page<Customer> result = customerRepository.findAll(q, Pageable.unpaged());
person Michał Stochmal    schedule 20.04.2020
comment
Привет, я надеялся, что это будет быстрое исправление моей мечты, но я использовал его более или менее в соответствии с вашим примером, и я получаю следующую ошибку: org.springframework.data.mapping.PropertyReferenceException: No property id found for type Void! в QueryDslRepositoryImpl.java:31 (строка «applyPagination») - я пытаюсь отсортировать по идентификатору - person wmakley; 01.07.2020