与一对多 Spring Data JPA 求和

Posted

技术标签:

【中文标题】与一对多 Spring Data JPA 求和【英文标题】:Sum with one to many Spring Data JPA 【发布时间】:2014-02-17 23:14:18 【问题描述】:

我试图用一对多关系求和,如下关系所示(仅显示父级):

@Entity
@Table(name = "Parent")
public class Parent implements Serializable 
    private static final long serialVersionUID = -7348332185233715983L;

    @Id
    @Basic(optional = false)
    @Column(name = "PARENT_ID")
    private Long parentId;

    @OneToMany
    @JoinColumn(name="CHILDREN", referencedColumnName="PARENT_ID")
    private List<Child> children;

    @Formula("(select sum(select height from children))")
    private BicDecimal totalHeight

它非常简单,没有任何限制,甚至有静态限制。但是,当子列表受到动态限制时,我遇到了麻烦。

就我而言,我使用的是 spring data 和 jpa。我正在使用规范来限制孩子并获得适当的孩子列表,但显然总和仍然适用于不受限制的孩子,因为@Formula 标记中没有 where 子句。

出于性能原因和结果是分页的,我不想在 java 中遍历列表。此外,总和不是分页结果的总和,而是所有结果的总和。

我是 Spring Data/JPA 的新手。从历史上看,我可以动态构建此查询或使用休眠条件。我可以运行一个完整的单独查询来进行此计算。我不需要使用 @Formula 注释,因为每次调用只有 1 个聚合。在休眠框架中,我可以将 select 子句声明为“sum(field)”并构建标准。在 Spring Data/JPA 框架中,我可以构建涵盖标准的规范,但我不知道如何操作查询的选择部分,因为它似乎与实体紧密相关。

如果我知道我需要限制哪些字段,则在存储库上使用 @Query 注释可以作为自己的查询,但通常这些字段为空并且需要在查询中忽略。有 8 个可能的字段,给我留下了 256 种可能的组合 (2^8)。存储库中的方法太多了。

在切换框架之外有什么想法吗?

【问题讨论】:

【参考方案1】:

因为我最近遇到了类似的问题,所以发布了这个老问题的答案。

我决定使用自定义存储库,该存储库具有一种基于传入其中的任何规范进行聚合的方法。可以组合规范以组成动态标准(参见 org.springframework.data.jpa.domain.Specifications)

所以我的以上儿童身高问题的存储库如下所示:

package something

import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

@Repository
public class ChildHeightRepository 

    @PersistenceContext
    EntityManager entityManager;


    public Long getTotalHeight(Specification<Child> spec) 

        CriteriaBuilder cb = entityManager.getCriteriaBuilder();

        CriteriaQuery query = cb.createQuery(Long.class);

        Root root = query.from(Child.class);

        return ((Long) entityManager.createQuery(
                query.where(spec.toPredicate(root, query, cb))
                        .select(cb.sum(root.get("height")))).getSingleResult());


    

【讨论】:

【参考方案2】:

你有没有在 JPQL 中尝试过

select sum(d.height) from Parent a join a.children d

如果你不想忽略空值

select sum(d.height) from Parent a left join a.children d

我认为您的其他问题是如何根据属性进行过滤。我的意思是,如果您需要一个包含多种组合的 where 语句。

为什么您不尝试使用列表并将您想要应用的所有谓词添加到列表中,具体取决于您想要的组合。示例

创建查询

   CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery cq = cb.createQuery(Double.class);
    Root<Parent> root = cq.from(Parent.class)
    Join<Parent, Child> join = cq.join(("children")); 
    cq.select(cb.<Double>sum(join.get("height")));

创建谓词列表

List<Predicates> listOfpredicates = new ArrayList<Predicates>();
if(property1 != null && !"".equals(property1)
      PatameterExpression<String> p = cb.parameter(String.class, "valueOfproperty1")
      listOfpredicates.add(cb.equal(join.get("property1"),p);
 

然后添加到 CriteriaQuery

if(listOfPredicates.size() == 1)
   cq.where(listOfPredicates.get(0))
else
   cq.where(cb.and(listOfPredicates.toArray(new Predicate[0])));

最后执行查询。

TypedQuery<Double> q = em.createQuery(cq);
q.getResultList();

这将使用任意组合动态创建您的查询。

【讨论】:

【参考方案3】:

晚了 6 年,但我还是花了一段时间才让它工作,这是我为您的地图制作的方法:

@Formula("(select sum(children.height) from children_table children inner join Parent p on children.parent_id=p.id where children.parent_id=parent_id)")
private BicDecimal totalHeight

你需要照顾的东西:

    () 添加到公式的开头和结尾,否则 sql 的语法将无法正确翻译。 据我所知,公式查询是本机 SQL 查询,而不是 JPQL,有人可能想就此纠正我吗? 属性是表的属性,而不是您在 Java 中命名属性的名称,因此列名和表名必须是表名。

【讨论】:

以上是关于与一对多 Spring Data JPA 求和的主要内容,如果未能解决你的问题,请参考以下文章

spring Data jpa 一对多关联 动态查询怎么写

Spring-Data-Jpa 中一对多映射的 JSON 结果错误

spring data jpa关联查询(一对一对多多对多)

spring-data-jpa一对多多对一多对多关联

使用spring boot和spring data jpa时一对多关系的奇怪行为

外键在一对多关系中始终为空 - Spring Boot Data with JPA