【问题标题】:Sum with one to many Spring Data JPA与一对多 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)。存储库中的方法太多了。

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

【问题讨论】:

    标签: spring jpa sum spring-data-jpa aggregation


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

        你需要照顾的东西:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-10-28
          • 2022-01-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-08-21
          相关资源
          最近更新 更多