【问题标题】:JPQL How to sum up a given property of children and fetch these children avoiding n + 1 selections?JPQL如何总结孩子的给定属性并获取这些孩子避免n + 1个选择?
【发布时间】:2026-01-22 03:40:01
【问题描述】:

我有两个实体:类别和产品。它们是关联的,并且类别是父类:

@Entity
@Table(name = "categories")
public class Category {
    @Id
    @GeneratedValue(generator = "inc")
    @GenericGenerator(name = "inc", strategy = "increment")
    private int id;
    private String name;
    private int totalQuantity;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "category")
    private Set<Product> products;
  
    public Category(int id, String name, int totalQuantity, Set<Product> products) {
        this.id = id;
        this.name = name;
        this.totalQuantity = totalQuantity;
        this.products = products;
    }

产品实体:

@Entity
@Table(name = "products")
public class Product {
    @Id
    @GeneratedValue(generator = "inc")
    @GenericGenerator(name = "inc", strategy = "increment")
    private int id;
    private String name;
    private int amount;
    @ManyToOne
    @JoinColumn(name = "category_id")
    private Category category
}

totalQuantity 是与该类别关联的产品amount 的总和) 我想以防止 n + 1 并进行求和的方式获取所有类别和所有相关产品。这是我的查询错误/未完成,因为我不知道该怎么做/完成它:

@Query("SELECT new com.example.demo.category.Category(p.category.id, p.category.name, SUM(p.amount), ) FROM Product p GROUP BY p.category.id")
List<Category> findAll();

编辑: 为了更好地展示目标,我添加了我的类别视图(“前端”、“后端”)以及与它们中的每一个相关联的产品:

【问题讨论】:

    标签: spring-boot jpa jpql


    【解决方案1】:

    使用@Formula关键字做原生sql求产品金额。

    // the p.category_id=id <-- this id is Category itself id
    @Formula("(SELECT COALESCE(SUM(p.amount),0) FROM products p INNER JOIN categories c ON p.category_id=c.id WHERE p.category_id=id)")
    private int totalQuantity;
    

    注意:如果你的totalQuantity 类型是int,你需要使用COALESCE 避免空值。如果它的类型是Integer,则不需要使用它。

    使用LEFT JOIN FETCH来防止N+1问题。

    @Query("SELECT DISTINCT c FROM Category c LEFT JOIN FETCH c.products")
    List<Category> findAll()
    

    Hibernate sql 结果:

    select distinct category0_.id                         as id1_6_0_,
                    products1_.id                         as id1_11_1_,
                    category0_.name                       as name2_6_0_,
                    (SELECT COALESCE(SUM(p.amount), 0)
                     FROM product p
                              INNER JOIN categories c ON p.category_id = c.id
                     where p.category_id = category0_.id) as formula1_0_,
                    products1_.amount                     as amount2_11_1_,
                    products1_.category_id                as category4_11_1_,
                    products1_.name                       as name3_11_1_,
                    products1_.category_id                as category4_11_0__,
                    products1_.id                         as id1_11_0__
    from categories category0_
             left outer join products products1_ on category0_.id = products1_.category_id
    

    您可以使用一个查询同时获取所有类别和产品的总和。


    旧答案

    您可以使用LEFT JOIN FETCH 来防止n+1 问题。

    @Query("SELECT DISTINCT c FROM Category c LEFT JOIN FETCH c.products")
    List<Category> findAll();
    

    使用sum()运算符计算产品数量。

    List<Category> categories = categoryRepository.findAll().stream().peek(category -> {
        Set<Product> products = category.getProducts();
        if (!ObjectUtils.isEmpty(products)) {
            int totalQuantity = products.stream().mapToInt(Product::getAmount).sum();
            category.setTotalQuantity(totalQuantity);
        }
    }).collect(Collectors.toList());
    

    最后,您可以使用一个查询来获取所有类别并统计产品总数。


    【讨论】:

    • 难道不能构建这个查询来在查询中进行求和吗?
    • @Monoxyd 我更新了答案。您可以构建此查询并在查询中进行求和。
    • 看起来不错,但是当我添加@Formula 关键字时出现此错误:org.hibernate.exception.SQLGrammarException: could not extract ResultSet
    • MySQL,我的数据库名称是 shop-dbSqlExceptionHelper : SQL Error: 1146, SQLState: 42S02 2021-07-02 17:33:30.944 ERROR 7492 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper : (conn=330) Table 'shop-db.product' doesn't exist org.hibernate.exception.SQLGrammarException: could not extract ResultSet] with root cause
    • 我很困惑为什么有shop-db.product 而不是shop-db.products(我的数据库中存在表products
    【解决方案2】:

    我有一个项目有这个功能,你可以创建一个这样的实体

    SampleEntity

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "sample")
        private Set<AttributeEntity> attributes = new HashSet<>();
    

    带有实体列表。创建一个具有长期类似字段的 DTO

    SampleDTO

    当你将实体转换为 DTO 对象时,你可以使用计数,在我的例子中,我使用了 mapStruct 插件

    SampleMapper

    
        public abstract List<SampleDTO> convert(List<SampleEntity> sampleEntities);
    
        public long map(Set<AttributeEntity> past) {
            return past.size();
        }
    
    

    在我的服务中,我调用 find all

    @Override
        public List<SampleDTO> findAll() {
            List<SampleEntity> entities = sampleRepository.findAll();
            return sampleMapper.convert(entities);
        }
    

    并且存储库是 jpaRepository 的默认存储库

    
    public interface ISampleRepository extends JpaRepository<SampleEntity, UUID> , JpaSpecificationExecutor<SampleEntity> {
    
    }
    

    【讨论】: