JPQL 如何总结给定的 属性 个子项并获取这些子项避免 n + 1 个选择?
JPQL How to sum up a given property of children and fetch these children avoiding n + 1 selections?
我有两个实体:类别和产品。它们是关联的并且类别是父级:
@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 并进行求和的方式获取所有类别和所有相关产品。这是我的查询 wrong/uncompleted 因为我不知道如何 do/complete 它:
@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();
编辑:
为了更好地展示目标,我添加了类别视图(“前端”、“后端”)以及与每个类别相关联的产品:
我有一个项目有这个功能,你可以像这样创建一个实体
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "sample")
private Set<AttributeEntity> attributes = new HashSet<>();
带有一个实体列表。创建一个包含长期字段的 DTO,如
当您将实体转换为 DTO 对象时,您可以使用计数,在我的例子中,我使用了 mapStruct 插件
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> {
}
使用@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;
Note: If your totalQuantity type is int, you need to use COALESCE
avoid the null value. If it type is Integer, you don't need to use it.
使用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());
最后,您可以使用一个查询来获取所有类别并统计产品的总量。
我有两个实体:类别和产品。它们是关联的并且类别是父级:
@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 并进行求和的方式获取所有类别和所有相关产品。这是我的查询 wrong/uncompleted 因为我不知道如何 do/complete 它:
@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();
编辑:
为了更好地展示目标,我添加了类别视图(“前端”、“后端”)以及与每个类别相关联的产品:
我有一个项目有这个功能,你可以像这样创建一个实体
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "sample")
private Set<AttributeEntity> attributes = new HashSet<>();
带有一个实体列表。创建一个包含长期字段的 DTO,如
当您将实体转换为 DTO 对象时,您可以使用计数,在我的例子中,我使用了 mapStruct 插件
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> {
}
使用@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;
Note: If your totalQuantity type is int, you need to use
COALESCE
avoid the null value. If it type is Integer, you don't need to use it.
使用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());
最后,您可以使用一个查询来获取所有类别并统计产品的总量。