使用QueryDslPredicateExecutor和Joining的Spring-Data-JPA集合查询

16

假设我有一个像这样的数据模型(伪代码):

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

@Entity
PersonAttribute {
    @ManyToOne
    AttributeName attributeName;

    String attributeValue;
}

@Entity
AttributeName {
    String name;
}

我有一个定义为Spring-Data-JPA repository的:

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

我在QueryDSL文档中看到有一种从Person到PersonAttribute的Join机制,但似乎需要访问QueryDsl查询对象,而仓库的客户端不会有该对象。

我的Predicate想要做的是查找所有具有AttributeValue(一个join)值为"blue"和AttributeName(另一个join)名称为"eyecolor"的人。我不确定如何使用any()来实现,以确保仅获取具有eye_color=blue而非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(),它将匹配任何具有属性值“blue”和任何具有“眼睛颜色”属性的内容,无论其颜色如何。 我该如何使这些条件适用于集合内相同的属性?

2个回答

22

您无法直接在谓词中连接列,但可以创建类似于这样的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之外,您还可以通过Spring Data使用Querydsl查询,如下所示:

public class CustomerRepositoryImpl
 extends QuerydslRepositorySupport
 implements CustomerRepositoryCustom {

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

这个例子取自这里


1
是的,我理解了。我正在尝试看看是否有一种方法可以使用QueryDslPredicateExecutor来实现它,这意味着我不需要提供任何实现,存储库的调用者可以制作更有趣的查询,而无需我通过API公开新方法。无论如何,感谢您的答案和链接。 - digitaljoel
非常棒的更新,谢谢,已点赞。我知道“any”的用法,但不知道如何将“AttributeValue”和“AttributeName”连接起来并匹配它们。我已经更新了我的问题以反映这一点。 - digitaljoel
Timo,非常感谢你在这方面所花费的时间和精力。我明白仅使用QueryDslPredicateExecutor和spring-data-jpa模块是无法实现我想要的功能的。稍微写一点代码也不会有什么问题,我只是想看看仅使用接口的方法的限制在哪里。 - digitaljoel
new JPASubQuery()...exists() 是一个 Predicate,因此它与 QueryDslPredicateExecutor 兼容。 - Timo Westkämper
我知道这是老问题,但分页限制仍然适用吗?我在我的列表属性上使用了any()方法,似乎除了分页之外都可以工作。 - Vetemi

9
为了执行更复杂的查询,我创建了自己的QueryDslRepository接口,并支持JPQL查询和Spring Data JPA分页。
接口:
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());

嗨,我本来希望这是我的梦幻快速解决方案,但我已经按照您的示例使用它了,但我收到以下错误:org.springframework.data.mapping.PropertyReferenceException: No property id found for type Void! in QueryDslRepositoryImpl.java:31('applyPagination'行)- 我正在尝试按“id”排序。 - wmakley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接