【问题标题】:Spring-Data-JPA with QueryDslPredicateExecutor and Joining into a collectionSpring-Data-JPA 与 QueryDslPredicateExecutor 并加入集合
【发布时间】:2014-03-05 10:45:51
【问题描述】:

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

@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(有一个连接)和一个名称为“eyecolor”的 AttributeName(还有另一个连接)的人。我不确定如何使用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(),它只匹配任何具有“蓝色”属性值的东西和任何具有“眼睛颜色”属性的东西,而不管颜色如何。 如何使这些条件适用于集合中的同一属性?

【问题讨论】:

    标签: java spring-data spring-data-jpa querydsl


    【解决方案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()
    

    除了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);
        }
    }
    

    示例取自这里https://blog.42.nl/articles/spring-data-jpa-with-querydsl-repositories-made-easy/

    【讨论】:

    • 是的,我明白了。我正在尝试查看是否有办法使用 QueryDslPredicateExecutor 来执行此操作,这意味着我不必提供任何实现,并且存储库的调用者可以制作更有趣的查询,而无需我通过公开新方法api。无论如何,感谢您的回答和链接。
    • 很棒的更新,谢谢,点赞。我知道任何,但看不到如何通过 AttributeValue 加入 AttributeName 并将它们匹配起来。我已经更新了我的问题以反映这一点。
    • Timo,非常感谢您为此付出的时间和精力。我了解仅使用 QueryDslPredicateExecutor 和 spring-data-jpa 模块将无法做我想做的事情。一点代码也没有什么坏处,我只是想看看纯接口方法的局限性是什么。
    • new JPASubQuery()...exists() 是一个谓词,因此它与 QueryDslPredicateExecutor 兼容。
    • 我知道这是旧的,但分页限制仍然适用吗?我在我的 List 属性上使用了 any() 方法,除了分页之外它似乎可以工作
    【解决方案2】:

    为了执行更复杂的查询,我创建了自定义 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”排序
    猜你喜欢
    • 1970-01-01
    • 2014-12-29
    • 2017-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-12
    • 1970-01-01
    • 2020-07-05
    相关资源
    最近更新 更多