Spring-Data-JPA with QueryDslPredicateExecutor and Join into a collection

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

@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对象,而存储库的客户端不会有。

我想用我的谓词来查找所有那些具有属性值(有一个连接)且值为“blue”和属性名称(还有另一个连接)且名称为“eyecolor”的人。我不确定我该如何做到这一点,并强制我只得到那些eye_color=blue的人,而不是那些shoe_color=blue的人。any()

我希望我能做这样的事情:

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

您不能直接联接谓词中的列,但可以创建如下所示的 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()

除此之外,您还可以通过Spring Data使用Querydsl查询,如下所示QueryDslPredicateExecutor

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/


答案 2

为了执行更复杂的查询,我创建了支持JPQL查询和弹簧数据JPA分页的自定义。QueryDslRepository

接口:

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());

推荐