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, который не был бы у клиента репозитория.

То, что я хотел бы сделать с моим Predicate, - это найти всех тех лиц, у которых есть AttributeValue (там одно соединение) со значением "blue" и AttributeName (там еще одно соединение) с именем "eyecolor". Я не уверен, как бы это сделать с помощью any() и обеспечить, что я получаю только те, у которых есть eye_color = синий, а не те, у которых есть shoe_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() там он просто соответствует любому значению атрибута "синий" и любому атрибуту "цвет глаз" независимо от цвета. Как я могу применить эти условия к одному и тому же атрибуту в наборе?

Ответ 1

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

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 Данные, подобные этому

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/